We are looking into the possibility of abandoning NHibernate in favor of OpenAccess, and so far everything I've seen has me firmly convinced that OA is the right tool. Working with NH for the past year has been a somewhat painful experience, and discovering OA was eye-opening, since you seem to be spot-on about a number of things that NH does not do "correctly" (from my personal point of view.)
Looking forward, there are two concerns I'm stuck with.
Our existing NH project makes extensive use of what's called "component mappings" in NH - so to describe a practical example, I have a type called a "financial profile", and one of my entities has two different "financial profiles" attached to it, a "customer financial profile" and a "vendor financial profile". Both of these are mapped to the same table, together with other properties of the entity. So in the table, you see property names like "Name" and "City" (the entity's own properties), along with "CustomerFinance_TaxPayerId" and "VendorFinance_TaxPayerId" ... simplified code sample:
public
class
Location
{
public
string
Name {
get
;
set
; }
public
string
City {
get
;
set
; }
// ... (many other properties) ...
public
LocationFinance CustomerFinance {
get
;
set
; }
public
LocationFinance VendorFinance {
get
;
set
; }
}
public
class
LocationFinance
{
public
string
TaxPayerId {
get
;
set
; }
// ... (many other properties) ...
}
Rather than mapping these to separate tables, the two LocationFinance instances are merely two sets of columns (with a prefix) in the same table.
What is the equivalent of a component mapping in OA? Or does it not exist?
My second question is about interfaces - for example, I have multiple entities that implement an Address interface with properties like City, Street, State, Country, etc... in the visual designer, I can add an interface and model that, but what I don't understand is, how do you specify that an entity in your diagram "implements" an interface? I couldn't find anything in the help file.
(PS: I'm not trying to get free tech support or trying to avoid purchasing a license - as soon as I have answers to these questions, I can make an official recommendation that we purchase licenses for OA for the next version of the software we're building; in other words, I'm evaluating for purchase, and at this point would gladly pay and work hard to be rid of NH...)
8 Answers, 1 is accepted
Thank you for your interest in Telerik OpenAccess ORM.
Unfortunately the "component mapping" is not supported by OpenAccess ORM, currently we require each class to be mapped to a separate table. While it is one of the features to be introduced in the long run, we do not have immediate plans to implement it in the upcoming releases. If this limitation is a problem for your implementation, let us know and we will try to help you find a suitable workaround.
Regarding the usage of interfaces, you should explicitly define inheritance between the Interface element and the Domain Class element in the Visual Designer. You can do that through the Inheritance element from the Visual Studio toolbox. An explanation can be found in the Complex Inheritance documentation article.
Do not hesitate to contact us if you have any questions or you need further clarifications.
Best wishes,
Ivailo
the Telerik team
NEW and UPDATED OpenAccess ORM Resources. Check them out!
For a simple scenario where a Customer has a MailingAddress and ShippingAddress, both of type Address, here's what I came up with:
// Generic Address component:
public
class
Address<TEntity>
where TEntity :
class
{
public
delegate
string
GetString(TEntity customer);
public
delegate
void
SetString(TEntity customer,
string
value);
public
Address(
TEntity customer,
GetString getStreet,
SetString setStreet,
GetString getCity,
SetString setCity,
GetString getCountry,
SetString setCountry
)
{
_customer = customer;
_getStreet = getStreet;
_setStreet = setStreet;
_getCity = getCity;
_setCity = setCity;
_getCountry = getCountry;
_setCountry = setCountry;
}
private
readonly
TEntity _customer;
private
readonly
GetString _getStreet;
private
readonly
SetString _setStreet;
private
readonly
GetString _getCity;
private
readonly
SetString _setCity;
private
readonly
GetString _getCountry;
private
readonly
SetString _setCountry;
public
string
Street
{
get
{
return
_getStreet(_customer); }
set
{ _setStreet(_customer, value); }
}
public
string
City
{
get
{
return
_getCity(_customer); }
set
{ _setCity(_customer, value); }
}
public
string
Country
{
get
{
return
_getCountry(_customer); }
set
{ _setCountry(_customer, value); }
}
}
// Customer.generated.cs:
public
partial
class
Customer
{
protected
string
MailingAddress_Street {
get
;
set
; }
protected
string
MailingAddress_City {
get
;
set
; }
protected
string
MailingAddress_Country {
get
;
set
; }
protected
string
ShippingAddress_Street {
get
;
set
; }
protected
string
ShippingAddress_City {
get
;
set
; }
protected
string
ShippingAddress_Country {
get
;
set
; }
}
// Customer.cs:
public
partial
class
Customer
{
public
Customer()
{
MailingAddress =
new
Address<Customer>(
this
,
getStreet: c => c.MailingAddress_Street,
setStreet: (c, value) => c.MailingAddress_Street = value,
getCity: c => c.MailingAddress_City,
setCity: (c, value) => c.MailingAddress_City = value,
getCountry: c => c.MailingAddress_Country,
setCountry: (c, value) => c.MailingAddress_Country = value
);
ShippingAddress =
new
Address<Customer>(
this
,
getStreet: c => c.ShippingAddress_Street,
setStreet: (c, value) => c.ShippingAddress_Street = value,
getCity: c => c.ShippingAddress_City,
setCity: (c, value) => c.ShippingAddress_City = value,
getCountry: c => c.ShippingAddress_Country,
setCountry: (c, value) => c.ShippingAddress_Country = value
);
}
public
Address<Customer> MailingAddress {
get
;
private
set
; }
public
Address<Customer> ShippingAddress {
get
;
private
set
; }
}
That's pretty terrible, both in terms of complexity and chance of error.
I'm not sure how to solve this more elegantly, except perhaps relying on conventions and reflection...
// Generic base-class for components:
abstract
public
class
Component<TEntity>
where TEntity :
class
{
public
Component(TEntity entity,
string
prefix)
{
_entity = entity;
_prefix = prefix;
}
private
readonly
TEntity _entity;
private
readonly
string
_prefix;
protected
TValue Get<TValue>(Expression<Func<Address<TEntity>,
object
>> expression)
{
var name = ((MemberExpression) expression.Body).Member.Name;
return
(TValue)
_entity.GetType()
.GetProperty(_prefix + name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.GetProperty)
.GetValue(_entity,
null
);
}
protected
void
Set<TValue>(Expression<Func<Address<TEntity>,
object
>> expression,
object
value)
{
var name = ((MemberExpression)expression.Body).Member.Name;
_entity.GetType()
.GetProperty(_prefix + name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.SetProperty)
.SetValue(_entity, value,
null
);
}
}
// Address component:
public
class
Address<TEntity> : Component<TEntity>
where TEntity :
class
{
public
Address(TEntity entity,
string
prefix) :
base
(entity, prefix)
{
}
public
string
Street
{
get
{
return
Get<
string
>(x => x.Street); }
set
{ Set<
string
>(x => x.Street, value); }
}
public
string
City
{
get
{
return
Get<
string
>(x => x.City); }
set
{ Set<
string
>(x => x.City, value); }
}
public
string
Country
{
get
{
return
Get<
string
>(x => x.Country); }
set
{ Set<
string
>(x => x.Country, value); }
}
}
// Customer.generated.cs:
public
partial
class
Customer
{
protected
string
MailingAddress_Street {
get
;
set
; }
protected
string
MailingAddress_City {
get
;
set
; }
protected
string
MailingAddress_Country {
get
;
set
; }
protected
string
ShippingAddress_Street {
get
;
set
; }
protected
string
ShippingAddress_City {
get
;
set
; }
protected
string
ShippingAddress_Country {
get
;
set
; }
}
// Customer.cs:
public
partial
class
Customer
{
public
Customer()
{
MailingAddress =
new
Address<Customer>(
this
,
"MailingAddress_"
);
ShippingAddress =
new
Address<Customer>(
this
,
"ShippingAddress_"
);
}
public
Address<Customer> MailingAddress {
get
;
private
set
; }
public
Address<Customer> ShippingAddress {
get
;
private
set
; }
}
Most of the complexity is now out of the Address-component and Customer-entity, at the cost of compile-time type-checking - so I still don't think this is ideal, but it's a start.
The convention/expectation is that the component-owner will prefix component-properties consistently - they can then be kept protected in the generated portion of the entity.
Of course, this would work much better if the code-generator would do the work, since it can perform compile-time checking, which would eliminate errors in user-code - and user's responsibility would be limited to calling a generated method that initializes the components. For example:
// Vendor-supplied generic base-class for components:
abstract
public
class
Component<TEntity>
where TEntity :
class
{
public
Component(TEntity entity,
string
prefix)
{
_entity = entity;
_prefix = prefix;
}
private
readonly
TEntity _entity;
private
readonly
string
_prefix;
protected
TValue Get<TValue>(Expression<Func<Address<TEntity>,
object
>> expression)
{
var name = ((MemberExpression) expression.Body).Member.Name;
return
(TValue)
_entity.GetType()
.GetProperty(_prefix + name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.GetProperty)
.GetValue(_entity,
null
);
}
protected
void
Set<TValue>(Expression<Func<Address<TEntity>,
object
>> expression,
object
value)
{
var name = ((MemberExpression)expression.Body).Member.Name;
_entity.GetType()
.GetProperty(_prefix + name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.SetProperty)
.SetValue(_entity, value,
null
);
}
}
// Vendor-supplied interface for component initialization:
public
interface
ICreateComponents
{
void
CreateComponents();
}
// Address.generated.cs:
public partial
class
Address<TEntity> : Component<TEntity>
where TEntity :
class
{
public
Address(TEntity entity,
string
prefix) :
base
(entity, prefix)
{
}
public
string
Street
{
get
{
return
Get<
string
>(x => x.Street); }
set
{ Set<
string
>(x => x.Street, value); }
}
public
string
City
{
get
{
return
Get<
string
>(x => x.City); }
set
{ Set<
string
>(x => x.City, value); }
}
public
string
Country
{
get
{
return
Get<
string
>(x => x.Country); }
set
{ Set<
string
>(x => x.Country, value); }
}
}
// Customer.generated.cs:
public
partial
class
Customer : ICreateComponents
{
protected
string
MailingAddress_Street {
get
;
set
; }
protected
string
MailingAddress_City {
get
;
set
; }
protected
string
MailingAddress_Country {
get
;
set
; }
protected
string
ShippingAddress_Street {
get
;
set
; }
protected
string
ShippingAddress_City {
get
;
set
; }
protected
string
ShippingAddress_Country {
get
;
set
; }
public
Address<Customer> MailingAddress {
get
;
private
set
; }
public
Address<Customer> ShippingAddress {
get
;
private
set
; }
public
void
CreateComponents()
{
MailingAddress =
new
Address<Customer>(
this
,
"MailingAddress_"
);
ShippingAddress =
new
Address<Customer>(
this
,
"ShippingAddress_"
);
}
}
// Customer.cs:
public
partial
class
Customer
{
public
Customer()
{
CreateComponents();
}
}
This would take away any concerns about making errors when writing out components by hand - the only possible error is you forget to call the generated CreateComponents() in your constructor, the code-generator can check everything else at run-time.
This seems like a perfectly good solution for what you are trying to achieve, however keep in mind that you will not be able to use the extra (component) classes that you have added in LINQ queries, our engine will not be able to translate this to SQL given that it does not know about them.
I would rather suggest that you implement a DTO layer over OpenAccess that provides object of custom shapes and can translate back and forth between the DTOs and OpenAccess objects. As a matter of fact we are working on a code generation layer that will provide the basis for such an abstraction over OpenAccess, this will be available for Q3 which is scheduled for release in the middle of November.
Nevertheless, thank you for sharing your findings with the OpenAccess community.
Serge
the Telerik team
NEW and UPDATED OpenAccess ORM Resources. Check them out!
Of course, you're right - that had not even occurred to me.
The DTO layer does sound like a better approach - I will definitely look into that when Q3 is released.
Thanks!
You can find the new wizard by right clicking on an rlinq (or on a web project) and selecting "Generate OpenAccess Domain Service...". Please note that it is still marked as a beta.
Unfortunately though there are two issues with the new wizard that you might stumble upon. First the menu command is sometimes called "MRU Placeholder...", so if you can't find it try this one. And second the information about your model isn't always retrieved correctly. Rebuilding the solution usually fixes that one.
I am looking forward to any feedback you might have.
Serge
the Telerik team