What’s New and Changed in Entity Framework Core 2
Introduction
By now you should know that EF Core 2 was released on August 14th. It brought something new and some breaking changes too. Alas, it still does not include some of the features that used to be in pre-Core editions and are in high demand, such as lazy loading and support for group by translation. See the list here.
.NET Standard 2.0
Entity Framework Core 2 now targets .NET Standard 2.0, which was also released just now. This means that it will be useful in other scenarios, on any platform that supports it.
Improved SQL Generation
Improvements include:
- Unneeded nested sub-queries are not created
- Select only requested columns (projections)
- No more creating multiple SQL queries for a single LINQ query
Owned Entities
Complex types are back, and they are now called owned entities. Remember that a difference between a complex type and an entity is that the former does not have an identity. Think, for example, of an Address class and several properties, Personal, Work, etc; all of these properties can be mapped to this class, and they will be stored in the same table as the containing entity. It looks like this:
modelBuilder
.Entity<Customer>()
.OwnsOne(c => c.PersonalAddress);
You can also put the content for these properties in another table, and you do it like this:
modelBuilder
.Entity<Customer>()
.OwnsOne(c => c.PersonalAddress)
.ToTable(“CustomerAddress”);
Of course, you can “own” multiple properties at once. Not possible to declare owned entities through attributes at this time.
Table Splitting
You can now have different classes that point to the same physical table, Entity Framework Core will not complain. These classes will probably expose different properties.
Entity State Listener
There’s a new interface that is registered by default, ILocalViewListener, that can be used to track entity changes – not materialized entities, unfortunately:
var events = ctx.GetService<ILocalViewListener>();
events.RegisterView((entry, state) =>
{
//entry contains the entity and state its current state
});
In case you are wondering, you cannot use this to cancel changes, because it is only called after the actual event took place.
Pluralization
There is a new IPluralizer interface and a dummy implementation NullPluralizer. It can be used to pluralize table names when EF is generating the database (dotnet ef database update) or entities when generating classes from it (Scaffold-DbContext). The way to use it is somewhat tricky, as we need to have a class implementing IDesignTimeServices, and this class will be discovered automatically by these tools:
public class CustomPluralizerDesignTimeServices : IDesignTimeServices
{
public void ConfigureDesignTimeServices(IServiceCollection services)
{
services.AddSingleton<IPluralizer, CustomPluralizer>();
}
}
public class CustomPluralizer : IPluralizer
{
public string Pluralize(string name)
{
return ...;
}
public string Singularize(string name)
{
return ...;
}
}
Because the tools also rely on the dependency injection framework, we are providing an alternative implementation of the IPluralizer interface through it.
DbContext Pools
Normally when a DbContext is injected somewhere by the dependency injection framework, a new instance is created every time. With this, we can have a pool of instances, 128 by default. It is a performance improvement and it is configured like this:
services.AddDbContextPool<DataContext>(options =>
{
//...
}, poolSize: 256);
Attach
The Attach method, for attaching existing entities, is now more clever: if any of the entities in the graph being attached has its key set, it will be treated as unchanged, and if not, as new.
Entity Type Configuration
We can now store entity configuration in separate classes, similar to what we used to have:
public class MyEntityTypeConfiguration : IEntityTypeConfiguration<MyEntity>
{
public void Configure(EntityTypeBuilder<MyEntity> builder)
{
//...
}
}
only these classes are not discovered automatically any more:
modelBuilder.ApplyConfiguration(new MyEntityTypeConfiguration());
Global Filters
Global filters already existed, for entities, not collections, in pre-Core EF. They are useful in essentially two scenarios:
- For multitenant apps
- For soft deletes
Its configuration goes like this for soft deletes:
modelBuilder
.Entity<Post>()
.HasQueryFilter(p => !p.IsDeleted);
Or, for a multi-tenant:
modelBuilder
.Entity<Blog>()
.HasQueryFilter(p => p.TenantId == this.TenantId);
It will apply filtering to any entities loaded as the result of a query (including eager loads) or from a one-to-many collection, but it will not filter a query by id, a one-to-one or many-to-one.
You can explicitly disable any existing filters by calling the IgnoreQueryFilters extension method:
ctx
.Blog
.IgnoreQueryFilters()
.ToList();
Disabling Client-Side Evaluation
You may be aware that EF Core can do client-side evaluation of methods that it does not know about, that is, cannot be translated to database calls. This happens transparently and may turn into a performance issue. Should you need to disable this, you now can by configuring the logging infrastructure to throw an exception when client evaluation occurs:
var builder = new DbContextOptionsBuilder<DataContext>()
.ConfigureWarnings(options =>
{
options.Throw(RelationalEventId.QueryClientEvaluationWarning);
options.Default(WarningBehavior.Log);
});
Like
We now have support for SQL’s LIKE function, although in the past we also supported something similar, through the String.Contains method. It goes like this:
ctx
.Posts
.Where(x => EF.Functions.Like(x.Title, “%NET%”)
.ToList();
Unfortunately, Microsoft didn’t make Like an extension method, which I think would be easier to use.
Calling Scalar Functions
Support for calling scalar functions is back too, with some minor caveats:
- These functions need to be static and declared on the context class
- They can only return and take as parameters scalar values
An example, first, the declaration, using the T-SQL built-in SOUNDEX function, through the [DbFunction] attribute:
[DbFunction]
public static string Soundex(string name)
{
throw new NotImplementedException();
}
Or by code:
modelBuilder.HasDbFunction(this.GetType().GetMethod(“Soundex”));
In either case you can specify both the schema or the function’s name, if it is different from the method to be used:
[DbFunction("FuncName", Schema = "dbo")]
modelBuilder.HasDbFunction(this.GetType().GetMethod("FuncName"), options =>
{
options.HasName("FuncName");
options.HasSchema("dbo");
});
And its usage:
var sounds = ctx
.MyEntity
.Select(x => x.Soundex(x.Name))
.ToList();
String Interpolation Support
Now, the FromSql and ExecuteSqlCommand methods support interpolated strings, and will happily produce parameters as needed. You do not have to worry about those nasty SQL injection attacks and performance issues due to query plan creation! It goes like this:
ctx
.Database
.ExecuteSqlCommand($"UPDATE Record SET Value = {value} WHERE Id = {id}");
Explicitly Compiled Queries
Entity Framework Core included query caching since version 1, but there is still some overhead associated with calculating the key from the query and getting it from the cache. Therefore, version 2 introduced a capability that existed in LINQ to SQL and Entity Framework pre-Core: explicit query compilation and execution. By this, we are able to pre-compile a query and use it in whatever context we want (of a compatible type, of course). We can even eagerly fetch associated collections or entities:
static readonly Func<MyEntityContext, int, IEnumerable<MyEntity>>
CompiledQuery = EF.CompileQuery<MyEntityContext, int, MyEntity>((ctx, id) =>
ctx.MyEntities.Where(x => x.Id == id).Include(x => x.Others).OrderBy(x => x.Name));
As you can see, it returns a delegate that we can invoke passing it the proper parameters – in this example, a context and a parameter, but you can have up to 8 parameters, of different types:
var results = CompiledQuery(ctx, 100).ToList();
Breaking Changes
The IDbContextFacfory<T> interface was replaced by IDesignTimeDbContextFactory<T>. The signature of the CreateDbContext method changed also:
public class DummyContextFactory : IDesignTimeDbContextFactory<DummyContext>
{
public DummyContext CreateDbContext(string[] args)
{
var builder = new DbContextOptionsBuilder<DummyContext>();
builder.UseSqlServer("…");
return new DummyContext(builder.Options);
}
}
UseInMemoryDatabase now needs a name:
optionsBuilder.UseInMemoryDatabase("MyDatabase")
Package Microsoft.EntityFrameworkCore.SqlServer.Design is deprecated in favor of Microsoft.EntityFrameworkCore.Design (now provider-agnostic).
Only 2.0 providers will work, so any existing providers that target EF Core 1.x will need to be rewritten.
Logging event IDs have changed from the previous version and they are now identical to those used by corresponding ILogger messages. The logger categories now come from subclasses of DbLoggerCategory, such as DbLoggerCategory.Database.Command, DbLoggerCategory.Migrations, DbLoggerCategory.Infrastructure, etc, all of which offer a Name property.
What’s Still Not Here
Still missing are (not a complete list, mind you):
- Non-relational providers
- Lazy loading (https://github.com/aspnet/EntityFramework/issues/3797)
- Using stored procedures for CUD operations (https://github.com/aspnet/EntityFramework/issues/245)
- Date and time, spatial types-related operations (https://github.com/aspnet/EntityFramework/issues/2850, https://github.com/aspnet/EntityFrameworkCore/issues/6025, https://github.com/aspnet/EntityFramework/issues/1100)
- Many to many collections (https://github.com/aspnet/EntityFramework/issues/1368)
You can find a more thorough list here: https://weblogs.asp.net/ricardoperes/missing-features-in-entity-framework-core.
Conclusion
Still a long way to go; especially, GroupBy translation, many to many and lazy loading seems to be taking forever, both are scheduled for the next version (2.1) though. Non-relational providers are also nowhere to be seen. This new version has interesting new stuff and Microsoft seems to be going in the right direction, but it strikes me as odd that such high demand features are still absent. Let’s see how things evolve.
References
https://blogs.msdn.microsoft.com/dotnet/2017/08/14/announcing-entity-framework-core-2-0
https://docs.microsoft.com/en-us/ef/core/what-is-new
https://github.com/aspnet/EntityFrameworkCore/wiki/Roadmap
https://github.com/aspnet/EntityFrameworkCore/issues?q=is%3Aopen+is%3Aissue+milestone%3A2.0.0
https://weblogs.asp.net/ricardoperes/missing-features-in-entity-framework-core