EF Core Mapping EntityTypeConfiguration

129

В EF6 мы обычно можем использовать этот способ настройки Entity.

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, поскольку, когда класс Inherit EntityTypeConfiguration не смог найти класс.

Я скачиваю исходный код EF Core с GitHub, но не могу его найти. Может кто-нибудь помочь в этом?

Герман
источник
8
Почему бы не принять этот ответ?
Den
так как сейчас он находится в beta5, когда мы помещаем maxLength (50). в базе данных он генерирует nvarchar (max)
Герман
6
Для всех, кто заинтересован в этом, теперь есть метод IEntityTypeConfiguration<T>с одним, void Configure()который вы можете реализовать. Подробности здесь: github.com/aspnet/EntityFramework/pull/6989
Galilyou

Ответы:

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
если у вас есть пользовательская конфигурация man, просто поместите 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());
}
devdigital
источник
1
Где ForSqlServerToTable()?
im1dermike
1
Это сейчас ToTable, см. Docs.microsoft.com/en-us/ef/core/modeling/relational/tables
devdigital,
1
Как использовать с этим HasColumnType ? , Например, entity.Property(c => c.JoinDate).HasColumnType("date");
Биджу Соман
OnModelCreatingбыл обновлен, чтобы требовать DbModelBuilder. Теперь можно добавить конфигурации к этомуmodelBuilder.Configurations.Add(new UserConfiguration());
Иззи
2
@Izzy - DbModelBuilder - это Entity Framework 6.0, ModelBuilder - это EF Core. Это разные сборки, и в данном случае вопрос касался EF Core.
Джейсон
29

В EF7 вы переопределяете OnModelCreating в классе DbContext, который вы реализуете.

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. См. Здесь в разделе «Особые типы». Внесите это в свой контекст.
Avi Cherry
22

Используется последняя, ​​бета-версия 8. Попробуйте следующее:

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:

Сначала создайте интерфейс для ваших типов сопоставления:

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 });
    }
}
Cocowalla
источник
Какую ссылку использует DataContextи .Where? Я сделал для этого отдельный проект и, похоже, не нашел ссылки.
Ruchan 01
.Whereis System.Linq, DataContextэто класс, в который добавлен код (мой EF DbContextimpl)
Cocowalla
12

Начиная с EF Core 2.2 вы можете добавлять все конфиги (классы, реализующие интерфейс IEntityTypeConfiguration) одной строкой в ​​методе OnModelCreating в классе, который наследуется от класса 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, вы можете реализовать интерфейс IEntityTypeConfiguration, настроить конфигурацию сопоставления с помощью FluentAPI в методе Configure.

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 свернет файл в проводнике решений. так что он содержится в файле класса сущности.

Джейми Гулд
источник
Спасибо за ваше решение. Я оптимизирую свой код решения в конце своего проекта ... Я обязательно проверю это в будущем.
Miroslav Siska
Я могу только предположить, что «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' не может быть применен к операнду типа 'группа методов' » на '! X.IsAbstract' (System.Type.IsAbstract) в ModelBuilderExtenions.GetMappingTypes () , Мне нужно добавить ссылку на mscorlib? Как мне сделать это с проектом .NET Core 1.0?
RandyDaddis
для основных проектов .net (с использованием netstandard) вам необходимо использовать расширение GetTypeInfo () в пространстве имен System.Reflection. Использовать как x.GetTypeInfo (). IsAbstract или x.GetTypeInfo (). GetInterfaces ()
animalito maquina
Я использовал часть вашего решения на своем, и оно отлично сработало. Спасибо!
Diego Cotini
2

Просто реализуйте IEntityTypeConfiguration

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

а затем добавьте его в свой объект Context

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

В ядре ef мы должны реализовать IEntityTypeConfiguration вместо EntityTypeConfiguration, в этом случае у нас есть полный доступ к DbContext modelBuilder, и мы можем использовать свободный API, но в ядре EF этот API немного отличается от предыдущих версий. вы можете найти более подробную информацию о конфигурации базовой модели ef на

https://www.learnentityframeworkcore.com/configuration/fluent-api

Mohammadali Ghanbari
источник
1

В Entity Framework Core 2.0:

Я взял ответ Cocowalla и адаптировал его для версии 2.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.
Тохид
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 (), но принятый ответ не требует никаких вспомогательных классов. Есть ли что-то, что мне не хватает, что можно улучшить в вашем ответе?
Парусное дзюдо
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

Вот проблема для улучшения репозитория EF7 Github: https://github.com/aspnet/EntityFramework/issues/2805

Вы можете отслеживать проблему прямо там, хотя она все еще находится в очереди без назначенного приоритета.

Денис Биондик
источник
Должна появиться проблема типа «Прекратите ломать хорошо продуманные вещи».
Камил
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