Implementing Missing Features in Entity Framework Core – Part 7: Entity Configuration in Mapping Classes

This is the seventh post in a series of posts about bringing the features that were present in Entity Framework pre-Core into EF Core. The others are:

  • Part 1: Introduction, Find, Getting an Entity’s Id Programmatically, Reload, Local, Evict

  • Part 2: Explicit Loading

  • Part 3: Validations

  • Part 4: Conventions

  • Part 5: Getting the SQL for a Query

  • Part 6: Lazy Loading

This time I’m going to cover automatic entity configuration in mapping classes, that is, outside of the DbContext.OnModelCreating method. If you remember, Entity Framework Code First supported having classes inheriting from EntityTypeConfiguration in the same assembly as the context, and these classes would be loaded automatically. This made it much simpler to add new mapping classes to a project without touching the context.

This functionality hasn’t been ported to Entity Framework Core yet, but it is being developed for the next version, and is tracked by ticket 2805. My implementation finds mapping classes in the same assembly as the context automatically, or it can load configuration explicitly from a mapping class. Here is my contract for the mapping class:

public interface IEntityTypeConfiguration<T> where T : class
{
void Configure(EntityTypeBuilder<T> entityTypeBuilder);
}

As you can see, this needs to be implemented in a concrete generic class and bound to a specific entity type.

The actual implementation goes like this:

public static class EntityTypeConfigurationExtensions
{
private static readonly MethodInfo entityMethod = typeof(ModelBuilder).GetTypeInfo().GetMethods().Single(x => (x.Name == "Entity") && (x.IsGenericMethod == true) && (x.GetParameters().Length == 0));

private static Type FindEntityType(Type type)
{
var interfaceType = type.GetInterfaces().First(x => (x.GetTypeInfo().IsGenericType == true) && (x.GetGenericTypeDefinition() == typeof(IEntityTypeConfiguration<>)));
return interfaceType.GetGenericArguments().First();
}

private static readonly Dictionary<Assembly, IEnumerable<Type>> typesPerAssembly = new Dictionary<Assembly, IEnumerable<Type>>();

public static ModelBuilder ApplyConfiguration<T>(this ModelBuilder modelBuilder, IEntityTypeConfiguration<T> configuration) where T : class
{
var entityType = FindEntityType(configuration.GetType());

dynamic entityTypeBuilder = entityMethod
.MakeGenericMethod(entityType)
.Invoke(modelBuilder, new object[0]);

configuration.Configure(entityTypeBuilder);

return modelBuilder;
}

public static ModelBuilder UseEntityTypeConfiguration(this ModelBuilder modelBuilder)
{
IEnumerable<Type> configurationTypes;
var asm = Assembly.GetEntryAssembly();

if (typesPerAssembly.TryGetValue(asm, out configurationTypes) == false)
{
typesPerAssembly[asm] = configurationTypes = asm
.GetExportedTypes()
.Where(x => (x.GetTypeInfo().IsClass == true) && (x.GetTypeInfo().IsAbstract == false) && (x.GetInterfaces().Any(y => (y.GetTypeInfo().IsGenericType == true) && (y.GetGenericTypeDefinition() == typeof(IEntityTypeConfiguration<>)))));
}

var configurations = configurationTypes.Select(x => Activator.CreateInstance(x));

foreach (dynamic configuration in configurations)
{
ApplyConfiguration(modelBuilder, configuration);
}

return modelBuilder;
}
}

You can see that it uses some dynamic magic to make things simpler, otherwise we’d need to have even more reflection. Dynamics take care of these things quite nicely.

The code essentially looks at the entry assembly and finds all non-abstract public types that implement IEntityTypeConfiguration<T>. For each of those, it creates an instance, extracts the template argument and creates an EntityTypeBuilder<T> from calling the Entity<T> method of the ModelBuilder class and calls the IEntityTypeConfiguration<T>.Configure method of the instantiated mapping class passing it the EntityTypeBuilder<T> which allows it to supply mapping configuration for the mapped entity (T).

We need to explicitly call this extension inside DbContext.OnModelCreating:

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

And it takes care of everything for us. Or, if we want to load a single mapping class explicitly, we can also do so:

modelBuilder.UseEntityTypeConfiguration<MyEntityTypeConfiguration>();

Finally, a simple mapping class might be:

public class MyEntityTypeConfiguration : IEntityTypeConfiguration<MyEntity>
{
public void Configure(EntityTypeBuilder<MyEntity> entityTypeBuilder)
{
entityTypeBuilder.ToTable("MyEntity");
entityTypeBuilder.Property(x => x.MyEntityId).HasColumnName("Id");
}
}

In case you’re interested, this feature is similar to the one being implemented for .NET Core, except that it doesn’t find mapping classes automatically. The IEntityTypeConfiguration<T> interface is exactly the same.

What’s New in Entity Framework Core 1.1

Introduction

Entity Framework Core 1.1 was released last November. With it, besides some bug fxes and semi-transparent improvements, came along a few goodies. If you read my previous post on features missing in Entity Framework Core 1.0, you’ll be please to know that a few have been addressed.

New API Methods

The Find method, for returning an entity from its primary key, is back (I had provided a workaround here):

var e1 = ctx.DbSet<MyEntity>().Find(1);

New is GetDatabaseValues, which goes to the database and fetches the current values for the current entity and primary key:

var dbProperties = ctx.Entry<MyEntity>(e).GetDatabaseValues();

Reload and Explicit Load

It is now again possible to reload an entity, causing it to be re-hydrated with the current values from the database, through the Reload method (also available as a workaround here):

ctx.Entry<MyEntity>(e).Reload();

And it is also possible to force load a not-loaded collection ():

ctx.Entry<MyEntity>(e).Collection(x => x.MyColl).Load();

As well as entity references (one-to-one, many-to-one):

ctx.Entry<MyEntity>(e).Reference(x => x.MyRef).Load();

Connection Resiliency

Connection resiliency made it way to version 1.1 as well:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseSqlServer("my connection string", opt => opt.EnableRetryOnFailure());

base.OnConfiguring(optionsBuilder);
}

The EnableRetryOnFailure method is just a wrapper around ExecutionStrategy passing it SqlServerRetryingExecutionStrategy:

optionsBuilder
.ExecutionStrategy(x => new MyExecutionStrategy(x));

This one allows you to provide your own strategy for retries, by implementing IExecutionStrategy.

Configurable Change Tracking

Now, this is something that could have been really cool, but, as it is now, I find it somewhat crippled… you can now tell Entity Framework Core how should it find out if an entity has changed – the common change tracker functionality. But, the only supported techniques are the built-in (default, based on snapshots) or the use of INotifyPropertyChanged/INotifyCollectionChanged. This is not really that extensible, as you only have these two options. Here is how you configure it:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<MyEntity>()
.HasChangeTrackingStrategy(ChangeTrackingStrategy.ChangedNotifications);

base.OnModelCreating(modelBuilder);
}

If you want to use this approach, your entity must implement INotifyPropertyChanged and all of its collections must implement INotifyCollectionChanged. If any of the properties or collections in it changes, you must raise the PropertyChanged or CollectionChanged events, otherwise EF will not know that it is modified.

This can be set as the default for all entities, by the way:

modelBuilder
.HasChangeTrackingStrategy(ChangeTrackingStrategy.ChangedNotifications);

Using Fields

A most welcome addition, that was never previously available, is mapping to fields! This better supports a pure Domain Driven Design approach. It needs to be configured using code mapping:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder
.Entity<MyEntity>()
.Property(b => b.MyProp)
.HasField("_myField");

base.OnModelCreating(modelBuilder);
}

IEnumerable Collections

Another handy improvement is the ability to map collections declared as IEnumerable<T>, whereas in the past this was only possible for ICollection<T> (and derived classes, of course). The configuration is the same:

modelBuilder
.Entity<MyEntity>()
.HasMany(x => x.Children);

Of course, the concrete collection class must itself implement ICollection<T>, otherwise Entity Framework would have no way to populate it:

public class MyEntity
{
public IEnumerable<MyChild> Children { get; } = new HashSet<MyChild>();
}

Support for SQL Server In Memory Tables

In case you are using Hekaton, you can now tell Entity Framework that your entity is persisted as a memory-optimized table:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<MyEntity>()
.ForSqlServerIsMemoryOptimized();

base.OnModelCreating(modelBuilder);
}

Switching Services

Last, but not least, EF Core 1.1 makes it much easier to replace one of the services that EF uses internally:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.ReplaceService<IEntityStateListener, CustomEntityStateListener>();

base.OnConfiguring(optionsBuilder);
}

Conclusion

It’s nice to see things progressing, but there’s still a long way to go. In particular, as I said in my post about the missing features, there are quite a few features that still didn’t make it. In particular, I still miss:

  • Group By translation
  • Lazy loading
  • Date/Time operations
  • Support for custom SQL functions
  • Many-to-many relations
  • Command and query interception

We just have to wait for the next priorities of the team.

Entity Framework Core Cookbook – Second Edition

Some of you may be aware that my new book for Packt Publishing is out! It is titled Entity Framework Core Cookbook – Second Edition because it was meant to be the second edition of Entity Framework 4.1: Expert’s Cookbook. In fact, it is mostly a full rewrite.

It is organized in chapters:

Chapter 1: Improving Entity Framework in the Real World

Chapter 2: Mapping Entities

Chapter 3: Validation and Changes

Chapter 4: Transactions and Concurrency Control

Chapter 5: Querying

Chapter 6: Advanced Scenarios

Chapter 7: Performance and Scalability

Appendix: Pitfalls

When I started writing it, .NET Core was still in early RC1. Things changed a lot from RC1 to RC2 and then again to RTM, so I had to revisit all chapters in the end. It was a pity that EF Core 1.1 was released shortly after the book was closed, because I could have talked about it too. Also, there are things that I could have covered, like extending Entity Framework Core, but there were so many of them! Smile Maybe in a future time!

Those of you who are interested can get a copy from the Pack Publishing site or from other sellers, either as an e-book or in hardcopy.

I want to thank everyone at Packt Publishing, namely Chaitanya Nair, Merint Mathew and Siddhi Chavan for their professionalism and support!

What’s New in .NET Core 1.1

.NET Core 1.1 – including ASP.NET Core and Entity Framework Core – was just released at the time of the Connect(); event. With it came some interesting features and improvements.

Before you start using version 1.1 you need to make sure you install the .NET Core 1.1 SDK from https://www.microsoft.com/net/download/core. If you don’t, some stuff will not work properly.

Here are some highlights.

ASP.NET Core

In version 1.1 you can now treat View Components like Tag Helpers! Not sure why they did this, but I guess it’s OK.

You new have URL rewriting middleware that can consume the same configuration file as the IIS URL Rewrite Module.

Also new is Caching middleware, bringing what was Output Cache in ASP.NET Web Forms to Core Land.

GZip compression is also starring as a middleware component.

Middleware components can now be applied as global attributes. Seems interesting, but I don’t know how this works, because we can’t specify the ordering.

Next big thing is WebListener. It’s another HTTP server, but this time tuned for Windows. Because it this, it supports Windows authentication, port sharing, HTTPS with Server Name Indication (SNI), HTTP/2 over TLS (on Windows 10), direct file transmission, and response caching WebSockets (on Windows 8 or higher).

Temp data can now be stored in a cookie, as with MVC pre-Core.

You can now log to Azure App Service and you can also get configuration information from Azure Key Vault. Still on Azure, you can make use of Redis and Azure Storage Data Protection.

Finally, something that was also previously available is view precompilation. Now you can build your views at compile time and get all errors ahead of time.

Not all is here, though: for example, mobile views are still not available.

More on https://blogs.msdn.microsoft.com/webdev/2016/11/16/announcing-asp-net-core-1-1 and https://github.com/aspnet/home/releases/1.1.0.

Entity Framework Core

The Find method is back, allowing us to load entities by their primary keys. As a side note, I have published a workaround for this for the initial version of EF Core 1.0. Same for Reload, GetModifiedProperties and GetDatabaseValues.

Explicit loading for references and collections is also here.

Connection resiliency, aka, the ability to retry a connection, also made its move to version 1.1, similar to what it was in pre-Core.

Totally new is the support for SQL Server’s Memory Optimized Tables (Hekaton).

Now we can map to fields, not just properties! This was an often requested feature, which helps follow a Domain Driven Design approach.

Also new is the capacity to change a specific service implementation without changing the whole service provider at startup.

There are more API changes and apparently LINQ translation has improved substantially. Time will tell!

A lot is still missing from pre-Core, see some of it here.

More info here: https://blogs.msdn.microsoft.com/dotnet/2016/11/16/announcing-entity-framework-core-1-1 and here: https://github.com/aspnet/EntityFramework/releases/tag/rel%2F1.1.0.

.NET Core

First of all, .NET Core 1.1 can now be installed in more Linux distributions than before and also in MacOS 10 and in Windows Server 2016.

The dotnet CLI has a new template for .NET Core projects.

.NET Core 1.1 supports .NET Standard 1.6.

Lots of performance improvements, bug fixes and imported APIs from .NET full.

Read more about it here: https://blogs.msdn.microsoft.com/dotnet/2016/11/16/announcing-net-core-1-1/ and here: https://github.com/dotnet/core/tree/master/release-notes/1.1.

Entity Framework Pitfalls: Skip/Take Position Matters

In LINQ, you normally do paging through the Skip and Take operators. These are the LINQ counterparts to whatever the relational database uses for pagination – ROW_NUMBER(), LIMIT…OFFSET, TOP, ROWNUM, etc.

There are some gotchas to it:

  • When you do paging, Entity Framework demands that you add ordering to your query. This makes sense as without an explicit ORDER BY clause, search results may come in an unexpected order;
  • Skip and Take yield different results if added to different places in the LINQ expression.

Consider these two LINQ queries:

ctx
.MyEntities
.Where(x => x.Name == "")
.OrderBy(x => x.Id)
.Skip(10)
.Take(5)
.ToList();

And:

ctx
.MyEntities
.OrderBy(x => x.Id)
.Skip(10)
.Take(5)
.Where(x => x.Name == "")
.ToList();

The first one will produce this SQL:

SELECT TOP (5)
[Filter1].[Id] AS [Id],
[Filter1].[Name] AS [Name],
[Filter1].[Date] AS [Date]
FROM ( SELECT [Extent1].[Id] AS [Id], [Extent1].[Name] AS [Name], [Extent1].
[Date] AS [Date], row_number() OVER (ORDER BY [Extent1].[Id] ASC) AS [row_number
]
FROM [dbo].[MyEntities] AS [Extent1]
WHERE N'' = [Extent1].[Name]
) AS [Filter1]
WHERE [Filter1].[row_number] > 10
ORDER BY [Filter1].[Id] ASC

Whereas the second will produce this one:

SELECT
[Limit1].[Id] AS [Id],
[Limit1].[Name] AS [Name],
[Limit1].[Date] AS [Date]
FROM ( SELECT TOP (5) [Extent1].[Id] AS [Id], [Extent1].[Name] AS [Name], [Extent1].[Date] AS [Date]
FROM ( SELECT [Extent1].[Id] AS [Id], [Extent1].[Name] AS [Name], [Extent1].[Date] AS [Date], row_number() OVER (ORDER BY [Extent1].[Id] ASC) AS [row_number]
FROM [dbo].[MyEntities] AS [Extent1]
) AS [Extent1]
WHERE [Extent1].[row_number] > 10
ORDER BY [Extent1].[Id] ASC
) AS [Limit1]
WHERE N'' = [Limit1].[Name]
ORDER BY [Limit1].[Id] ASC

Basically, the TOP(5) expression is placed in the wrong place. So, in LINQ, it’s not just the presence of a Skip and Take operator, it’s where you place it that matters!

Entity Framework Pitfalls: Null Semantics

Imagine you have a simple class:

public class MyEntity
{
public int Id { get; set; }
public string Name { get; set; }
}

You may want to query it:

string name = ...;

var entities = ctx
.MyEntities
.Where(x => x.Name == name)
.ToList();

Entity Framework generates a query such as this:

SELECT [Extent1].[Id] AS Id, [Extent1].[Name] AS Name
FROM [dbo].[MyEntities] AS [Extent1]
WHERE ([Extent1].[Name] = @p__linq__0)
OR (([Extent1].[Name] IS NULL) AND (@p__linq__0 IS NULL))

Meaning: return all records from the MyEntities table if

  1. The Name column is identical to the name variable or
  2. Both the Name column and the name variable are NULL

Seems excessively complex, doesn’t it? Well, this is to account for the case where the name parameter is null. In  that case, a simple COLUMN = VALUE comparison won’t work, because in SQL, NULL = NULL returns FALSE! The syntax for comparing with NULL is COLUMN IS NULL. By generating this kind of query, Entity Framework is playing safe. Mind you, this only happens if you do not use the literal null, if you do, Entity Framework is smart enough to generate a simpler query:

var entities = ctx
.MyEntities
.Where(x => x.Name == null)
.ToList();

Results in:

SELECT [Extent1].[Id] AS Id, [Extent1].[Name] AS Name
FROM [dbo].[MyEntities] AS [Extent1]
WHERE ([Extent1].[Name] IS NULL)

However, there is a configuration setting, UseDatabaseNullSemantics (the opposite of the old UseCSharpNullComparisonBehavior of ObjectContext), that can be used to control this behavior. If set to true (the opposite of the default):

ctx.Configuration.UseDatabaseNullSemantics = true;

It generates all SQL queries as the unsafe version:

SELECT [Extent1].[Id] AS Id, [Extent1].[Name] AS Name
FROM [dbo].[MyEntities] AS [Extent1]
WHERE ([Extent1].[Name] = @p__linq__0)

The result is, even if you have a record whose Name column is NULL, it won’t be returned: remember that in SQL, by default, NULL <> NULL!

Therefore, if you are not 100% sure that the parameter that you will be using in the LINQ query will never be null, do not mess with UseDatabaseNullSemantics even if it generates somewhat poorer SQL.

Of course, if you are using SQL Server, you can set ANSI_NULLS to OFF, and the problem goes away; in that case, NULL = NULL.

Entity Framework Pitfalls: Queries Over Navigation Properties When There Are Foreign Key Properties Are Ignored

Long title, I know! Smile

Another one for Entity Framework Code First. The problem is: imagine you have an entity with a relation to another entity (one-to-one, many-to-one) where you have both the navigation property and the foreign key property:

public class Source
{
public int Id { get; set; }

[ForeignKey("TargetId")]
public virtual Target Target { get; set; }

public int? TargetId { get; set; }
}

public class Target
{
public int Id { get; set; }
public virtual ICollection<Source> Sources { get; set; }
}

You may want to query all Sources that have a Target set:

var sourcesWithTarget = ctx
.Sources
.Where(source => source.Target != null)
.ToList();

However, you will get this SQL instead:

SELECT [Extent1].[Id] AS [Id], [Extent1].[TargetId] AS [TargetId], 
FROM [dbo].[Source] AS [Extent1]

See? No WHERE clause!

If, instead, we query on the foreign key property:

var sourcesWithTarget = ctx
.Sources
.Where(source => source.TargetId != 0)
.ToList();

Then we get what we want:

SELECT [Extent1].[Id] AS [Id], [Extent1].[TargetId] AS [TargetId], 
FROM [dbo].[Source] AS [Extent1]
WHERE 0 <> [Extent1].[TargetId]

Weird, huh?

Stay tuned for more!

Missing Features in Entity Framework Core 1

Here’s a brief summary of some of the features that were present in previous versions of Entity Framework (6.x) and were excluded (or are not yet implemented) in Entity Framework Core 1:

Feature Description Reason/Workaround
Lazy loading The ability to load entity relations after the root entity was loaded, automatically

Will be available in a future version. For now, we need to use eager loading (Include).

https://github.com/aspnet/EntityFramework/issues/3797

Explicit loading The ability to load entity relations after the root entity was loaded, explicitly

Will be available in a future version. For now, we need to use eager loading (Include).

https://github.com/aspnet/EntityFramework/issues/3797

Support for Group By Specifying GroupBy in a LINQ query

Currently it falls back silently to LINQ to Objects, meaning, brings everything from the database and groups in memory. Will be available in a future version. For now, use plain SQL.

https://github.com/aspnet/EntityFramework/issues/2341

Support for user defined functions

Using static methods as UDFs

The ability to use .NET static methods as User Defined Functions (DbFunctionAttribute) is not supported now. Will be available in a future version.

https://github.com/aspnet/EntityFramework/issues/4319

DateTime Operations and common SQL functions Doing DateTime operations and using common SQL functions in LINQ

The ability to do queries involving Date/Time operations (DbFunctions) and common SQL functions (SqlFunctions) in LINQ queries is not supported now. Will be available in a future version.

https://github.com/aspnet/EntityFramework/issues/2850

Complex Values Support for properties of complex types (value objects)

The ability to use properties of complex types is not supported yet. Will be available in a future version.

https://github.com/aspnet/EntityFramework/issues/246

Many to Many Collections Many-to-many relations without a middle entity/table

Will be available in a future version. For now, we need a middle entity and table.

https://github.com/aspnet/EntityFramework/issues/1368

Table Per Type Inheritance Strategy

Will be available in a future version.

https://github.com/aspnet/EntityFramework/issues/2266

Table Per Concrete Type Inheritance Strategy

Will be available in a future version.

https://github.com/aspnet/EntityFramework/issues/3170

Mapping CUD with stored procedures The ability to use stored procedures for doing inserts, updates and deletes seemlesly

Will be available in a future version.

https://github.com/aspnet/EntityFramework/issues/245

Map database views The ability to map views instead of tables

Will be available in a future version.

https://github.com/aspnet/EntityFramework/issues/1679

https://github.com/aspnet/EntityFramework/issues/827

Spatial data types The ability to query and use spatial data types

Will be available in a future version.

https://github.com/aspnet/EntityFramework/issues/1100

Custom conventions The ability to add custom conventions

Will be available in a future version.

https://github.com/aspnet/EntityFramework/issues/214

Populate non-model types from SQL Turn the results of custom SQL into classes that are not part of the model

Will be available in a future version.

https://github.com/aspnet/EntityFramework/issues/240

Connection resiliency support The ability to retry connecting and sending queries

Will be available in a future version.

https://github.com/aspnet/EntityFramework/issues/237

Seeding data in migrations The ability to add data when migrating

Will be available in a future version.

https://github.com/aspnet/EntityFramework/issues/629

Command and query interception The ability to intercept queries and SQL commands

Will be available in a future version.

https://github.com/aspnet/EntityFramework/issues/626

https://github.com/aspnet/EntityFramework/issues/4048

https://github.com/aspnet/EntityFramework/issues/737

Visual Studio support for generating/updating entities from the database and viewing the model The ability to generate the model from the database from inside Visual Studio and to view the model graphically

Will be available in a future version.

https://github.com/aspnet/EntityFramework/issues/1186

Database initializers Database initializers Dropped.
Automatic migrations Automatic migrations Dropped.
Pluralization Service Pluralization Service

Dropped.

https://github.com/aspnet/EntityFramework/issues/2506

ObjectContext events SavingChanges and ObjectMaterialized events of ObjectContext

Dropped.

https://github.com/aspnet/EntityFramework/issues/3204

ObjectContext (Entity SQL) Entity SQL Dropped.
Model first approach Model first approach Dropped.
Data Annotations validations The ability to perform data annotations validations before saving changes Dropped.

Please let me know if you think I missed something! Winking smile

For the most up to date roadmap, please consult the EF Core roadmap: https://github.com/aspnet/EntityFramework/wiki/Roadmap.

Also, do check out the Entity Framework issue tracker: https://github.com/aspnet/EntityFramework/issues

Implementing Missing Features in Entity Framework Core – Part 5: Getting the SQL for a Query

This will be the fifth post in a series of posts about bringing the features that were present in Entity Framework pre-Core into EF Core. The others are:

  • Part 1: Introduction, Find, Getting an Entity’s Id Programmatically, Reload, Local, Evict

  • Part 2: Explicit Loading

  • Part 3: Validations

  • Part 4: Conventions

This time I’m going to talk about something that is often requested: how can I get the SQL string for a LINQ query? If you remember, in the pre-Core days you had to do some reflection in order to get the underlying ObjectQuery and then call its ToTraceString method. Now, things are very different, although I may say, still rather tricky!

So, this is all we need:

private static readonly FieldInfo QueryCompilerField = typeof(EntityQueryProvider).GetTypeInfo().DeclaredFields.First(x => x.Name == "_queryCompiler");
private static readonly PropertyInfo NodeTypeProviderField = QueryCompilerTypeInfo.DeclaredProperties.Single(x => x.Name == "NodeTypeProvider");
private static readonly MethodInfo CreateQueryParserMethod = QueryCompilerTypeInfo.DeclaredMethods.First(x => x.Name == "CreateQueryParser");
private static readonly FieldInfo DataBaseField = QueryCompilerTypeInfo.DeclaredFields.Single(x => x.Name == "_database");
private static readonly FieldInfo QueryCompilationContextFactoryField = typeof(Database).GetTypeInfo().DeclaredFields.Single(x => x.Name == "_queryCompilationContextFactory");

public static string ToSql<TEntity>(this IQueryable<TEntity> query) where TEntity : class
{
if (!(query is EntityQueryable<TEntity>) && !(query is InternalDbSet<TEntity>))
{
throw new ArgumentException("Invalid query");
}

var queryCompiler = (IQueryCompiler) QueryCompilerField.GetValue(query.Provider);
var nodeTypeProvider = (INodeTypeProvider) NodeTypeProviderField.GetValue(queryCompiler);
var parser = (IQueryParser) CreateQueryParserMethod.Invoke(queryCompiler, new object[] { nodeTypeProvider });
var queryModel = parser.GetParsedQuery(query.Expression);
var database = DataBaseField.GetValue(queryCompiler);
var queryCompilationContextFactory = (IQueryCompilationContextFactory) QueryCompilationContextFactoryField.GetValue(database);
var queryCompilationContext = queryCompilationContextFactory.Create(false);
var modelVisitor = (RelationalQueryModelVisitor) queryCompilationContext.CreateQueryModelVisitor();
modelVisitor.CreateQueryExecutor<TEntity>(queryModel);
var sql = modelVisitor.Queries.First().ToString();

return sql;
}

You can see that it needs some reflection, meaning, things *may* break on a future version. I cached all of the fields to make the access faster in subsequent calls. For the time being, it works perfectly:

var sql1 = ctx.Blogs.ToSql();

var sql2 = ctx
.Blogs
.Where(b => b.CreationDate.Year == 2016)
.ToSql();

Hope this is useful to you! Winking smile

Implementing Missing Features in Entity Framework Core – Part 4: Conventions

Conventions

This will be the fourth in a series of posts about bringing the features that were present in Entity Framework pre-Core into EF Core. The others are:

  • Part 1: Introduction, Find, Getting an Entity’s Id Programmatically, Reload, Local, Evict
  • Part 2: Explicit Loading
  • Part 3: Validations

Conventions are a mechanism by which we do not have to configure specific aspects of our mappings over and over again. We just accept how they will be configured, of course, we can override them if we need, but in most cases, we’re safe.

In EF 6.x we had a number of built-in conventions (which we could remove) but we also had the ability to add our own. In Entity Framework Core 1, this capability hasn’t been implemented, or, rather, it is there, but hidden under the surface.

A lot has changed. As of Entity Framework Core, conventions are specific to the provider in use, which makes perfect sense. But then we have several kinds of conventions. Just to give you an idea, the ConventionSet class exposes 15 convention collections! This is the place where we can register our own conventions, but there are two problems:

  • The current ConventionSet instance is not publicly accessible, so we need to use reflection to get hold of it;
  • We don’t want to add a new “blank” instance of ConventionSet because this way we would lose all of the conventions that would have been injected by the provider.

So, the solution I came up with had to use reflection to get the current convention set and allow adding new conventions, which is not ideal. Let’s see the code.

We need an extension method to get hold of the ConventionSet from the ModelBuilder and allow adding a convention, in the form of a IModelConvention:

public static class ModelBuilderExtensions

{

    public static ModelBuilder AddConvention(this ModelBuilder modelBuilder, IModelConvention convention)

    {

        var imb = modelBuilder.GetInfrastructure();

        var cd = imb.Metadata.ConventionDispatcher;

        var cs = cd.GetType().GetField("_conventionSet", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(cd) as ConventionSet;

 

        cs.ModelBuiltConventions.Add(convention);

 

        return modelBuilder;

    }

 

    public static ModelBuilder AddConvention<TConvention>(this ModelBuilder modelBuilder) where TConvention : IModelConvention, new()

    {

        return modelBuilder.AddConvention(new TConvention());

    }

}

Feel free to cache the _conventionSet field somewhere, but that is not the point of this post.

You can see that we are adding the new convention to the ModelBuiltConventions collection, basically, because this is the only collection that Entity Framework Core will go through during the OnModelCreating method, all of the other collections will have been processed before it. We’re cool with that.

Let’s see two examples of sample conventions, first, one to set the maximum length of strings, where it hasn’t been explicitly set:

public sealed class DefaultStringLengthConvention : IModelConvention

{

    internal const int DefaultStringLength = 50;

    internal const string MaxLengthAnnotation = "MaxLength";

 

    private readonly int _defaultStringLength;

 

    public DefaultStringLengthConvention(int defaultStringLength = DefaultStringLength)

    {

        this._defaultStringLength = defaultStringLength;

    }

 

    public InternalModelBuilder Apply(InternalModelBuilder modelBuilder)

    {

        foreach (var entity in modelBuilder.Metadata.GetEntityTypes())

        {

            foreach (var property in entity.GetProperties())

            {

                if (property.ClrType == typeof(string))

                {

                    if (property.FindAnnotation(MaxLengthAnnotation) == null)

                    {

                        property.AddAnnotation(MaxLengthAnnotation, this._defaultStringLength);

                    }

                }

            }

        }

 

        return modelBuilder;

    }

}

Easy, easy, hey? We go through all of the mapped entities, then through all of their properties of type string, check if the maximum length annotation is present, and, if not, add a new one. Only one method, with access to all mapped entities.

Another example, turning off the table pluralization. As you know, as of EF RC2, table names of DbSet properties exposed in the DbContext are pluralized. We can get rid of this behavior if we explicitly set the name to something, in this case, it will be the entity type name:

public sealed class SingularizeTableNameConvention : IModelConvention

{

    public InternalModelBuilder Apply(InternalModelBuilder modelBuilder)

    {

        foreach (var entity in modelBuilder.Metadata.GetEntityTypes())

        {

            if (entity.FindAnnotation(RelationalAnnotationNames.TableName) == null)

            {

                entity.Relational().TableName = entity.Name.Split('.').Last();

            }

        }

 

        return modelBuilder;

    }

}

Now, some new extension methods come handy:

public static ModelBuilder UseDefaultStringLength(this ModelBuilder modelBuilder, int defaultStringLength = DefaultStringLengthConvention.DefaultStringLength)

{

    modelBuilder.AddConvention(new DefaultStringLengthConvention(defaultStringLength));

 

    return modelBuilder;

}

 

public static ModelBuilder UseSingularTableNames(this ModelBuilder modelBuilder)

{

    modelBuilder.AddConvention<SingularizeTableNameConvention>();

 

    return modelBuilder;

}

And that’s it! The way to apply these conventions (one or both) is during OnModelCreating:

protected override void OnModelCreating(ModelBuilder modelBuilder)

{

    modelBuilder

        .UseDefaultStringLength()

        .UseSingularTableNames();

 

    base.OnModelCreating(modelBuilder);

}

You can now reuse these conventions in all of your context classes very easily. Hope you enjoy it! Two final remarks about this solution:

  • The reflection bit is a problem, because things may change in the future. Hopefully Microsoft will give us a workaround;
  • There is no easy way to remove built-in (or provider-injected) conventions, because are applied before OnModelCreating, but I think this is not a big problem, since we can change them afterwards, as we’ve seen.

Stay tuned for more!