trouble with one-way association from base class (horizontal inheritance)?

4 posts, 1 answers
  1. Dan
    Dan avatar
    1 posts
    Member since:
    Jan 2009

    Posted 12 Apr 2012 Link to this post

    I'm having trouble getting a one-way association to work from a base class (Transaction) to another persistent class (Profile) using the foreign key column I've specified in ToColumn().

    Using horizontal inheritance in code-only mappings, I defined a base class (Transaction) and several derived classes (TrxAddCustomer, TrxReceivePayment, etc). Each of these transaction records needs to point to a Profile, but I don't want the Profile to be littered with a collection for every transaction type, so I'm using a one-way association.

    I found that I had to define all of the HasProperty statements in the derived classes, even for those properties that were already defined in base classes. This was annoying, knowing that the metadata for the base class was already defined once, and that setting column types or other parameters in the base class were ignored instead of being inherited by the derived classes. Because of this, I tried adding the association from each Transaction-derived class to the Profile class, but I got the error about the property's backing field not existing in the derived class (again, OpenAccess being unintelligent in looking for where they're defined), so I moved the association to the configuration of the base class. Despite using ToColumn("ProfileOid") to designate the foreign key column, OpenAccess creates and uses a column called Oid2.

    Here is my test data model: 

    public abstract class ModelObject
    {
        public Guid Oid { get; set; }
    }
     
    public class Profile : ModelObject
    {
        public string Name { get; set; }
    }
     
    public abstract class Transaction : ModelObject
    {
        public string TransactionCode { get; set; }
     
        public Profile Profile { get; set; }
        public Guid ProfileOid { get; set; }
    }
     
    public class TrxGetBallmerDrunk : Transaction
    {
        public string Joke { get; set; }
    }

    My code-only fluent mapping looks like this:

    protected override IList<MappingConfiguration> PrepareMapping()
    {
        var configurations = new List<MappingConfiguration>();
     
        var ModelObjectConfig = new MappingConfiguration<ModelObject>();
        ModelObjectConfig.MapType(
            x => new
            {
                Oid = x.Oid
            })
            .Inheritance(InheritanceStrategy.Horizontal);
     
        var TransactionConfig = new MappingConfiguration<Transaction>();
        TransactionConfig.MapType(
            x => new
            {
                Profile = x.Profile,
                ProfileOid = x.ProfileOid,
                TransactionCode = x.TransactionCode
            })
            .Inheritance(InheritanceStrategy.Horizontal);
        TransactionConfig.HasAssociation(x => x.Profile).ToColumn("ProfileOid");
     
        var ProfileConfig = new MappingConfiguration<Profile>();
        ProfileConfig.MapType(x => new
            {
                Oid = x.Oid,
                Name = x.Name
            }).ToTable("Profiles");
        ProfileConfig.HasProperty(x => x.Oid).IsIdentity(KeyGenerator.Guid);
        ProfileConfig.HasProperty(x => x.Name).HasLength(20);
     
        var BallmerConfig = new MappingConfiguration<TrxGetBallmerDrunk>();
        BallmerConfig.MapType(x => new
            {
                Oid = x.Oid,
                TransactionCode = x.TransactionCode,
                Joke = x.Joke,
                Profile = x.Profile,
                ProfileOid = x.ProfileOid
            }).ToTable("TrxGetBallmerDrunk");
        BallmerConfig.HasProperty(x => x.Oid).IsIdentity(KeyGenerator.Guid);
        BallmerConfig.HasProperty(x => x.TransactionCode).HasLength(20);
        BallmerConfig.HasProperty(x => x.Joke).HasLength(100);
     
        configurations.Add(ModelObjectConfig);
        configurations.Add(TransactionConfig);
        configurations.Add(ProfileConfig);
        configurations.Add(BallmerConfig);
     
        return configurations;
    }

    I just downloaded OpenAccess a week or two ago, so I assume I'm using the latest version. VS2010, Win7, SQL Server. I was going to attach my complete Visual Studio test solution with the problem. but the forums here don't allow it? Bummer. Here is that Sample Project.

    I attached an image to this post showing the query results.
  2. Answer
    PetarP
    Admin
    PetarP avatar
    754 posts

    Posted 17 Apr 2012 Link to this post

    Hi Dan,

     This is a know issue when creating associations in horizontally mapped class. The problem lies in the fact that indeed the field name cannot be directly inferred from your class hierarchy. 
    A simple workaround for this would be to specify the field name with its fully qualified name (including the name of the class in which it is defined.
    This said your association needs to be defined on your TrxGetBallmerDrunk  
     class (as you have attempted earlier). The only change would be that you will have to give reference to the actual field (in that case Transaction.profile).

    public abstract class ModelObject
       {
           public Guid Oid { get; set; }
       }
     
       public class Profile : ModelObject
       {
           public string Name { get; set; }
       }
     
       public abstract class Transaction : ModelObject
       {
           public string TransactionCode { get; set; }
           private Profile profile;
     
           public Profile Profile
           {
               get
               {
                   return this.profile;
               }
               set
               {
                   this.profile = value;
               }
           }
     
           public Guid ProfileOid { get; set; }
       }
     
       public class TrxGetBallmerDrunk : Transaction
       {
           public string Joke { get; set; }
       }

    And the actual mapping:
    protected override IList<MappingConfiguration> PrepareMapping()
            {
                var configurations = new List<MappingConfiguration>();
     
                var ModelObjectConfig = new MappingConfiguration<ModelObject>();
                ModelObjectConfig.MapType(
                    x => new
                    {
                        Oid = x.Oid
                    })
                    .Inheritance(InheritanceStrategy.Horizontal);
     
                var TransactionConfig = new MappingConfiguration<Transaction>();
                TransactionConfig.MapType(
                    x => new
                    {
                        Profile = x.Profile,
                        ProfileOid = x.ProfileOid,
                        TransactionCode = x.TransactionCode
                    })
                    .Inheritance(InheritanceStrategy.Horizontal);
     
                var ProfileConfig = new MappingConfiguration<Profile>();
                ProfileConfig.MapType(x => new
                {
                    Oid = x.Oid,
                    Name = x.Name
                }).ToTable("Profiles");
                ProfileConfig.HasProperty(x => x.Oid).IsIdentity(KeyGenerator.Guid);
                ProfileConfig.HasProperty(x => x.Name).HasLength(20);
     
                var BallmerConfig = new MappingConfiguration<TrxGetBallmerDrunk>();
                BallmerConfig.MapType(x => new
                {
                    Oid = x.Oid,
                    TransactionCode = x.TransactionCode,
                    Joke = x.Joke,
                    Profile = x.Profile,
                    ProfileOid = x.ProfileOid
                }).ToTable("TrxGetBallmerDrunk");
                BallmerConfig.HasProperty(x => x.Oid).IsIdentity(KeyGenerator.Guid);
                BallmerConfig.HasProperty(x => x.TransactionCode).HasLength(20);
                BallmerConfig.HasProperty(x => x.Joke).HasLength(100);
                BallmerConfig.HasAssociation(x => x.Profile).HasFieldName("Transaction.profile").ToColumn("ProfileOid");
     
     
                configurations.Add(ModelObjectConfig);
                configurations.Add(TransactionConfig);
                configurations.Add(ProfileConfig);
                configurations.Add(BallmerConfig);
     
                return configurations;
            }
    I hope that this works for you.

    Kind regards,
    Petar
    the Telerik team
    Share your passion about the Telerik Open Access by voting for Telerik's powerful ORM tool here >>
  3. DevCraft banner
  4. Can
    Can avatar
    4 posts
    Member since:
    Sep 2014

    Posted 13 Apr 2015 Link to this post

    I am having the same issue. Above example did not work for me. I have created a simple model below:

     

    public abstract class HasName
     {
         public string Name { get; set; }
     }
     
     public abstract class HasCity : HasName
     {
         public int CityID { get; set; }
     
         private City _city;
         public City City { get { return _city; } set{_city = value;} }
     }
     
     public class Cat : HasCity
     {
         public int CatID { get; set; }
     }
     
     public class Dog : HasCity
     {
         public int DogID { get; set; }
     }
     
     public class City : HasName
     {
         public int CityID { get; set; }       
     }

     

    And mapping:

     

    protected override IList<MappingConfiguration> PrepareMapping()
    {
        List<MappingConfiguration> mappingConfigurations = new List<MappingConfiguration>();
     
        MappingConfiguration<HasName> beingConfiguration = new MappingConfiguration<HasName>();
        beingConfiguration.MapType().Inheritance(Telerik.OpenAccess.InheritanceStrategy.Horizontal);
        mappingConfigurations.Add(beingConfiguration);
     
        MappingConfiguration<HasCity> livesInACityConfig = new MappingConfiguration<HasCity>();
        livesInACityConfig.MapType().Inheritance(Telerik.OpenAccess.InheritanceStrategy.Horizontal);
        mappingConfigurations.Add(livesInACityConfig);
     
     
        MappingConfiguration<City> cityConfiguration = new MappingConfiguration<City>();
        cityConfiguration.MapType().WithConcurencyControl(OptimisticConcurrencyControlStrategy.Changed).ToTable("City");
        cityConfiguration.HasProperty(x => x.CityID).IsIdentity(KeyGenerator.Autoinc).WithDataAccessKind(DataAccessKind.ReadWrite).ToColumn("CityID").IsNotNullable().HasColumnType("int").HasPrecision(0).HasScale(0);
        mappingConfigurations.Add(cityConfiguration);
     
     
        MappingConfiguration<Dog> dogConfiguration = new MappingConfiguration<Dog>();
        dogConfiguration.MapType().ToTable("Dog");
        dogConfiguration.HasProperty(x => x.DogID).IsIdentity(KeyGenerator.Autoinc).WithDataAccessKind(DataAccessKind.ReadWrite).ToColumn("DogID").IsNotNullable().HasColumnType("int").HasPrecision(0).HasScale(0);
        dogConfiguration.HasAssociation(x => x.City).HasFieldName("HasCity._city").ToColumn("CityID");
        mappingConfigurations.Add(dogConfiguration);
     
     
        MappingConfiguration<Cat> catConfiguration = new MappingConfiguration<Cat>();
        catConfiguration.MapType().WithConcurencyControl(OptimisticConcurrencyControlStrategy.Changed).ToTable("Cat");
        catConfiguration.HasProperty(x => x.CatID).IsIdentity(KeyGenerator.Autoinc).WithDataAccessKind(DataAccessKind.ReadWrite).ToColumn("CatID").IsNotNullable().HasColumnType("int").HasPrecision(0).HasScale(0);
        catConfiguration.HasAssociation(x => x.City).HasFieldName("HasCity._city").ToColumn("CityID");
        mappingConfigurations.Add(catConfiguration);
         
        return mappingConfigurations;
    }

     

     

    Below sample code throws null reference exception when dog.City.Name is called

     

    FluentModel mod = new FluentModel();
    City cit = new City();
    cit.Name = "test city";
    mod.Add(cit);
    mod.SaveChanges();
     
     
    Dog dog = new Dog();
    dog.Name = "test dog";
    dog.CityID = 1;
    mod.Add(dog);
    mod.SaveChanges();
     
     
    FluentModel mod2 = new FluentModel();
     
    foreach (var ct in mod2.Cities)
    {
        Console.WriteLine(ct.CityID + " - " + ct.Name);
    }
     
    foreach (var dg in mod2.Dogs)
    {
        Console.WriteLine(dg.DogID + " - " + dg.Name + " - " + dg.CityID);
        //dg.CityID = 1 and there is a city with ID = 1
        Console.WriteLine(dg.City.Name);
        //but this throws null reference exception as dg.City is null
    }

     

    I am using the latest version.

     

     

     

     

     

     

     

  5. Yavor Slavchev
    Admin
    Yavor Slavchev avatar
    22 posts

    Posted 16 Apr 2015 Link to this post

    Hello Can,
    Thank you for contacting us.

    The association of the "Dog" at "Cat" class to the "City" is made through a property of the base "HasCity" class but due to the Horizontal type of inheritance for this class, it does not have a corresponding table in the database. This is the reason this property returns null when you try to access it. However, you can overcome this limitation by moving the "City" property of the base "HasCity" class in each of its inheritors. This will allow Telerik Data Access to correctly wire the association and then the navigation "City" property should work.
    Other approach would be to play around with the flat inheritance type and try to achieve the same behavior.

    I hope this information is helpful.

    Regards,
    Yavor Slavchev
    Telerik
     
    OpenAccess ORM is now Telerik Data Access. For more information on the new names, please, check out the Telerik Product Map.
     
Back to Top