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

クラスが見つからないEntityTypeConfigurationをクラスが継承したときから、EF Coreでどのようにできるか。

EF Coreの未加工のソースコードをGitHubからダウンロードしましたが、見つかりません。誰かがこれを手伝ってくれる?


8
その答えを受け入れませんか?
Den

それは今ベータ5で、maxLength(50)を置いたときからです。dbでnvarchar(max)を生成します
Herman

6
これに関心のあるIEntityTypeConfiguration<T>人のvoid Configure()ために、実装可能なwith one メソッドがあります。詳細はこちら: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の最良の答えです。ありがとう!
Collin M. Barrett

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

1
どこForSqlServerToTable()ですか
im1dermike 2017年

1
これは現在ToTabledocs.microsoft.com/en
us/

1
これでHasColumnTypeを使用するにはどうすればよいですか?。たとえば entity.Property(c => c.JoinDate).HasColumnType("date");
Biju Soman 2017年

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のエンティティタイプの構成がある場合、それらを1つの巨大なメソッドに配置しますか?
Den

6
デフォルトではそうです。基本クラスを拡張し、型指定されたEntityBuilder <Foo>を渡すメソッドを持つ独自のFooMapper / FooModelBuilderクラスを作成できます。豪華になりたい場合は、新しい依存関係注入と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
私はこれと同じようにしてしまいました。ただし、コンストラクターの代わりに静的メソッドを使用することにしました。
Matt Sanders

私はこの方法論を使用しており、これまでは継承以外の問題はありませんでした。あなたの例の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使用していますか?このために別のプロジェクトを作成しましたが、参照が見つかりません。
Ruchan

.WhereisはSystem.LinqDataContextコードが追加されたクラスです(私のEF DbContext
実装

12

EF Core 2.2以降では、すべての構成(IEntityTypeConfigurationインターフェイスを実装したクラス)を、DbContextクラスから継承されたクラスOnModelCreatingのメソッドの1行に追加できます

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の折りたたみ動作を利用するために私がしたいもう1つのことは、「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'は、ModelBuilderExtenions.GetMappingTypes()の '!x.IsAbstract'(System.Type.IsAbstract)で、タイプ 'メソッドグループ'のオペランドに適用できません。 。mscorlibへの参照を追加する必要がありますか?.NET Core 1.0プロジェクトにそれを行うにはどうすればよいですか?
RandyDaddis 2016

.netコアプロジェクト(netstandardを使用)の場合、System.Reflection名前空間で拡張GetTypeInfo()を使用する必要があります。x.GetTypeInfo()。IsAbstractまたはx.GetTypeInfo()。GetInterfaces()として使用
animalito maquina 2017年

私は私のソリューションの一部を使用しましたが、うまくいきました。ありがとう!
ディエゴコティーニ2018

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の場合:

私はココワラの答えを受け取り、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
    }
}

次に、スタートアップクラスで、DbContextを構成するときに、すべての構成クラスを検索する場所をEntity Frameworkに通知する必要があります。

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


複数の質問に同じ回答を投稿しないでください。同じ情報が実際に両方の質問に答える場合、一方の質問(通常は新しい質問)を他方の質問の複製として閉じます。これを重複として閉じることを投票することでこれを示すことができます。または、その十分な評判がない場合は、フラグが立てられて重複であることを示します。それ以外の場合は、この質問に対する回答を調整し、同じ回答を複数の場所に貼り付けないでください。
エリクセニド2016年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.