FluentNHibernate: Automapping OneToMany relation using attribute and convention
Asked Answered
L

2

6

This is very similar to my previous question: FluentNHibernate: How to translate HasMany(x => x.Addresses).KeyColumn("PersonId") into automapping


Say I have these models:

public class Person
{
    public virtual int Id { get; private set; }
    public virtual ICollection<Address> Addresses { get; private set; }
}

public class Address
{
    public virtual int Id { get; private set; }
    public virtual Person Owner { get; set; }
}

I want FluentNHibernate to create the following tables:

Person
    PersonId
Address
    AddressId
    OwnerId

This can be easily achieved by using fluent mapping:

public class PersonMapping : ClassMap<Person>
{
    public PersonMapping()
    {
        Id(x => x.Id).Column("PersonId");
        HasMany(x => x.Addresses).KeyColumn("OwnerId");
    }
}

public class AddressMapping : ClassMap<Address>
{
    public AddressMapping()
    {
        Id(x => x.Id).Column("AddressId");
        References(x => x.Person).Column("OwnerId");
    }
}

I want to get the same result by using auto mapping. I tried the following conventions:

class PrimaryKeyNameConvention : IIdConvention
{
    public void Apply(IIdentityInstance instance)
    {
        instance.Column(instance.EntityType.Name + "Id");
    }
}

class ReferenceNameConvention : IReferenceConvention
{
    public void Apply(IManyToOneInstance instance)
    {
        instance.Column(string.Format("{0}Id", instance.Name));
    }
}

// Copied from @Fourth: https://mcmap.net/q/1777129/-fluentnhibernate-how-to-translate-hasmany-x-gt-x-addresses-keycolumn-quot-personid-quot-into-automapping/6091307#6091307
public class SimpleForeignKeyConvention : ForeignKeyConvention
{
    protected override string GetKeyName(Member property, Type type)
    {
        if(property == null)
            return type.Name + "Id";
        return property.Name + "Id";
    }
}

But it created the following tables:

Person
    PersonId
Address
    AddressId
    OwnerId
    PersonId // this column should not exist

So I added a AutoMappingOverride:

public class PersonMappingOverride : IAutoMappingOverride<Person>
{
    public void Override(AutoMapping<Person> mapping)
    {
        mapping.HasMany(x => x.Addresses).KeyColumn("OwnerId");
    }
}

This correctly solved the problem. But I want to get the same result using attribute & convention. I tried:

public class Person
{
    public virtual int Id { get; private set; }

    [KeyColumn("OwnerId")]
    public virtual ICollection<Address> Addresses { get; private set; }
}

class KeyColumnAttribute : Attribute
{
    public readonly string Name;

    public KeyColumnAttribute(string name)
    {
        Name = name;
    }
}

class KeyColumnConvention: IHasManyConvention
{
    public void Apply(IOneToManyCollectionInstance instance)
    {
        var keyColumnAttribute = (KeyColumnAttribute)Attribute.GetCustomAttribute(instance.Member, typeof(KeyColumnAttribute));
        if (keyColumnAttribute != null)
        {
            instance.Key.Column(keyColumnAttribute.Name);
        }
    }
}

But it created these tables:

Person
    PersonId
Address
    AddressId
    OwnerId
    PersonId // this column should not exist

Below is the rest of my code:

ISessionFactory sessionFactory = Fluently.Configure()
    .Database(MsSqlConfiguration.MsSql2008.ConnectionString(connectionString))
    .Mappings(m =>
                m.AutoMappings.Add(AutoMap.Assemblies(typeof(Person).Assembly)
                    .Conventions.Add(typeof(PrimaryKeyNameConvention))
                          .Conventions.Add(typeof(PrimaryKeyNameConvention))
                          .Conventions.Add(typeof(ReferenceNameConvention))
                          .Conventions.Add(typeof(SimpleForeignKeyConvention))
                          .Conventions.Add(typeof(KeyColumnConvention)))

                //m.FluentMappings
                //    .Add(typeof (PersonMapping))
                //    .Add(typeof (AddressMapping))
    )
    .ExposeConfiguration(BuildSchema)
    .BuildConfiguration()
    .BuildSessionFactory();

Any ideas? Thanks.


Update:

The test project can be downloaded from here.

Loincloth answered 23/5, 2011 at 0:3 Comment(0)
L
6

Sigh... Learning NHibernate is really a hair pulling experience.

Anyway I think I finally figured out how to solve this problem: Just remove the SimpleForeignKeyConvention and everything will work fine.

It seems the SimpleForeignKeyConvention conflicts with both ReferenceKeyConvention & KeyColumnConvention. It has higher priority than KeyColumnConvention but lower priority than ReferenceKeyConvention.

public class SimpleForeignKeyConvention : ForeignKeyConvention
{
    protected override string GetKeyName(Member property, Type type)
    {
        if(property == null)
            // This line will disable `KeyColumnConvention`
            return type.Name + "Id";

        // This line has no effect when `ReferenceKeyConvention` is enabled.
        return property.Name + "Id";
    }
}
Loincloth answered 24/5, 2011 at 1:58 Comment(2)
care to accept the answer, so others know its the solution to your problemKimbrakimbrell
@Firo: I got a message "You can accept your own answer in x hours" (at the moment, x = 16). I will try again tomorrow.Loincloth
M
1

I've tested your classes with FHN's auto-mapping feature and it does not create that second PersonId on Address table. I'm using FHN v1.2.0.721 from here

Madelyn answered 23/5, 2011 at 4:35 Comment(2)
Thanks for testing! Did you include PersonMappingOverride into the configuration? If so, yes it does not create that second PersonId on Address table. But it is supposed to be excluded from configuration, because I want to get the same result by using KeyColumnAttribute & KeyColumnConvention. I've updated my question with a download link of my test project, which should reproduce the issue.Loincloth
Now you have changed the names of the properties, right? (to prove that ...) So plz read this similar thread (To avoid having two different foreign key columns…): #311141Madelyn

© 2022 - 2024 — McMap. All rights reserved.