EntityTypeConfiguration mapowania EF Core


129

W EF6 zwykle możemy użyć tego sposobu do skonfigurowania 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);
    }
}

Jak możemy to zrobić w EF Core, od kiedy klasa I Inherit EntityTypeConfiguration, która nie może znaleźć klasy.

Pobieram surowy kod źródłowy EF Core z GitHub, nie mogę go znaleźć. Czy ktoś może w tym pomóc?


8
Dlaczego nie przyjąć tej odpowiedzi?
Den

ponieważ teraz w wersji beta5, kiedy umieścimy maxLength (50). w db generuje nvarchar (max)
Herman

6
Dla każdego, kto jest tym zainteresowany, istnieje teraz IEntityTypeConfiguration<T>jedna void Configure()metoda, którą można wdrożyć. Szczegóły tutaj: github.com/aspnet/EntityFramework/pull/6989
Galilyou,

Odpowiedzi:


183

Ponieważ EF Core 2,0 istnieje IEntityTypeConfiguration<TEntity>. Możesz go używać w ten sposób:

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

Więcej informacji na temat tej i innych nowych funkcji wprowadzonych w wersji 2.0 można znaleźć tutaj .


8
To najlepsza odpowiedź dla EF Core 2.0. Dzięki!
Collin M. Barrett

2
To jest wspaniałe. Szukałem sposobu na oddzielenie płynnych definicji API. Dzięki
Blaze

Zobacz także tę odpowiedź dla „ToTable” i „HasColumnName” itp. ::: stackoverflow.com/questions/43200184/…
granadaCoder

jeśli masz niestandardową konfigurację człowieka, po prostu umieść builder.ApplyConfigurationsFromAssembly(typeof(ApplicationDbContext).Assembly);ją, zastosuje wszystkie niestandardowe potwierdzenia
alim91

52

Możesz to osiągnąć za pomocą kilku prostych dodatkowych typów:

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

Stosowanie:

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
Gdzie jest ForSqlServerToTable()?
im1dermike


1
Jak używać HasColumnType z tym? . Np. entity.Property(c => c.JoinDate).HasColumnType("date");
Biju Soman

OnModelCreatingzostał zaktualizowany, aby wymagać DbModelBuilder. Sposób na dodanie do tego konfiguracji jest terazmodelBuilder.Configurations.Add(new UserConfiguration());
Izzy

2
@Izzy - DbModelBuilder to Entity Framework 6.0, ModelBuilder to EF Core. Są to różne zestawy iw tym przypadku pytanie było specyficzne dla EF Core.
Jason,

29

W EF7 przesłaniasz OnModelCreating w implementowanej klasie 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
Więc jeśli mam 20 konfiguracji typu encji, umieszczam je w jednej ogromnej metodzie?
Den

6
Domyślnie tak się wydaje. Możesz stworzyć własne klasy FooMapper / FooModelBuilder, które rozszerzają klasę bazową i mają metodę przekazującą EntityBuilder <Foo>. Możesz nawet użyć nowego interfejsu iniekcji zależności i interfejsu IConfiguration, aby je automatycznie wykryć / wywołać, jeśli chcesz być fantazyjny!
Avi Cherry

1
Nie ma za co. Głosowanie za odpowiedzią (i zachęcanie pytającego do jej zaakceptowania) jest jeszcze lepsze!
Avi Cherry

Zwykle to robię :)
Den

4
Wypróbuj nowe narzędzia do iniekcji zależności? Utwórz IEntityMapperStrategyinterfejs z void MapEntity(ModelBuilder, Type)podpisem i bool IsFor(Type). Zaimplementuj interfejs tyle razy, ile chcesz (aby można było tworzyć klasy, które mogą mapować więcej niż jedną jednostkę, jeśli chcesz), a następnie utwórz kolejną klasę (dostawcę strategii), która wstrzykuje IEnumerablewszystkie klasy IEntityMapperStrategies. Zobacz tutaj w sekcji „Typy specjalne”. Wstrzyknij to w swoim kontekście.
Avi Cherry

22

Używana jest najnowsza wersja beta 8. Spróbuj tego:

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

Następnie w swoim DbContext:

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

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

3
Skończyło się na tym, że zrobiłem podobnie. Postanowiłem jednak użyć metody statycznej zamiast konstruktora.
Matt Sanders

Używam tej metodologii i do tej pory nie miałem żadnych problemów z wyjątkiem dziedziczenia. Jeśli chcę odziedziczyć AccountMap z twojego przykładu do nowego i dodać alternatywny klucz - jakie byłoby najlepsze podejście?
chris

14

Za pomocą odbicia można wykonywać czynności bardzo podobne do ich działania w EF6, z osobną klasą mapowania dla każdej jednostki. Działa to w finale RC1:

Najpierw utwórz interfejs dla swoich typów mapowania:

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

Następnie utwórz klasę mapowania dla każdej z twoich encji, np. Dla Personklasy:

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

Teraz magia odbicia OnModelCreatingw twojej DbContextimplementacji:

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

Jakiego odniesienia używa DataContexti .Whereużywa? Zrobiłem dla tego osobny projekt i nie wydaje mi się, aby znaleźć odniesienie.
Ruchan

.Whereis System.Linq, DataContextto klasa, w której kod jest dodawany (my EF DbContextimpl)
Cocowalla

12

Od EF Core 2.2 można dodać wszystkie konfiguracje (klasy, które zaimplementowały interfejs IEntityTypeConfiguration) w jednym wierszu w metodzie OnModelCreating w klasie, która jest dziedziczona z klasy DbContext

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

I, jak wspomniano w poprzedniej odpowiedzi, od EF Core 2.0 można zaimplementować interfejs IEntityTypeConfiguration, skonfigurować konfigurację mapowania za pomocą FluentAPI w metodzie 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

To właśnie robię w projekcie, nad którym obecnie pracuję.

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

Stosowanie:

W metodzie OnModelCreating Twojego Context:

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

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

Przykładowa klasa mapowania:

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

Inną rzeczą, którą lubię robić, aby skorzystać z zachowania zwijania programu Visual Studio 2015, jest dla jednostki o nazwie „Użytkownik”, nazywasz plik mapowania „User.Mapping.cs”, a program Visual Studio zwinie plik w eksploratorze rozwiązań tak, że jest zawarty w pliku klasy jednostki.


Dziękuję za rozwiązanie. Zoptymalizuję kod rozwiązania na końcu projektu ... na pewno sprawdzę to w przyszłości.
Miroslav Siska

Mogę tylko założyć, że „IEntityTypeConfiguration <T>” Configure(builder)nie istniał w 2016 roku? Dzięki niewielkiej zmianie okablowania, aby wskazywało na TypeConfiguration, nie ma potrzeby stosowania „dodatkowego” interfejsu.
WernerCD

3

Skończyłem na tym rozwiązaniu:

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

Przykładowe zastosowanie:

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

i

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

Otrzymuję błąd podczas kompilacji: „ Operator”! X.IsAbstract ”nie może być zastosowany do operandu typu„ grupa metod ” „ on ”! X.IsAbstract” (System.Type.IsAbstract) w ModelBuilderExtenions.GetMappingTypes () . Czy muszę dodać odwołanie do mscorlib? Jak to zrobić z projektem .NET Core 1.0?
RandyDaddis

dla projektów .net core (używając netstandard) musisz użyć rozszerzenia GetTypeInfo () w przestrzeni nazw System.Reflection. Użyj jako x.GetTypeInfo (). IsAbstract lub x.GetTypeInfo (). GetInterfaces ()
animalito maquina

Użyłem części twojego rozwiązania na moim i działa dobrze. Dzięki!
Diego Cotini

2

Po prostu zaimplementuj IEntityTypeConfiguration

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

a następnie dodaj go do kontekstu encji

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

W Entity Framework Core 2.0:

Wziąłem odpowiedź Cocowalli i dostosowałem ją do wersji 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 });
            }
        }


    }

I jest używany w DbContext w następujący sposób:

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

W ten sposób tworzysz konfigurację typu jednostki dla jednostki:

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

Nie działa dla mnie. Wyjątek:Late bound operations cannot be performed on types or methods for which ContainsGenericParameters is true.
Tohid

PS: Znaleziono rozwiązanie: &&! T.IsGenericType. Ponieważ miałem klasę bazową, która jest ogólna ( class EntityTypeConfigurationBase<TEntity> : IEntityTypeConfiguration<TEntity>). Nie możesz utworzyć instancji tej klasy bazowej.
Tohid

0

Czy mam rację?

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

Mogę przekazać konfigurację:

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

    } 

Przyjęta odpowiedź wydaje się lepsza niż ta. Oba mają ten sam negatywny efekt uboczny w postaci masowo zaśmieconej OnModelCreating (), ale zaakceptowana odpowiedź nie wymaga żadnych klas pomocniczych. Czy jest coś, czego brakuje mi, a twoja odpowiedź się poprawia?
Sailing Judo

0

Podążyłem podobnie do sposobu, w jaki Microsoft zaimplementował ForSqlServerToTable

przy użyciu metody rozszerzenia ...

częściowy flaga jest wymagana, jeśli chcesz używać tej samej nazwy klasy w wielu plikach

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

Następnie w DataContext OnModelCreating wykonaj wywołanie dla każdego rozszerzenia ...

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

    }

W ten sposób postępujemy według tego samego wzorca używanego przez inne metody konstruktora.

O co ci chodzi?



0

Mam projekt, który umożliwia konfigurowanie jednostek poza DbContext.OnModelCreating konfiguracją każdej jednostki w osobnej klasie, która dziedziczy poStaticDotNet.EntityFrameworkCore.ModelConfiguration.EntityTypeConfiguration

Najpierw musisz utworzyć klasę, która dziedziczy po tym, StaticDotNet.EntityFrameworkCore.ModelConfiguration.EntityTypeConfiguration<TEntity>skąd TEntityjest klasa, którą chcesz skonfigurować.

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

Następnie w klasie Startup wystarczy wskazać Entity Framework, gdzie znaleźć wszystkie klasy konfiguracji podczas konfigurowania 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 )
    );
}

Istnieje również możliwość dodania konfiguracji typu za pomocą dostawcy. Repozytorium ma pełną dokumentację dotyczącą korzystania z niego.

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


Nie publikuj tej samej odpowiedzi na wiele pytań. Jeśli te same informacje rzeczywiście odpowiadają na oba pytania, to jedno pytanie (zwykle nowsze) powinno zostać zamknięte jako duplikat drugiego. Możesz to wskazać, głosując za zamknięciem go jako duplikatu lub, jeśli nie masz wystarczającej reputacji, podnieś flagę, aby wskazać, że jest to duplikat. W przeciwnym razie pamiętaj, aby dopasować swoją odpowiedź na to pytanie i nie wklejać tej samej odpowiedzi w wielu miejscach.
elixenide
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.