Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Model configuration: Entity type configuration can be factored into a class #2805

Closed
mmillican opened this issue Aug 10, 2015 · 45 comments
Closed
Assignees
Labels
closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. type-enhancement
Milestone

Comments

@mmillican
Copy link

mmillican commented Aug 10, 2015

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.

using Microsoft.EntityFrameworkCore.Metadata.Builders;

namespace Microsoft.EntityFrameworkCore
{
    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>());
        }
    }
}

And here is a sample application that uses these types:

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);
        }
    }
}
@mmillican mmillican changed the title Equivalent of EntityTypeConfiguration<T> in EF 7? Equivalent of EntityTypeConfiguration<T> in EF 7? Aug 10, 2015
@AndriySvyryd
Copy link
Member

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

@jarrettv
Copy link

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.

@AndriySvyryd
Copy link
Member

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

@rosieks
Copy link

rosieks commented Aug 11, 2015

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

@rowanmiller rowanmiller reopened this Aug 12, 2015
@rowanmiller
Copy link
Contributor

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.

@rowanmiller rowanmiller changed the title Equivalent of EntityTypeConfiguration<T> in EF 7? EntityTypeConfiguration<T> (or similar) Aug 12, 2015
@mmillican
Copy link
Author

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.

@rowanmiller rowanmiller added this to the Backlog milestone Aug 18, 2015
@rowanmiller
Copy link
Contributor

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.

@Grinderofl
Copy link
Contributor

@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:

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

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

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

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

_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.

@mmillican
Copy link
Author

@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.

@Grinderofl
Copy link
Contributor

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

@AndriySvyryd
Copy link
Member

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

@Grinderofl
Copy link
Contributor

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

@valmont
Copy link

valmont commented Mar 20, 2016

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

@Grinderofl
Copy link
Contributor

@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

@domenkogler
Copy link

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

@freerider7777
Copy link

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

@Grinderofl
Copy link
Contributor

@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.

@xhevatibraimi
Copy link

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

@rowanmiller
Copy link
Contributor

@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.

@john-t-white
Copy link

john-t-white commented Nov 23, 2016

@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

@rorymurphy
Copy link

rorymurphy commented Dec 21, 2016

@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<TEntity> where TEntity : class
    {
        private List<Action<EntityTypeBuilder<TEntity>>> _actions = new List<Action<EntityTypeBuilder<TEntity>>>();
        protected PropertyConfiguration<T> Property<T>(Expression<Func<TEntity, T>> selector)
        {
            PropertyConfiguration<T> pConfig = new PropertyConfiguration<T>();
            _actions.Add(b => {
                var p = b.Property<T>(selector);
                pConfig.Apply(p);
            });
            return pConfig;
        }
        internal virtual void Apply(EntityTypeBuilder<TEntity> 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.

@rowanmiller rowanmiller modified the milestones: 2.0.0, Backlog Jan 19, 2017
rowanmiller added a commit that referenced this issue Jan 19, 2017
rowanmiller added a commit that referenced this issue Jan 19, 2017
@rowanmiller rowanmiller changed the title EntityTypeConfiguration<T> (or similar) IEntityTypeConfiguration<TEntity> (allows configuration for an entity to be factored into a class) Jan 19, 2017
@rowanmiller rowanmiller added the closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. label Jan 19, 2017
@ajcvickers ajcvickers changed the title IEntityTypeConfiguration<TEntity> (allows configuration for an entity to be factored into a class) Entity type configuration can be factored into a class May 9, 2017
@ajcvickers ajcvickers changed the title Entity type configuration can be factored into a class Model configuration: Entity type configuration can be factored into a class May 9, 2017
@ilya-chumakov
Copy link

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

@ajcvickers
Copy link
Member

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

@djamajo
Copy link

djamajo commented Aug 10, 2017

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

@AndriySvyryd
Copy link
Member

@djamajo This is not relevant to this issue, but you can call

modelBuilder.Entity<Customer>().HasAlternateKey(b => b.Name)

to add a unique key constraint on Name

@DanielSSilva
Copy link

DanielSSilva commented Jan 22, 2018

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?

@AndriySvyryd
Copy link
Member

@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..

@DanielSSilva
Copy link

@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 :)

@DanielSSilva
Copy link

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

@whizkidwwe1217
Copy link

whizkidwwe1217 commented Feb 4, 2018

@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

@Diaskhan
Copy link

Diaskhan commented Mar 6, 2018

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 !

@ajcvickers ajcvickers modified the milestones: 2.0.0-preview1, 2.0.0 Oct 15, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. type-enhancement
Projects
None yet
Development

No branches or pull requests