Skip to content
Nero Sule edited this page Jul 4, 2016 · 3 revisions

FluentModelBuilder gives an easy way to configure the backing model for Entity Framework Core's DbContext, and also allowing you to have a DbContext without having to inherit from it. A minimal example that adds a DbContext with InMemoryDatabase and all classes that inherit from Entity from the assembly of Entity looks like this:

var services = new ServiceCollection();
services.AddDbContext<DbContext>(x => x
    .UseInMemoryDatabase()
    .Configure(fluently => 
        fluently.UsingAssemblyOf<MyEntity>(entity => 
            entity.IsSubClassOf(typeof(Entity))));

FluentModelBuilder provides an entry point for Entity Framework Core Model customisation programmatically, instead of using DbSet or OnModelCreating override. This gives the DbContext model configuration a more modular and automatic approach. It's made with the purpose of allowing familiar configuration for developers switching from Fluent NHibernate, which has provided heaps of inspiration for this project.

Goals

Entity Framework Core is a ground-up rework of Microsoft's ORM platform. Unfortunately, it also means all of the features previous EF's had will need to be re-implemented. That's a lot of work, understandably so, but sadly a whole lot of features are still missing. One of the biggest offenders is the ability to configure your backing model from outside your subclassed DbContext, where in EF6 and earlier, there were several means of doing this, currently there are two officially supported ways - building your model yourself, which has the pitfall of not being considerate with the DbContext's DbSet properties - but is well documented; or implementing your own IModelCustomizer and replacing it in the service collection. Both of those are cumbersome, require writing boilerplate code, and require some knowledge of Entity Framework Core's internals.

Cue FluentModelBuilder: It has its own ModelCustomizer which uses Fluent syntax and several different ways of altering its functionality.

Principles

FluentModelBuilder is based on conventions. This means that it aims to do as much as possible with as many suitable pre-configured options as possible, ensuring that configuration would be needed in only extreme cases.

The configuration is primarily attached to the action for configuring DbContextOptions during ServiceCollection building using AddDbContext<TContext>(options => options.Configure(fluently => {});. By default FluentModelBuilder injects itself into the internal service provider Entity Framework Core uses, so if you are using application service provider, it's suggested you use services.ConfigureEntityFramework(fluently => {}); after AddDbContext<TContext>().

FluentModelBuilder executes in multiple stages. First, all Alterations are gathered and applied, as Alterations can change the behaviour of AutoModelBuilder, add more overrides, type sources, conventions, etc. After Alterations are done executing, all Type Sources are gathered, and types from them added based on the initial configuration. Next, Conventions are applied over the entire Model Builder, after which individual Overrides are executed over their respective entity type.

Syntax

There are couple of different entry points for the fluent API. One is lambda syntax, following the standard Configure(fluently => {}), the other one uses From syntax, allowing multiple different model builders if needed: Configure(From.AssemblyOf<Entity>()...). They both lead to exact same followup however, From can just be prepared before the configuration.

From:

services.AddDbContext<DbContext(x =>
    x.Configure(From
        .AssemblyOf<MyEntity>(new MyConfiguration())
        .UseOverridesFromAssemblyOf<MyEntity>());

Fluent:

services.AddDbContext<DbContext(x =>
    x.Configure(fluently =>
        fluently.FromAssemblyOf<MyEntity>(new MyConfiguration())
        .UseOverridesFromAssemblyOf<MyEntity>());

Examples

Here are a couple more examples to show you how it works:

Example 1 - DbContext with specific entities, without DI

Subclassing DbContext

var services = new ServiceCollection()
    .AddDbContext<ApplicationContext>(x => x.UseInMemoryDatabase());

FluentModelBuilder equivalent

var services = new ServiceCollection().AddDbContext<ApplicationContext>(x => 
    x
      .UseInMemoryDatabase()
      .Configure(c => c.Using().Override<EntityOne>()));

Subclass DbContext with FluentModelBuilder

public class ApplicationContext : DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder builder)
    {
        builder
          .UseInMemoryDatabase()
          .Configure(c => c.Using().Override<EntityOne>()));
    }
}

Example 2 - DbContext with specific entities, using application DI

Subclassing DbContext

// ApplicationContext.cs
public class ApplicationContext : DbContext
{
  public DbSet<EntityOne> EntityOnes { get; set; } // Available as both 
context.EntityOnes and context.Set<EntityOne>();
  public DbSet<EntityTwo> EntityTwos { get; set; } // Available as both 
context.EntityTwos and context.Set<EntityTwo>();

  protected override void OnModelCreating(ModelBuilder builder)
  {
    // Alternatively:
    builder.Entity<EntityThree>(); // Available as context.Set<EntityThree>();
  }
}

// Startup.cs
public class Startup
{
    public void Configure(IServiceCollection services)
    {
    	services.AddDbContext<ApplicationContext>(x => x.UseInMemoryDatabase());
    }
}

FluentModelBuilder equivalent

// Startup.cs
public class Startup
{
    public void Configure(IServiceCollection services)
    {
        // Use the 'From' API
	services
      	    .AddDbContext<DbContext>(x => x.UseInMemoryDatabase()
                .Configure(From.Empty()
                    .Override<EntityOne>()
                    .Override<EntityTwo>()
                    .Override<EntityThree>());
    }
}

Example 3 - DbContext with discovered entities

Subclassing DbContext

// ApplicationContext.cs
public class ApplicationContext : DbContext
{
  	protected override void OnModelCreating(ModelBuilder builder)
  	{
    	var types = typeof (ApplicationContext).GetTypeInfo()
                .Assembly.GetExportedTypes()
                .Where(x => x.GetTypeInfo().Namespace.EndsWith(".Entities"));
            foreach (var type in types)
                modelBuilder.Entity(type);
  	}
}

// Startup.cs
public class Startup
{
	public void Configure(IServiceCollection services)
    {
    	services.AddDbContext<ApplicationContext>(x => x.UseInMemoryDatabase());
    }
}

FluentModelBuilder equivalent

// Startup.cs
public class Startup
{
	public void Configure(IServiceCollection services)
	{
   	    services.AddDbContext<DbContext>(x => x.UseInMemoryDatabase()
                .Configure(c => 
                    c.UsingAssemblyOf<Startup>(new EntityConfiguration())));
    }
}

// EntityConfiguration.cs
public class EntityConfiguration : DefaultEntityAutoConfiguration
{
    public override bool ShouldMap(Type type)
    {
    	return base.ShouldMap(type) && type.GetTypeInfo().Namespace.EndsWith(".Entities");
    }
}