"Component mappings" (NH term) and interfaces in OA?

9 posts, 0 answers
  1. Rasmus
    Rasmus avatar
    6 posts
    Member since:
    Oct 2011

    Posted 24 Oct 2011 Link to this post

    Hello,

    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...)
  2. Ivailo
    Admin
    Ivailo avatar
    318 posts

    Posted 26 Oct 2011 Link to this post

    Hello Rasmus,

    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!

  3. DevCraft banner
  4. Rasmus
    Rasmus avatar
    6 posts
    Member since:
    Oct 2011

    Posted 26 Oct 2011 Link to this post

    I'm definitely open to ideas for a component work-around.

    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...
  5. Rasmus
    Rasmus avatar
    6 posts
    Member since:
    Oct 2011

    Posted 26 Oct 2011 Link to this post

    Here's my take on a generic component-abstraction, using the LINQ Expression-type...

    // 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.
  6. Rasmus
    Rasmus avatar
    6 posts
    Member since:
    Oct 2011

    Posted 26 Oct 2011 Link to this post

    (PS: I know you said this feature is not on the roadmap for an update in the near future, but it was a tiny stretch from what I came up with as my work-around, so I thought I'd pitch the idea anyway...)
  7. Serge
    Admin
    Serge avatar
    375 posts

    Posted 31 Oct 2011 Link to this post

    Hello Rasmus,

     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.

    Greetings,
    Serge
    the Telerik team

    NEW and UPDATED OpenAccess ORM Resources. Check them out!

  8. Rasmus
    Rasmus avatar
    6 posts
    Member since:
    Oct 2011

    Posted 31 Oct 2011 Link to this post

    Hi Serge,

    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!
  9. Rasmus
    Rasmus avatar
    6 posts
    Member since:
    Oct 2011

    Posted 02 Feb 2012 Link to this post

    I am currently evaluating the Q3 release - can you point me to an example (or documentation) demonstrating/explaining how to implement custom shapes?
  10. Serge
    Admin
    Serge avatar
    375 posts

    Posted 06 Feb 2012 Link to this post

    Hi Rasmus,

     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. 

    Greetings,
    Serge
    the Telerik team
    Sharpen your .NET Ninja skills! Attend Q1 webinar week and get a chance to win a license! Book your seat now >>
Back to Top
DevCraft banner