Can I make a Fluent NHibernate foreign key convention which includes parent key name?

后端 未结 3 648
被撕碎了的回忆
被撕碎了的回忆 2020-12-17 21:07

I have a database schema where the convention for a foreign key\'s name is:

ForeignTable.Name + ForeignTable.PrimaryKeyName

So, for a

相关标签:
3条回答
  • 2020-12-17 21:52

    For a system wide convention I believe this would serve the purpose best. ( I wasn't sure whether to include the whole text or just a portion here, since I answered it here already)

    Here's the solution with links to current Fluent NHibernate & automapping documentation.

    The issue (a simple example):

    Say you have the simple example (from fluent's wiki) with an Entity and it's Value Objects in a List:

    public class Product
    {
      public virtual int Id { get; set; }
      //..
      public virtual Shelf { get; set; }
    }
    
    public class Shelf
    {
      public virtual int Id { get;  set; }
      public virtual IList<Product> Products { get; set; }
    
      public Shelf()
      {
        Products = new List<Product>();
      }
    }
    

    With tables which have e.g.

    Shelf 
    id int identity
    
    Product 
    id int identity 
    shelfid int
    

    And a foreign key for shelfid -> Shelf.Id


    You would get the error: invalid column name ... shelf_id


    Solution:

    Add a convention, it can be system wide, or more restricted.

    ForeignKey.EndsWith("Id")
    

    Code example:

    var cfg = new StoreConfiguration();
    var sessionFactory = Fluently.Configure()
      .Database(/* database config */)
      .Mappings(m =>
        m.AutoMappings.Add(
          AutoMap.AssemblyOf<Product>(cfg)
              .Conventions.Setup(c =>
                  {
                      c.Add(ForeignKey.EndsWith("Id"));
                  }
        )
      .BuildSessionFactory();
    

    Now it will automap the ShelfId column to the Shelf property in Product.


    More info

    Wiki for Automapping

    Table.Is(x => x.EntityType.Name + "Table")
    PrimaryKey.Name.Is(x => "ID")
    AutoImport.Never()
    DefaultAccess.Field()
    DefaultCascade.All()
    DefaultLazy.Always()
    DynamicInsert.AlwaysTrue()
    DynamicUpdate.AlwaysTrue()
    OptimisticLock.Is(x => x.Dirty())
    Cache.Is(x => x.AsReadOnly())
    ForeignKey.EndsWith("ID")
    

    See more about Fluent NHibernate automapping conventions

    0 讨论(0)
  • 2020-12-17 22:10

    Take a look at conventions and especially at implementing a custom foreign key convention.


    UPDATE:

    Here's an example. Assuming the following domain:

    public class Parent
    {
        public virtual int Id { get; set; }
    }
    
    public class Child
    {
        public virtual string Id { get; set; }
        public virtual Parent Parent { get; set; }
    }
    

    which needs to be mapped to this schema:

    create table Child(
        Id integer primary key, 
        ParentId integer
    )
    
    create table Parent(
        Id integer primary key
    )
    

    you could use this convention:

    public class CustomForeignKeyConvention : IReferenceConvention
    {
        public void Apply(IManyToOneInstance instance)
        {
            instance.Column(instance.Class.Name + "Id");
        }
    }
    

    and to create the session factory:

    var sf = Fluently
        .Configure()
        .Database(
            SQLiteConfiguration.Standard.UsingFile("data.db3").ShowSql()
        )
        .Mappings(
            m => m.AutoMappings.Add(AutoMap
                .AssemblyOf<Parent>()
                .Where(t => t.Namespace == "Entities")
                .Conventions.Add<CustomForeignKeyConvention>()
            )
        )
        .BuildSessionFactory();
    
    0 讨论(0)
  • 2020-12-17 22:12

    If you can get the Mapping<T> for a class, you can get the name of its Id column.

    public class MyForeignKeyConvention: ForeignKeyConvention
    {
        public static IList<IMappingProvider> Mappings = new List<IMappingProvider>();
    
        protected override string GetKeyName( System.Reflection.PropertyInfo property, Type type )
        {
            var pk = "Id";
    
            var model = new PersistenceModel();
            foreach( var map in Mappings ) {
                model.Add( map );
            }
    
            try {
                var mymodel = (IdMapping) model.BuildMappings()
                    .First( x => x.Classes.FirstOrDefault( c => c.Type == type ) != null )
                    .Classes.First().Id;
    
                Func<IdMapping, string> getname = x => x.Columns.First().Name;
                pk = getname( mymodel );
            } catch {
            }
    
            if (property == null) {
                return type.Name + pk;
            }
            return type.Name + property.Name;
        }
    }
    

    We can get the Mapping object with a little bit of plumbing.

    The constructors of ClassMap<T> can pass this into our collection of Mappers.

    For AutoMapping<T>, we can use Override as follows.

    .Mappings( m => m.AutoMappings.Add( AutoMap.AssemblyOf<FOO>()
        .Override<User>( u => {
            u.Id( x => x.Id ).Column( "UID" );
            MyForeignKeyConvention.Mappings.Add( u );
        }
    )
    
    0 讨论(0)
提交回复
热议问题