EF代码优先:重复的外键(一个从命名规则,从一个导航属性)(EF Code First: Dupli

2019-10-19 08:18发布

我试图通过使用EF代码首先创建一个SQL数据库。

假设我有以下代码:

public class Account
{
    public int Id;
    public ICollection<User> Users;
}

public class User
{
    public int Id;
    public int AccountId;
}

public class AccountContext : DbContext
{
    public DbSet<Account> Accounts;
    public DbSet<User> Users;
}

(注意,没有任何流利的API命令或数据注释的;我想按照惯例做到这一点。)

当创建数据库时,我得到了用户表中的以下字段:

Id
AccountId
Account_Id

为什么不EF账户拿起一个事实,即“ACCOUNTID”指的是“ID”主键(按照约定)? 我想如果可能的话,以这种手动流利的API / DA避免映射,我想,以避免用户的帐户导航属性。

Answer 1:

只有两种方法,我知道如何执行你在找什么做的,无论是数据注解(非常快)或流利的映射。 你不能只是说public int AccountId; 并期望一切工作。

流利的API映射双向

public class Account
{
   public int Id { get; set; }

   public ICollection<User> Users { get; set; }
}

public class User
{
   public int Id { get; set; }

   public Account Account { get; set; }
}

public class AccountContext : DbContext
{
   public AccountContext()
        : base("DefaultConnection")
    {

    }

   public DbSet<Account> Accounts { get; set; }
   public DbSet<User> Users { get; set; }

   protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<User>().HasRequired(u => u.Account) 
            .WithMany(a => a.Users) 
            .HasForeignKey(u => u.AccountId);
    }
}

数据标注双向

public class Account
{
   [Key]
   public int Id { get; set; }

   public ICollection<User> Users { get; set; }
}

public class User
{
   [Key]
   public int Id { get; set; }

   [ForeignKey("Account"), DatabaseGenerated(DatabaseGeneratedOption.None)]
   public int AccountId { get; set; }

   public Account Account { get; set; }
}

// and of course you need your context class, but with less code

public class AccountContext : DbContext
{
   public AccountContext()
        : base("DefaultConnection")
    {

    }

   public DbSet<Account> Accounts { get; set; }

   public DbSet<User> Users { get; set; }       
}

没有数据注释或流畅API映射双向

public class Account
{
   public int Id { get; set; } //as Id in Accounts Table

   public ICollection<User> Users { get; set; }
}

public class User
{
   public int Id { get; set; } //as Id in Users Table

   public Account Account { get; set; } // as Account_Id in Users Table
}

// and of course you need your context class, but with less code

public class AccountContext : DbContext
{
   public AccountContext()
        : base("DefaultConnection")
    {

    }

   public DbSet<Account> Accounts { get; set; }

   public DbSet<User> Users { get; set; }       
}

我希望这可以帮助你或任何人怀疑别人

编辑

如果你想避免Bi-Directional导航然后更改Users喜欢这个

public class User
{
   public int Id { get; set; }

   //delete below from User class to avoid Bi-directional navigation 
   //public Account Account { get; set; }
}

注:没有测试但逻辑是合理的



Answer 2:

您可以编写自己的自定义的公约,但它真的会变得复杂(例如如果Account有两个集合的导航性能User S'怎么会EF知道哪个集合导航属性是由单一的引用User.AccountId财产?有无数种可能注意事项/陷阱,它可能无法考虑到他们所有。

我下面的例子将在你所描述的情况下工作,但可能会开始打破,如果你的模型变得更加复杂。 你应该过滤可能的实体类型那些你希望在你的模型(下面的例子检查在应用程序域中所有类型)。 我强烈建议你干脆用流利的API或数据注解在下面,但它确实工作,为您所陈述的需求,是一个自定义的公约,一个有趣的例子。

// I recommend filtering this
var possibleEntityTypes = AppDomain.CurrentDomain.GetAssemblies()
    .SelectMany( ass => ass.GetTypes() );

modelBuilder.Properties()
    .Where( cp => 
        IsValidForeignKeyType( cp.PropertyType ) &&
        cp.Name.Length > 2 && 
        ( cp.Name.EndsWith( "ID" ) || cp.Name.EndsWith( "Id" ) ) &&
        !cp.Name.Substring( 0, cp.Name.Length - 2 ).Equals( cp.ReflectedType.Name, StringComparison.OrdinalIgnoreCase ) )
    .Configure( cppc => 
    {
        var sourcePropertyType = cppc.ClrPropertyInfo.PropertyType;
        var sourceEntityType = cppc.ClrPropertyInfo.ReflectedType;
        var targetEntityName = cppc.ClrPropertyInfo.Name.Substring( 0, cppc.ClrPropertyInfo.Name.Length - 2 );
        var icollectionType = typeof( ICollection<> ).MakeGenericType( sourceEntityType );

        // possible problem of multiple classes with same name but different namespaces
        // for this example I simply select the first but this should be more robust
        // e.g. check for ID/ClassNameID property in the class or require same 
        // namespace as the property's class
        var targetEntityType = possibleEntityTypes.FirstOrDefault( t =>
            t.Name == targetEntityName &&
            // check if the type has a nav collection property of the source type
            t.GetProperties().Any( pi =>
                pi.PropertyType.IsGenericType &&
                icollectionType.IsAssignableFrom( pi.PropertyType ) ) );

        if( null != targetEntityType )
        {
            // find the nav property
            var navPropInfos = targetEntityType.GetProperties()
                .Where( pi =>
                    pi.PropertyType.IsGenericType && 
                    icollectionType.IsAssignableFrom( pi.PropertyType ) && 
                    pi.PropertyType.GetGenericArguments().First() == sourceEntityType );

            if( 1 != navPropInfos.Count() )
            {
                // more than one possible nav property, no way to tell which to use; abort
                return;
            }

            var navPropInfo = navPropInfos.First();

            // get EntityTypeConfiguration for target entity
            var etc = modelBuilder.GetType().GetMethod( "Entity" )
                .MakeGenericMethod( targetEntityType )
                .Invoke( modelBuilder, new object[] { } );

            var etcType = etc.GetType();

            var tetArg = Expression.Parameter( targetEntityType, "tet" );

            // Invoke EntityTypeConfiguration<T>.HasMany( x => x.Col )
            // returns ManyNavigationPropertyConfiguration object
            var mnpc = etcType.GetMethod( "HasMany" ).MakeGenericMethod( sourceEntityType )
                .Invoke( etc, new[] { 
                    Expression.Lambda(
                        Expression.Convert( 
                            Expression.Property( 
                                tetArg, 
                                navPropInfo ),
                            icollectionType ),
                        tetArg ) } );

            string withMethodName = ( sourcePropertyType.IsPrimitive || sourcePropertyType == typeof( Guid ) )
                ? "WithRequired"
                : "WithOptional";

            // Invoke WithRequired/WithOptional method
            // returns DependentNavigationPropertyConfiguration object
            var dnpc = mnpc.GetType().GetMethods().Single( mi =>
                mi.Name == withMethodName && !mi.GetParameters().Any() )
                .Invoke( mnpc, new object[] { } );

            var setArg = Expression.Parameter( sourceEntityType, "set" );

            // Invoke HasForiegnKey method
            var x = dnpc.GetType().GetMethod( "HasForeignKey" ).MakeGenericMethod( sourcePropertyType )
                .Invoke( dnpc, new[]{
                    Expression.Lambda(
                        Expression.Property(
                            setArg,
                            cppc.ClrPropertyInfo ),
                        setArg ) } );
        }
    });

Helper方法:

public static bool IsValidForeignKeyType( Type type )
{
    var retVal = type.IsPrimitive ||
        type == typeof( string ) ||
        type == typeof( Guid );

    if( !retVal )
    {
        if( type.IsGenericType && type.GetGenericTypeDefinition() == typeof( Nullable<> ) )
        {
            var genArgType = type.GetGenericArguments().Single();

            retVal = genArgType.IsPrimitive || genArgType == typeof( Guid );
        }
    }

    return retVal;
}


文章来源: EF Code First: Duplicate foreign keys (one from name convention, one from navigation property)