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, починаючи з коли клас I Inherit EntityTypeConfiguration, який не в змозі знайти клас.

Я завантажую вихідний код EF Core з GitHub, я не можу його знайти. Може хтось у цьому допоможе?


8
Чому б не прийняти цю відповідь?
Den

оскільки він у beta5 зараз, коли ми ставимо maxLength (50). в db він генерує 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. Дякую
Блейз

Також дивіться відповідь на «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());
}

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");
Biju Soman

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, щоб вони автоматично відкривали / викликали вас, якщо ви хочете пофантазувати!
Avi Cherry

1
Ласкаво просимо. Відповідь під голосування (і заохочуючи запитувача прийняти її) ще краще!
Avi Cherry

Я зазвичай роблю це :)
Den

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 у вашому прикладі до нового та додати альтернативний ключ - який би був найкращий підхід?
chris

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 });
    }
}

Яку посилання використовує DataContextта .Whereвикористовує? Я зробив для цього окремий проект і, схоже, не знаходжу посилання.
Ручан

.Whereце System.Linq, DataContextє клас , де додається код (мій EF DbContextосущ)
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 у методі Налаштування.

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.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.IsAb Abstract "не можна застосувати до операнду типу" метод методу " " на "! X.IsAb абстракт" (System.Type.IsAb абстракт) в ModelBuilderExtenions.GetMappingTypes () . Чи потрібно мені додати посилання на mscorlib? Як це зробити для .NET Core 1.0 проекту?
RandyDaddis

для базових проектів .net (за допомогою netstandard) потрібно використовувати розширення GetTypeInfo () у просторі імен System.Reflection. Використовувати як x.GetTypeInfo (). IsAb абстракт або x.GetTypeInfo (). GetInterfaces ()
animalito 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

У ядрах ef нам належить імпеленція IEntityTypeConfiguration замість EntityTypeConfiguration. У цьому випадку ми маємо повний доступ до DbContext modelBuilder, і ми можемо використовувати вільні api, але в ядрах ef цей api є літ-бітом, що відрізняється від попередніх версій. ви можете знайти більш детальну інформацію про конфігурацію основної моделі ef на

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


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.
Тохід

PS: Знайшов рішення: &&! T.IsGenericType. Тому що у мене був базовий клас, який є загальним ( class EntityTypeConfigurationBase<TEntity> : IEntityTypeConfiguration<TEntity>). Ви не можете зробити примірник цього базового класу.
Тохід

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

Ну ось питання щодо удосконалення репортажу Github EF7: https://github.com/aspnet/EntityFramework/isissue/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
    }
}

Потім у вашому класі запуску вам просто потрібно сказати 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


Будь ласка, не публікуйте однакову відповідь на кілька запитань. Якщо однакова інформація дійсно відповідає на обидва запитання, то одне питання (як правило, нове) має бути закрите як дублікат іншого. Ви можете вказати це, проголосувавши, щоб закрити його як дублікат або, якщо у вас недостатньо репутації для цього, підняти прапор, щоб вказати, що це дублікат. В іншому випадку не забудьте підготувати свою відповідь на це питання і не просто вставляйте ту саму відповідь у декілька місць.
еліксинід
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.