C# EF代码优先:重复外键(一个来自名称约定,一个来自导航属性)
我试图先使用EF代码创建一个SQL数据库 假设我有以下代码: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
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;
}