EF核心映射EntityTypeConfiguration


129

在EF6中,我们通常可以使用这种方式来配置实体。

public class AccountMap : EntityTypeConfiguration<Account>
{
    public AccountMap()
    {
        ToTable("Account");
        HasKey(a => a.Id);

        Property(a => a.Username).HasMaxLength(50);
        Property(a => a.Email).HasMaxLength(255);
        Property(a => a.Name).HasMaxLength(255);
    }
}

我们如何在EF Core中做,因为当我继承该类时,EntityTypeConfiguration找不到该类。

我从GitHub下载EF Core原始源代码,但找不到。有人可以帮忙吗?


8
为什么不接受这个答案?
书斋

自从它现在在beta5中以来,当我们放置maxLength(50)时。在数据库中它生成nvarchar(max)
Herman 2015年

6
对于对此感兴趣的其他人,现在IEntityTypeConfiguration<T>有一种void Configure()您可以实现的方法。此处的详细信息:github.com/aspnet/EntityFramework/pull/6989
Galilyou

Answers:


183

从EF Core 2.0开始IEntityTypeConfiguration<TEntity>。您可以像这样使用它:

class CustomerConfiguration : IEntityTypeConfiguration<Customer>
{
  public void Configure(EntityTypeBuilder<Customer> builder)
  {
     builder.HasKey(c => c.AlternateKey);
     builder.Property(c => c.Name).HasMaxLength(200);
   }
}

...
// OnModelCreating
builder.ApplyConfiguration(new CustomerConfiguration());

有关此功能以及2.0中引入的其他新功能的更多信息,请参见此处


8
这是EF Core 2.0的最佳答案。谢谢!
科林·巴雷特

2
太好了 我一直在寻找分离流畅的API定义的方法。谢谢
Blaze

也看到这个答案“ToTable”和“HasColumnName”等::: stackoverflow.com/questions/43200184/...
granadaCoder

如果您具有人工自定义配置,则只需将builder.ApplyConfigurationsFromAssembly(typeof(ApplicationDbContext).Assembly);其应用所有自定义配置 即可
alim91

52

您可以通过一些简单的其他类型来实现此目的:

internal static class ModelBuilderExtensions
{
   public static void AddConfiguration<TEntity>(
     this ModelBuilder modelBuilder, 
     DbEntityConfiguration<TEntity> entityConfiguration) where TEntity : class
   {     
       modelBuilder.Entity<TEntity>(entityConfiguration.Configure);
   }
}

internal abstract class DbEntityConfiguration<TEntity> where TEntity : class
{     
    public abstract void Configure(EntityTypeBuilder<TEntity> entity);
}

用法:

internal class UserConfiguration : DbEntityConfiguration<UserDto>
{
    public override void Configure(EntityTypeBuilder<UserDto> entity)
    {
        entity.ToTable("User");
        entity.HasKey(c => c.Id);
        entity.Property(c => c.Username).HasMaxLength(255).IsRequired();
        // etc.
    }
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);

    modelBuilder.AddConfiguration(new UserConfiguration());
}

1
哪里ForSqlServerToTable()
im1dermike '17


1
如何与此使用HasColumnType?。例如。entity.Property(c => c.JoinDate).HasColumnType("date");
Biju Soman

OnModelCreating已更新,要求输入DbModelBuilder。现在向其中添加配置的方法是modelBuilder.Configurations.Add(new UserConfiguration());
Izzy

2
@Izzy-DbModelBuilder是Entity Framework 6.0,ModelBuilder是EF Core。它们是不同的程序集,在这种情况下,问题是针对EF Core的。
杰森

29

在EF7中,您可以在要实现的DbContext类上重写OnModelCreating。

protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        modelBuilder.Entity<Account>()
            .ForRelational(builder => builder.Table("Account"))
            .Property(value => value.Username).MaxLength(50)
            .Property(value => value.Email).MaxLength(255)
            .Property(value => value.Name).MaxLength(255);
    }

23
因此,如果我有20种实体类型配置,可以将它们放在一种巨大的方法中吗?

6
默认情况下,看起来是这样。您可以创建自己的FooMapper / FooModelBuilder类,该类扩展了基类并具有传递类型化EntityBuilder <Foo>的方法。如果您愿意,甚至可以使用新的依赖项注入和IConfiguration接口来自动发现/调用它们!
阿维·切里

1
别客气。投票(并鼓励提问者接受)会更好!
阿维·切里

我通常这样做:)

4
尝试新的依赖项注入工具?IEntityMapperStrategy使用void MapEntity(ModelBuilder, Type)签名和创建接口bool IsFor(Type)。根据需要实施任意多次的接口(以便您可以创建可以映射多个实体的类),然后使另一个类(策略提供者)注入IEnumerable所有IEntityMapperStrategies。见这里下的“特殊类型”。将其注入您的上下文中。
阿维·切里

22

这使用的是最新的Beta8。请尝试以下操作:

public class AccountMap
{
    public AccountMap(EntityTypeBuilder<Account> entityBuilder)
    {
        entityBuilder.HasKey(x => x.AccountId);

        entityBuilder.Property(x => x.AccountId).IsRequired();
        entityBuilder.Property(x => x.Username).IsRequired().HasMaxLength(50);
    }
}

然后在您的DbContext中:

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        new AccountMap(modelBuilder.Entity<Account>());
    }

3
我最终做了类似的事情。我决定使用静态方法而不是构造函数。
马特·桑德斯

我正在使用这种方法,到目前为止,除继承外,我都没有任何问题。如果我想在您的示例中将AccountMap继承到一个新的帐户中并添加一个备用密钥-最好的方法是什么?
克里斯

14

您可以使用反射来执行与EF6中的工作非常相似的操作,并为每个实体使用单独的映射类。这在RC1 final中有效:

首先,为您的映射类型创建一个接口:

public interface IEntityTypeConfiguration<TEntityType> where TEntityType : class
{
    void Map(EntityTypeBuilder<TEntityType> builder);
}

然后为每个实体创建一个映射类,例如一个Person类:

public class PersonMap : IEntityTypeConfiguration<Person>
{
    public void Map(EntityTypeBuilder<Person> builder)
    {
        builder.HasKey(x => x.Id);
        builder.Property(x => x.Name).IsRequired().HasMaxLength(100);
    }
}

现在,反射魔法OnModelCreating在你DbContext实现:

protected override void OnModelCreating(ModelBuilder builder)
{
    base.OnModelCreating(builder);

    // Interface that all of our Entity maps implement
    var mappingInterface = typeof(IEntityTypeConfiguration<>);

    // Types that do entity mapping
    var mappingTypes = typeof(DataContext).GetTypeInfo().Assembly.GetTypes()
        .Where(x => x.GetInterfaces().Any(y => y.GetTypeInfo().IsGenericType && y.GetGenericTypeDefinition() == mappingInterface));

    // Get the generic Entity method of the ModelBuilder type
    var entityMethod = typeof(ModelBuilder).GetMethods()
        .Single(x => x.Name == "Entity" && 
                x.IsGenericMethod && 
                x.ReturnType.Name == "EntityTypeBuilder`1");

    foreach (var mappingType in mappingTypes)
    {
        // Get the type of entity to be mapped
        var genericTypeArg = mappingType.GetInterfaces().Single().GenericTypeArguments.Single();

        // Get the method builder.Entity<TEntity>
        var genericEntityMethod = entityMethod.MakeGenericMethod(genericTypeArg);

        // Invoke builder.Entity<TEntity> to get a builder for the entity to be mapped
        var entityBuilder = genericEntityMethod.Invoke(builder, null);

        // Create the mapping type and do the mapping
        var mapper = Activator.CreateInstance(mappingType);
        mapper.GetType().GetMethod("Map").Invoke(mapper, new[] { entityBuilder });
    }
}

DataContext.Where使用什么参考?我为此做了一个单独的项目,但似乎找不到参考。
鲁坎

.WhereSystem.LinqDataContext是添加代码的类(我的EF展示DbContext
Cocowalla19年

12

从EF Core 2.2开始,您可以在类的OnModelCreating方法中一行添加所有配置(类,实现IEntityTypeConfiguration接口的类),该方法继承自DbContext类

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    //this will apply configs from separate classes which implemented IEntityTypeConfiguration<T>
    modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());
}

并且,如前一个答案中所述,从EF Core 2.0开始,您可以在Configure方法中使用FluentAPI来实现接口IEntityTypeConfiguration和设置映射配置。

public class QuestionAnswerConfig : IEntityTypeConfiguration<QuestionAnswer>
{
    public void Configure(EntityTypeBuilder<QuestionAnswer> builder)
    {
      builder
        .HasKey(bc => new { bc.QuestionId, bc.AnswerId });
      builder
        .HasOne(bc => bc.Question)
        .WithMany(b => b.QuestionAnswers)
        .HasForeignKey(bc => bc.QuestionId);
      builder
        .HasOne(bc => bc.Answer)
        .WithMany(c => c.QuestionAnswers)
        .HasForeignKey(bc => bc.AnswerId);
    }
}

6

这就是我目前正在从事的项目中正在做的事情。

public interface IEntityMappingConfiguration<T> where T : class
{
    void Map(EntityTypeBuilder<T> builder);
}

public static class EntityMappingExtensions
{
     public static ModelBuilder RegisterEntityMapping<TEntity, TMapping>(this ModelBuilder builder) 
        where TMapping : IEntityMappingConfiguration<TEntity> 
        where TEntity : class
    {
        var mapper = (IEntityMappingConfiguration<TEntity>)Activator.CreateInstance(typeof (TMapping));
        mapper.Map(builder.Entity<TEntity>());
        return builder;
    }
}

用法:

在上下文的OnModelCreating方法中:

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);

        builder
            .RegisterEntityMapping<Card, CardMapping>()
            .RegisterEntityMapping<User, UserMapping>();
    }

映射类示例:

public class UserMapping : IEntityMappingConfiguration<User>
{
    public void Map(EntityTypeBuilder<User> builder)
    {
        builder.ToTable("User");
        builder.HasKey(m => m.Id);
        builder.Property(m => m.Id).HasColumnName("UserId");
        builder.Property(m => m.FirstName).IsRequired().HasMaxLength(64);
        builder.Property(m => m.LastName).IsRequired().HasMaxLength(64);
        builder.Property(m => m.DateOfBirth);
        builder.Property(m => m.MobileNumber).IsRequired(false);
    }
}

为了利用Visual Studio 2015的折叠行为,我想做的另一件事是对于名为“ User”的实体,将您的映射文件命名为“ User.Mapping.cs”,Visual Studio会在解决方案资源管理器中折叠该文件使其包含在实体类文件下。


谢谢您的解决方案。我将在项目结束时优化解决方案代码...以后会检查。
米罗斯拉夫·西斯卡

我只能假设'IEntityTypeConfiguration <T>'并且Configure(builder)在2016年不存在吗?只需稍微改变一下指向TypeConfiguration的接线,就无需使用“额外”接口。
WernerCD

3

我以这个解决方案结束了:

public interface IEntityMappingConfiguration
{
    void Map(ModelBuilder b);
}

public interface IEntityMappingConfiguration<T> : IEntityMappingConfiguration where T : class
{
    void Map(EntityTypeBuilder<T> builder);
}

public abstract class EntityMappingConfiguration<T> : IEntityMappingConfiguration<T> where T : class
{
    public abstract void Map(EntityTypeBuilder<T> b);

    public void Map(ModelBuilder b)
    {
        Map(b.Entity<T>());
    }
}

public static class ModelBuilderExtenions
{
    private static IEnumerable<Type> GetMappingTypes(this Assembly assembly, Type mappingInterface)
    {
        return assembly.GetTypes().Where(x => !x.IsAbstract && x.GetInterfaces().Any(y => y.GetTypeInfo().IsGenericType && y.GetGenericTypeDefinition() == mappingInterface));
    }

    public static void AddEntityConfigurationsFromAssembly(this ModelBuilder modelBuilder, Assembly assembly)
    {
        var mappingTypes = assembly.GetMappingTypes(typeof (IEntityMappingConfiguration<>));
        foreach (var config in mappingTypes.Select(Activator.CreateInstance).Cast<IEntityMappingConfiguration>())
        {
            config.Map(modelBuilder);
        }
    }
}

样品使用:

public abstract class PersonConfiguration : EntityMappingConfiguration<Person>
{
    public override void Map(EntityTypeBuilder<Person> b)
    {
        b.ToTable("Person", "HumanResources")
            .HasKey(p => p.PersonID);

        b.Property(p => p.FirstName).HasMaxLength(50).IsRequired();
        b.Property(p => p.MiddleName).HasMaxLength(50);
        b.Property(p => p.LastName).HasMaxLength(50).IsRequired();
    }
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.AddEntityConfigurationsFromAssembly(GetType().Assembly);
}

我收到一个编译时错误:“ 无法将运算符'!x.IsAbstract'应用于 ModelBuilderExtenions.GetMappingTypes()中'!x.IsAbstract'(System.Type.IsAbstract)上类型为'method group'的操作数上。我是否需要添加对mscorlib的引用?如何对.NET Core 1.0项目执行此操作?
RandyDaddis

对于.net核心项目(使用netstandard),您需要在System.Reflection名称空间中使用扩展名GetTypeInfo()。用作x.GetTypeInfo()。IsAbstract或x.GetTypeInfo()。GetInterfaces()
maquina

我已经在您的解决方案中使用了一部分,效果很好。谢谢!
迭戈·科蒂尼

2

只需实现IEntityTypeConfiguration

public abstract class EntityTypeConfiguration<TEntity> : IEntityTypeConfiguration<TEntity> where TEntity : class
{
    public abstract void Configure(EntityTypeBuilder<TEntity> builder);
}

然后将其添加到您的实体中

public class ProductContext : DbContext, IDbContext
{
    public ProductContext(DbContextOptions<ProductContext> options)
        : base((DbContextOptions)options)
    {
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        modelBuilder.ApplyConfiguration(new ProductMap());
    }

    public DbSet<Entities.Product> Products { get; set; }
}


1

在Entity Framework Core 2.0中:

我接受了Cocowalla的回答,并将其改编为v2.0:

    public static class ModelBuilderExtenions
    {
        private static IEnumerable<Type> GetMappingTypes(this Assembly assembly, Type mappingInterface)
        {
            return assembly.GetTypes().Where(x => !x.IsAbstract && x.GetInterfaces().Any(y => y.GetTypeInfo().IsGenericType && y.GetGenericTypeDefinition() == mappingInterface));
        }

        public static void AddEntityConfigurationsFromAssembly(this ModelBuilder modelBuilder, Assembly assembly)
        {
            // Types that do entity mapping
            var mappingTypes = assembly.GetMappingTypes(typeof(IEntityTypeConfiguration<>));

            // Get the generic Entity method of the ModelBuilder type
            var entityMethod = typeof(ModelBuilder).GetMethods()
                .Single(x => x.Name == "Entity" &&
                        x.IsGenericMethod &&
                        x.ReturnType.Name == "EntityTypeBuilder`1");

            foreach (var mappingType in mappingTypes)
            {
                // Get the type of entity to be mapped
                var genericTypeArg = mappingType.GetInterfaces().Single().GenericTypeArguments.Single();

                // Get the method builder.Entity<TEntity>
                var genericEntityMethod = entityMethod.MakeGenericMethod(genericTypeArg);

                // Invoke builder.Entity<TEntity> to get a builder for the entity to be mapped
                var entityBuilder = genericEntityMethod.Invoke(modelBuilder, null);

                // Create the mapping type and do the mapping
                var mapper = Activator.CreateInstance(mappingType);
                mapper.GetType().GetMethod("Configure").Invoke(mapper, new[] { entityBuilder });
            }
        }


    }

它在DbContext中的用法如下:

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.AddEntityConfigurationsFromAssembly(GetType().Assembly);
    }

这是您为实体创建实体类型配置的方式:

    public class UserUserRoleEntityTypeConfiguration : IEntityTypeConfiguration<UserUserRole>
    {
        public void Configure(EntityTypeBuilder<UserUserRole> builder)
        {
            builder.ToTable("UserUserRole");
            // compound PK
            builder.HasKey(p => new { p.UserId, p.UserRoleId });
        }
    }

没为我工作。例外:Late bound operations cannot be performed on types or methods for which ContainsGenericParameters is true.
Tohid

PS:找到了解决方案:&&!t.IsGenericType。因为我有一个通用的基类(class EntityTypeConfigurationBase<TEntity> : IEntityTypeConfiguration<TEntity>)。您不能创建此基类的实例。
Tohid

0

我对吗?

public class SmartModelBuilder<T> where T : class         {

    private ModelBuilder _builder { get; set; }
    private Action<EntityTypeBuilder<T>> _entityAction { get; set; }

    public SmartModelBuilder(ModelBuilder builder, Action<EntityTypeBuilder<T>> entityAction)
    {
        this._builder = builder;
        this._entityAction = entityAction;

        this._builder.Entity<T>(_entityAction);
    }
}   

我可以通过配置:

 protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);
        // Customize the ASP.NET Identity model and override the defaults if needed.
        // For example, you can rename the ASP.NET Identity table names and more.
        // Add your customizations after calling base.OnModelCreating(builder);



        new SmartModelBuilder<Blog>(builder, entity => entity.Property(b => b.Url).Required());

    } 

公认的答案似乎比这更好。两者都具有与混乱的OnModelCreating()相同的负面影响,但可接受的答案不需要任何辅助类。我是否缺少您的答案可以改善的东西?
2015年

0

我采用了与Microsoft实现ForSqlServerToTable相似的方法

使用扩展方法...

部分,如果你想使用在多个文件中相同的类名标志是必需的

public class ConsignorUser
{
    public int ConsignorId { get; set; }

    public string UserId { get; set; }

    public virtual Consignor Consignor { get; set; }
    public virtual User User { get; set; }

}

public static partial class Entity_FluentMappings
{
    public static EntityTypeBuilder<ConsignorUser> AddFluentMapping<TEntity> (
        this EntityTypeBuilder<ConsignorUser> entityTypeBuilder) 
        where TEntity : ConsignorUser
    {
       entityTypeBuilder.HasKey(x => new { x.ConsignorId, x.UserId });
       return entityTypeBuilder;
    }      
}

然后在DataContext OnModelCreating中为每个扩展调用...

 public class DataContext : IdentityDbContext<User>
{

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);
        // Customize the ASP.NET Identity model and override the defaults if needed.
        // For example, you can rename the ASP.NET Identity table names and more.
        // Add your customizations after calling base.OnModelCreating(builder);

        builder.Entity<ConsignorUser>().AddFluentMapping<ConsignorUser>();
        builder.Entity<DealerUser>().AddFluentMapping<DealerUser>();           

    }

这样,我们将遵循其他构建器方法所使用相同模式。

你什么事



0

我有一个项目,可让您在之外配置实体。DbContext.OnModelCreating您可以在继承自的单独类中配置每个实体StaticDotNet.EntityFrameworkCore.ModelConfiguration.EntityTypeConfiguration

首先,你需要创建一个类从继承StaticDotNet.EntityFrameworkCore.ModelConfiguration.EntityTypeConfiguration<TEntity>哪里TEntity是要配置的类。

using StaticDotNet.EntityFrameworkCore.ModelConfiguration;
using Microsoft.EntityFrameworkCore.Metadata.Builders;

public class ExampleEntityConfiguration
    : EntityTypeConfiguration<ExampleEntity>
{
    public override void Configure( EntityTypeBuilder<ExampleEntity> builder )
    {
        //Add configuration just like you do in DbContext.OnModelCreating
    }
}

然后,在您的Startup类中,您只需要告诉Entity Framework在配置DbContext时在哪里可以找到所有配置类。

using StaticDotNet.EntityFrameworkCore.ModelConfiguration;

public void ConfigureServices(IServiceCollection services)
{
    Assembly[] assemblies = new Assembly[]
    {
        // Add your assembiles here.
    };

    services.AddDbContext<ExampleDbContext>( x => x
        .AddEntityTypeConfigurations( assemblies )
    );
}

还有一个使用提供程序添加类型配置的选项。回购包含有关如何使用它的完整文档。

https://github.com/john-t-white/StaticDotNet.EntityFrameworkCore.ModelConfiguration


请不要对多个问题发布相同的答案。如果相同的信息确实回答了两个问题,则应关闭一个问题(通常是较新的问题),作为另一个问题的重复。您可以通过投票将其关闭作为重复项来表明这一点,或者,如果您没有足够的声誉,可以举一个标志来表明它是重复项。否则,请确保您量身定制了问题的答案,而不仅仅是将同一答案粘贴到多个位置。
elixenide '16
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.