C# EF代码优先:重复外键(一个来自名称约定,一个来自导航属性)

C# EF代码优先:重复外键(一个来自名称约定,一个来自导航属性),c#,entity-framework,ef-code-first,C#,Entity Framework,Ef Code First,我试图先使用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<Ac

我试图先使用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;
}

为什么EF没有意识到“AccountId”指的是Account的“Id”主键(按惯例)?如果可能的话,我希望避免使用Fluent API/DA手动映射,并且我希望避免在用户上使用帐户导航属性。

我只知道两种方法来执行您希望执行的操作,即数据注释(非常快速)或Fluent映射。你不能只说
public intaccountid并期望一切正常

流畅的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; }       
}
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; }       
}

注意:未经测试,但逻辑合理

您可以编写自己的自定义约定,但它会变得非常复杂(例如,如果
Account
有两个
User
s的集合资产净值属性怎么办?EF如何知道单个
用户引用了哪个集合资产净值属性。AccountId
属性?有许多可能的警告/陷阱,可能无法全部解释

下面的示例适用于您描述的场景,但如果您的模型变得更复杂,则可能会出现故障。您应该将可能的实体类型筛选为您希望在模型中出现的实体类型(下面的示例检查应用程序域中的所有类型)。我强烈建议您只需在下面使用fluent 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 ) } );
        }
    });
//我建议对其进行筛选
var-possibleentitypes=AppDomain.CurrentDomain.GetAssemblys()
.SelectMany(ass=>ass.GetTypes());
modelBuilder.Properties()
.其中(cp=>
IsValidForeignKeyType(cp.PropertyType)&&
cp.Name.Length>2&&
(cp.Name.EndsWith(“ID”)| cp.Name.EndsWith(“ID”))&&
!cp.Name.Substring(0,cp.Name.Length-2).等于(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);
//多个名称相同但名称空间不同的类可能存在的问题
//对于这个例子,我只选择第一个,但它应该更健壮
//例如,检查类中的ID/ClassNameID属性或要求相同的属性
//命名空间作为属性的类
var targetEntityType=possibleEntityTypes.FirstOrDefault(t=>
t、 Name==targetEntityName&&
//检查该类型是否具有源类型的nav集合属性
t、 GetProperties().Any(pi=>
pi.PropertyType.IsGenericType&&
icollectionType.IsAssignableFrom(pi.PropertyType));
if(null!=targetEntityType)
{
//查找导航属性
var navPropInfo=targetEntityType.GetProperties()
.其中(pi=>
pi.PropertyType.IsGenericType&&
icollectionType.IsAssignableFrom(pi.PropertyType)和
pi.PropertyType.GetGenericArguments().First()==sourceEntityType);
如果(1!=navPropInfo.Count())
{
//多个可能的导航属性,无法判断要使用哪个;中止
返回;
}
var navprinfo=navprinfos.First();
//获取目标实体的EntityTypeConfiguration
var etc=modelBuilder.GetType().GetMethod(“实体”)
.MakeGenericMethod(targetEntityType)
.Invoke(modelBuilder,新对象[]{});
var etcType=etc.GetType();
var tetArg=Expression.Parameter(targetEntityType,“tet”);
//调用EntityTypeConfiguration.HasMany(x=>x.Col)
//返回ManyNavigationPropertyConfiguration对象
var mnpc=etcType.GetMethod(“HasMany”).MakeGenericMethod(sourceEntityType)
.Invoke(等,新[]{
Lambda(
表达式.转换(
表达式.属性(
特塔格,
navPropInfo),
icollectionType),
tetArg)});
字符串withMethodName=(sourcePropertyType.IsPrimitive | | sourcePropertyType==typeof(Guid))
?“带必填项”
:“可选”;
//调用WithRequired/WithOptional方法
//返回DependentNavigationPropertyConfiguration对象
var dnpc=mnpc.GetType().GetMethods().Single(mi=>
mi.Name==withMethodName&!mi.GetParameters().Any())
.Invoke(mnpc,新对象[]{});
var setArg=Expression.Parameter(sourceEntityType,“set”);
//调用hasforiegkey方法
var x=dnpc.GetType().GetMethod(“HasForeignKey”).MakeGenericMethod(sourcePropertyType)
.Invoke(dnpc,新[]{
Lambda(
表达式.属性(
塞塔格,
cppc.ClrPropertyInfo),
setArg)});
}
});
助手方法:

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;
}
公共静态bool IsValidForeignKeyType(类型)
{
var retVal=type.IsPrimitive||
type==typeof(字符串)||
type==typeof(Guid);
如果(!retVal)
{
if(type.IsGenericType&&type.GetGenericTypeDefinition()==typeof(可为空))
{
// 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 ) } );
        }
    });
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;
}