Efcore: Model configuration: Entity type configuration can be factored into a class

Created on 10 Aug 2015  Â·  45Comments  Â·  Source: dotnet/efcore

Edited by @rowanmiller Oct-13-2016

EntityTypeConfiguration<T> is a feature in EF6.x that allows you to encapsulate the configuration for an entity type in a class.

Here is some code you can use to enable the pattern until we add support in EF Core.

``` c#
using Microsoft.EntityFrameworkCore.Metadata.Builders;

namespace Microsoft.EntityFrameworkCore
{
public abstract class EntityTypeConfiguration
where TEntity : class
{
public abstract void Map(EntityTypeBuilder builder);
}

public static class ModelBuilderExtensions
{
    public static void AddConfiguration<TEntity>(this ModelBuilder modelBuilder, EntityTypeConfiguration<TEntity> configuration)
        where TEntity : class
    {
        configuration.Map(modelBuilder.Entity<TEntity>());
    }
}

}

And here is a sample application that uses these types:

``` c#
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;

namespace Demo
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var db = new BloggingContext())
            {
                db.Database.EnsureCreated();
            }
        }
    }

    public class BloggingContext : DbContext
    {
        public DbSet<Blog> Blogs { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Demo;Trusted_Connection=True;");
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.AddConfiguration(new BlogMap());
        }
    }

    public class Blog
    {
        public int BlogId { get; set; }
        public string Name { get; set; }
        public string Url { get; set; }
    }

    public class BlogMap : EntityTypeConfiguration<Blog>
    {
        public override void Map(EntityTypeBuilder<Blog> builder)
        {
            builder.ToTable("tbl_blogs");

            builder.Property(b => b.Name)
                .IsRequired()
                .HasMaxLength(200);

            builder.Property(b => b.Url)
                .IsRequired()
                .HasMaxLength(500);

            builder.HasIndex(b => b.Url);
        }
    }
}
closed-fixed type-enhancement

Most helpful comment

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

Use:

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

All 45 comments

You can configure the mapping by overriding OnModelCreating in your derived DbContext
Is there a specific thing you aren't able to configure?

So if we have complex mappings, we just need to factor out into our own static classes and call them from OnModelCreating. This will need to suffice for the primary use case for EntityTypeConfiguration in EF6.

There are several ways of factoring OnModelCreating.
For example you can turn

public override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Chassis>(b =>
        {
            b.Key(c => c.TeamId);
            b.Property(e => e.Version)
                .ValueGeneratedOnAddOrUpdate()
                .ConcurrencyToken();
        });
}

into

public override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Chassis>(ConfigureChassis);
}

private static void ConfigureChassis(EntityTypeBuilder<Chassis> b)
{
    b.Key(c => c.TeamId);
    b.Property(e => e.Version)
        .ValueGeneratedOnAddOrUpdate()
        .ConcurrencyToken();
}

or

public override void OnModelCreating(ModelBuilder modelBuilder)
{
    ConfigureChassis(modelBuilder);
}

private static void ConfigureChassis(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Chassis>(b =>
        {
            b.Key(c => c.TeamId);
            b.Property(e => e.Version)
                .ValueGeneratedOnAddOrUpdate()
                .ConcurrencyToken();
        });
}

It would be really great to have something like EntityTypeConfiguration and also with autoscanning like in FluentNHibernate.

Reopening so that we can triage this... we should consider enabling the same pattern we had in EF6. It's trivial to implement it yourself... but it is a nice piece of sugar that we've seen numerous people use.

I'm glad I'm not the only one. I rely on it heavily.

I've actually started a solution that I can probably contribute back.

Moving to backlog since it's not critical for our initial release (it's very easy to quickly implement this yourself without needing to modify EF code). That said, we do want to implement it and we would accept a PR for it.

@mmillican Do you have anything available already? I require the functionality of being able to specify entity type configurations, too. However, I won't have access to a concrete DbContext implementation to access OnModelCreating (this issue has been persistent throughout EF releases, requiring proxying to get around), as the DbContext is generated on-the-fly with assembly scanning. I would implement an IModelSource, but individual implementations of providers have their own modelsource which they request on model creation. One of the solutions I can see, is adding an two interfaces:

``` c#
public interface IModelBuilderContributor
{
void Contribute(ModelBuilder modelBuilder);
}

public interface IModelBuilderContributorSource
{
IEnumerable GetContributors([NotNull] DbContext context);
}

and extend parameters of IModelSource => GetModel() and ModelSource => CreateModel() with `[CanBeNull] IModelBuilderContributorSource value`
Between FindSets() and OnModelCreating() add

``` c#
if (contributorSource != null)
                ContributeToModelBuilder(modelBuilder, contributorSource.GetContributors(context));


protected virtual void ContributeToModelBuilder(ModelBuilder modelBuilder, IEnumerable<IModelBuilderContributor> getContributors)
        {
            foreach(var contributor in getContributors)
                contributor.Contribute(modelBuilder);
        }

finally add to DbContextServices constructor private variable assignment

c# _modelBuilderContributorSource = new LazyRef<IModelBuilderContributorSource>( () => _provider.GetRequiredService<IModelBuilderContributorSource>());

This would give "infinite extension points!", as contributors would come through DbContext's own serviceprovider, have the capability for DI, and would allow any arbitrary additional extension points (such as EntityTypeConfigurationContributor). All providers would therefore also have support for it.

If this is something that would be even wildly acceptable in EF7 (rather than hardwiring the responsibility of adding type configurations in OnModelCreating override), and is not already worked on, I'm more than willing to implement and PR.

@Grinderofl - I haven’t yet, unfortunately; I got tied up in projects. It is still on my list however.

My use case is similar to yours, in that I use assembly scanning to add types to the context. This is extremely useful for large applications as well as “plugin” style applications.

I think I follow what you are saying with your ideas, and they make sense. My idea was to essentially replicate the EntityTypeConfiguration<T> functionality.

Okay, I'll attempt to implement it and pr, got some rough stubs in place now.

2992 describes a mechanism that could be used to implement this.

This is still required, unfortunately due to time constraints I had to go ahead and implement the ability to configure dbcontext programmatically via https://github.com/Grinderofl/FluentModelBuilder instead of PRing, though truly, all that is needed, is to expose ModelBuilder somewhere outside of DbContext.OnModelCreating().

Removing EntityTypeConfiguration<T> and modelBuilder.Configurations.AddFromAssembly is a step backwards. I had to implement modelBuilder.Configurations.AddFromAssembly on my own since EF Code First CPT3 to comply with the open/closed principle. Finally in 6 it was part of the flow and now in EF7 all the bits are gone. #IMHO

@valmont EF Core is still trying to focus on simplicity first, which means that there's a mechanism to support any kind of model customization - IModelCustomizer (implement/override ModelCustomizer, replace in services after AddEntityFramework())

I've written a library to make that a bit easier if you are interested.

https://github.com/Grinderofl/FluentModelBuilder

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

Use:

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

So is EntityTypeConfiguration class (and modelBuilder.Configurations.Add mechanism) going to be implemented?

@freerider7777 I don't think EF team has this as a priority in their sights right now. In the meanwhile, may I point you towards the release of https://github.com/Grinderofl/FluentModelBuilder which does what you are looking for and more.

I think we will probably do this, or something like it, but it is lower priority than some of the other features EF Core is missing at the moment.

In case you want this with .NET Core:

Make sure you have this in your project.json:

"System.Reflection.TypeExtensions": "4.1.0",

And modify the code to use:

    using System;
    using System.Collections.Generic;
    using System.Reflection;
    using Microsoft.EntityFrameworkCore;
    using Microsoft.EntityFrameworkCore.Metadata.Builders;
    using System.Linq;

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

and

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

Hi Friends,

I would like to map this Separate EntityMap in ModelBuilder, Could you please help me how we cn do this. I am using EntityFrameworkCore.

``` C#
public class EnquiryMap : EntityTypeConfiguration
{
public const string TableName = "Enquiry";

    public EnquiryMap()
    {                   
        ToTable(TableName);

        Property(x => x.Key);
        Property(x => x.ApplicationNumber);            
    }
}

public class DbAccessContext :DbContext
{
public DbSet Enquiries { get; set; }

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

        //?????????????
        //modelBuilder.Entity<>
        //.RegisterEntityMapping<Enquiry, EnquiryMap>();

    }
}

```

@manishkrai I just updated the description at the top of this page to include some simple types you can add to your project to enable this pattern. It also shows a sample application that uses them.

@rowanmiller ,kindly share me the link of sample. I didn't find where you are referring or you reply the snipets here.

In meantime, I tried to achieve this but getting the error - The entity type 'Student.Enquiry' requires a primary key to be defined.. Key is primary key in table but It seems HasKey is not considering this as Primary here.

public class EnquiryMap : EntityTypeConfiguration<Enquiry>
    {
        public const string TableName = "Enquiries";

        public EnquiryMap()
        {                   
            ToTable(TableName);

            HasKey(x => x.Key);
            Property(x => x.Key).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); 
            Property(x => x.ApplicationNumber);            
        }
    }
public class DbAccessContext :DbContext
    {
        public DbSet<Enquiry> Enquiries { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlServer(ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString);
        }

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

            //?????????????
            //modelBuilder.Entity<>
            //.RegisterEntityMapping<Enquiry, EnquiryMap>();

        }
    }
var context = new DbAccessContext();
            var blog = context.Enquiries.FirstOrDefault();

@manishkrai scroll to the very top of the page and the example is listed in the first comment

@rowanmiller How we can map the "Identity with Primary Key" and Timestamp columns ?

As I am still getting the error The entity type 'SMS.DataAccess.Student.Enquiry' requires a primary key to be defined. even after using the approach you have suggested.

@manishkrai from the code you have listed it is not clear if the configuration you have in EnquiryMap is actually getting applied. In your OnModelCreating you are just creating an instance of EnquiryMap, but this won't cause the configuration to be applied to the model. This would explain why your configuration to set Key as the primary key is not applied.

I'm also not sure what the EntityTypeConfiguration<T> you are deriving from is, it looks like the EF6 class, but that doesn't exist in EF Core and the sample I provided uses a different pattern with a Map method (rather than applying configuration in the constructor).

@rowanmiller
I am getting this error (The entity type 'Enquiry' requires a primary key to be defined. ) while using the below code -

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

    public static class ModelBuilderExtensions
    {
        public static void AddConfiguration<TEntity>(this ModelBuilder modelBuilder, EntityTypeConfiguration<TEntity> configuration)
            where TEntity : class
        {
            configuration.Map(modelBuilder.Entity<TEntity>());
        }
    }
 public class Enquiry
    {
        public int Key { get; set; }
        public string ApplicationNumber { get; set; }
        public string Name { get; set; }        
    }
public class EnquiryMap : EntityTypeConfiguration<Enquiry>
    {
        public const string TableName = "Enquiries";

        public override void Map(EntityTypeBuilder<Enquiry> builder)
        {
            builder.ToTable(TableName);

            builder.Property(b => b.Key)
                .IsRequired()
                .ValueGeneratedOnAdd();

            builder.Property(b => b.ApplicationNumber);
            builder.Property(b => b.Name);            
        }
    }
public class DbAccessContext :DbContext
    {
        public DbSet<Enquiry> Enquiries { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlServer(ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString);
        }

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

        }
    }
var context = new DbAccessContext();
            var blog = context.Enquiries.FirstOrDefault();

@manishkrai you need to add a call to builder.HasKey(b => b.Key); into your configuration. Key isn't a name that EF will pick up by convention.

@rowanmiller Thanks,It solved my issue.

@rowanmiller, I am getting the error - "Specified cast is Invalid" while using EF Core with Repository pattern. If you could help me on this.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SMS.DataModel;
using System.Linq.Expressions;

namespace SMS.DataAccess.Repository
{
    public interface IRepository<T> where T : EntityBase
    {
        T GetByKey(long key);
        IEnumerable<T> List();
        IEnumerable<T> List(Expression<Func<T, bool>> predicate);
        void Add(T entity);
        void Update(T entity);
        void Delete(T entity);        
    }
}

using SMS.DataModel;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SMS.DataAccess.Repository
{
    public class Repository<T> : IRepository<T> where T :EntityBase
    {
        private readonly DbAccessContext _dbContext;

        public Repository(DbAccessContext dbContext)
        {
            _dbContext = dbContext;
        }

        public T GetByKey(long key)
        {
            return _dbContext.Set<T>().FirstOrDefault(x => x.Key == key);
        }

        public IEnumerable<T> List()
        {
            return _dbContext.Set<T>().AsEnumerable();
        }

        public IEnumerable<T> List(System.Linq.Expressions.Expression<Func<T, bool>> predicate)
        {
            return _dbContext.Set<T>().Where(predicate).AsEnumerable();
        }

        public void Add(T entity)
        {
            _dbContext.Set<T>().Add(entity);
            _dbContext.SaveChanges();
        }

        public void Update(T entity)
        {
            _dbContext.Entry(entity).State = Microsoft.EntityFrameworkCore.EntityState.Modified;
            _dbContext.SaveChanges();
        }

        public void Delete(T entity)
        {
            _dbContext.Set<T>().Remove(entity);
            _dbContext.SaveChanges();
        }        
    }
}

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SMS.DataModel
{
    public abstract class EntityBase
    {
        public long Key { get; set; }
    }
}

using SMS.Enumerations;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SMS.DataModel.Student
{
    public class Enquiry : EntityBase
    {
        public string ApplicationNumber { get; set; }
        public string Name { get; set; }
        public string GuardianName { get; set; }        
    }
}

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using SMS.DataAccess.Mappings;
using SMS.DataModel;
using SMS.DataModel.Student;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SMS.DataAccess
{
    public class DbAccessContext :DbContext
    {      

        protected new DbSet<TEntity> Set<TEntity>() where TEntity : EntityBase
        {
            return base.Set<TEntity>();
        }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlServer(ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString);
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
            modelBuilder.AddConfiguration(new AcademicYearMap());
            modelBuilder.AddConfiguration(new AreaMap());
            modelBuilder.AddConfiguration(new SectionMap());
            modelBuilder.AddConfiguration(new CourseMap());
            modelBuilder.AddConfiguration(new EnquiryMap());
            modelBuilder.AddConfiguration(new SMSTypeMap());
        }
    }
}

public class EnquiryListController : Controller
    {
        Repository<Enquiry> _enquiryRepository;
        Repository<SMSType> _smsTypeRepository;

        public EnquiryListController(Repository<Enquiry> enquiryRepository, Repository<SMSType> smsTypeRepository)
        {
            _enquiryRepository = enquiryRepository;
            _smsTypeRepository = smsTypeRepository;
        }
        //
        // GET: /Student/EnquiryList/
        public ActionResult Index()
        {
            EnquiryRecSelectContextViewModel contextModel = new EnquiryRecSelectContextViewModel();
            var a = _enquiryRepository.List().ToList().Count();
            contextModel.Enquiries = Mapper.Map<IEnumerable<Enquiry>, IEnumerable<EnquiryViewModel>>(_enquiryRepository.List());
            //contextModel.AllSMSTypes = Mapper.Map<IEnumerable<SMSType>, IEnumerable<SMSTypeViewModel>>(_smsTypeRepository.List());

            return View("EnquiryList", contextModel);
        }
    }

@manishkrai this is deviating from the EntityTypeConfiguration feature being tracked by this issue. Please open a new issue. When you do, can you include the full stack trace for the exception and a complete code listing that we can run.

Ok, Its bin a while since this issue opened.
Is there any alternative for EF6( EntityTypeConfiguration ), in EF7 aka EntityFrameworkCore

@xhevatibraimi not built in, but the description at the top of this page (the first comment) has a small amount of code you can add to your project to enable the same pattern.

@rowanmiller @xhevatibraimi I have added a project that allows you to configure entities outside of the DbContext.OnModelCreating where you only have to configure where to find the configuration in your Startup class.

First you need to create a class which inherits from StaticDotNet.EntityFrameworkCore.ModelConfiguration.EntityTypeConfiguration<TEntity> where TEntity is the class you want to configure.

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

Then in your Startup class you just need to tell Entity Framework where to find all of your configuration classes when you are configuring your 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 )
    );
}

There is also an option for adding type configurations using a provider. The repo has complete documentation on how to use it.

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

@rowanmiller @xhevatibraimi - Hi there, I've developed some code to fill this void and would be interested in contributing it back. The classes I have developed act similarly to the legacy EntityTypeConfiguration in that they allow you to define your mappings in a separate class, cleanly separating the data model from persistence. Similarly to EF6, EntityTypeConfigurations can be added in the OnModelBuilding() method using the ModelBuilder extension method AddConfiguration. While not a 100% match, it's also closer to the syntax of legacy EntityTypeConfiguration than the pattern above.

The API directly mirrors the EntityBuilder largely because, under the hood, the classes are merely accumulating the actions to be performed against an EntityBuilder. Since they are a facade over the existing functionality, there's minimal maintenance required and minimal opportunity for things to diverge, but I do recognize that I still need to add some unit tests. If there's any interest, the new code can be viewed in the feature/entitytypeconfiguration branch of my forked repo. An example of the methodology can be seen below (note, in reality this is only a portion of one class).
~~~~
public abstract class EntityTypeConfiguration where TEntity : class
{
private List>> _actions = new List>>();
protected PropertyConfiguration Property(Expression> selector)
{
PropertyConfiguration pConfig = new PropertyConfiguration();
_actions.Add(b => {
var p = b.Property(selector);
pConfig.Apply(p);
});
return pConfig;
}
internal virtual void Apply(EntityTypeBuilder builder)
{
foreach(var a in _actions)
{
a(builder);
}

    }
}

~~~~

Each method on the EntityTypeConfiguration is creating an action to be called on the EntityBuilder, which is then invoked when the Apply method is called (by the ModelBuilder.AddConfiguration extension method). Definitely still some work left to do to button things up, but would be happy to do so if it were likely to be used.

@ajcvickers - Hi there, Is it possible to generate these configuration classes on dbcontext scaffold (Database First) instead of single bulky OnModelCreating method?

@ilya-chumakov Not currently--see my comment on #8434

@mmillican please, can you tell how to add unique key constraint in dot net core fluent api ?

@djamajo This is not relevant to this issue, but you can call
C# modelBuilder.Entity<Customer>().HasAlternateKey(b => b.Name)
to add a unique key constraint on Name

Stumbling upon this issue/solution, it worked fine for me, until I have the need to have another generic parameter, like this:

public class BaseEntityIdMappingConfiguration<T,TId> : EntityMappingConfiguration<T,TId> where T : BaseEntityId<TId>
    {
        /// <inheritdoc />
        public override void Map(EntityTypeBuilder<T> entity)
        {
            entity.HasKey(e => e.Id);
            entity.Property(e => e.Id)
            .HasColumnName("id");

            entity.Property(e => e.IsDeleted)
                .HasColumnName("isDeleted")
                .HasDefaultValue(false);

            entity.Property(e => e.IsDisabled)
                .HasColumnName("isDisabled")
                .HasDefaultValue(false);
        }
    }

I've added the correspondent interface and class, and changed the IEnumerable<Type> mappingTypes to
IEnumerable<Type> mappingTypes = assembly.GetMappingTypes(typeof(IEntityMappingConfiguration<>)) .Concat(assembly.GetMappingTypes(typeof(IEntityMappingConfiguration<,>)));

The problem is that the type TId cannot be infered, throwing an "ArgumentException

System.ArgumentException: Cannot create an instance of
Configurations.BaseEntityIdMappingConfiguration`2[T,TId] because Type.ContainsGenericParameters is true

Any idea on how can i Solve this?

@DanielSSilva You'll have to specify the concrete type to use for TId by creating derived configurations and making BaseEntityIdMappingConfiguration abstract.
Even without this pattern EF doesn't map unbounded generic types and you would need to configure BaseEntityId<int>, BaseEntityId<string>, etc..

@AndriySvyryd thank you for your quick response. That's exactly the solution that I've ended up with. Glad i was on the right way :)

As a side note, I've also had to change the IEnumerable<Type> mappingTypes = assembly.GetMappingTypes(typeof(IEntityMappingConfiguration<>)) to

IEnumerable<Type> mappingTypes = assembly.GetMappingTypes(typeof(IEntityMappingConfiguration<>))
        .Where(t => !t.Name.StartsWith("BaseEntityLongIdMappingConfiguration")
                 && !t.Name.StartsWith("BaseEntityByteIdMappingConfiguration"));

because both types are being "catched" by GetMappingTypes method

@CumpsD @domenkogler You solutions don't work when DbContext is an abstract class and inherited by subclasses wherein the base abstract class is located on a separated library. I'm using Clean Architecture, so models and configurations are in a separate dll. Does anybody here have an idea how to apply configurations from the calling assembly?

Core.csproj
   --> Configurations
          - PersonConfig.cs
   --> Models
          - IBaseEntity.cs
          - Person.cs
    - AbstractBaseDbContext.cs

WebApi.csproj
    --> SqlServerDbContext.cs // this is the calling assembly, configurations from the base project are not being called during migrations

whizkidwwe1217 maybe reflection wiil help )) But is not so fast )) like native code !
I have problem, I'am too want thats all my model stores in asseblmy, and loaded dynamicaly!

I guessing many people on ef core Rep Want to make self enterprise systems, that must have user model configuration !

Was this page helpful?
0 / 5 - 0 ratings