Hot on the tail of EF Core 3 is EF Core 5. Remember when we skipped from EF v1 to EF4 to align with .NET Framework 4.0? Like that time, this new gap in version numbers was designed to align with the successor to .NET Core 3. Except that successor is no longer called .NET Core, it's .NET 5. Yet EF Core 5 is keeping the “Core” (as is ASP.NET Core 5). This allows us to continue differentiating from the pre-Core versions of EF and ASP.NET. So, EF Core 5 it is. Happily for me, this article is about EF Core 5 and I'm not charged with further explaining the name and other changes to .NET 5, details you can read about in another article in this issue.
When EF Core 5 was still a few months from release, Arthur Vickers (from the EF team) tweeted some impressive stats about the 637 GitHub issues already closed for the EF Core 5 release:
- 299 bugs fixed
- 113 docs and cleanup
- 225 features and minor enhancements
Building on the EF Core 3 Foundation
You may have read my article introducing CODE Magazine readers to EF Core 3, entitled Entity Framework Core 3.0, A Foundation for the Future. Because the first iteration of EF Core was a complete rewrite of Entity Framework, it had been a very “v1” version. EF Core 2 goals were around tightening down the existing code base and adding critical features, making EF Core truly production ready. With that stable version in place (and widely used), the team felt that with EF Core 3, it was safe to make the kind of low-level changes that would result in breaking changes. There weren't many new features in EF Core 3, so teams could continue to use EF Core 2 if they wanted while EF Core 3 prepared the framework for a long future.
With EF Core 5.0, we're now witnessing that future. I want to assure you that the great number of breaking changes in EF Core 3.0 were really a one-time event. Some of you may be worried that this will happen again, but the EF Core team is now focused on building on that foundation and minimizing future breaking changes. EF Core 5 doesn't have very many breaking changes, and they're clearly laid out in the Breaking Changes page in the docs. Today (as EF Core 5 is in Preview 8) there is only one listed and it's very much an edge case related to SQLite and Geometric Dimensions...something I've never even heard of. (https://docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-5.0/breaking-changes)
Unlike EF Core 3, which had only seven new features, EF Core 5 has many new features along with improvements to existing features and the usual bug fixes. There's no way to cover all of the new goodies in this article, so be sure to look at the docs to get a great overview. At the time of writing this article, there have been eight previews and you'll find a list of what's new for each of the different previews at https://docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-5.0/whatsnew. I'll pick some that I find interesting and/or important and drill into them in this article.
More importantly, much of the groundwork laid in EF Core 3 has enabled some great additions to EF Core 5.0, including the return of features from EF6. I think the most notable of these is support for many-to-many relationships that doesn't require us to tangle with classes that represent the join ? the solution we've been stuck with since EF Core first arrived.
And the reason many-to-many took so long to appear is a nice example of that EF Core 3 groundwork. The original many-to-many support was tied to the way Entity Framework was designed back in 2006. Although it was magical indeed in the way it inferred and persisted the join data, the source of that magic put a lot of limitations on what we developers could do with the data in that relationship. When building EF Core v1, the team didn't want to duplicate that limiting solution. However, re-thinking and re-devising many-to-many was going to be a lot of work that would have held up the release. In the meantime, we could at least leverage the workaround of using the explicit join entity. But now, with those necessary changes in place, the EF team was able to build a smarter, more flexible, and non-magical many-to-many feature in EF Core 5.
Platform Support for EF Core 5
EF Core 5 can be used with any platform that supports .NET Standard 2.1. This includes .NET Core 3.1 and .NET 5. But it won't run on .NET Standard 2.0, which means that starting with EF Core 5.0, you can no longer use EF Core with .NET Framework. You can still use .NET Framework with EF Core 1, 2, and 3.
Backward Compatible with EF Core 3
With the exception of the few breaking changes, you should find EF Core 5 to be backward compatible with your EF Core 3 code. I tested this out with various solutions of my own (e.g., the demos from the EF Core 3 article and some from my Pluralsight course, Getting Started with EF Core 3.1) and everything continued to run, with all tests passing. The icing on that compatibility cake however, is the opportunity to improve that code with new EF5 features.
Documentation and Community
There are a few other important themes to be aware of with EF Core 5. The team is focused on major improvements to the documentation. You might have noticed that earlier this year, the docs were reorganized. The home page for EF docs (Figure 1) at docs.microsoft.com/ef, has guided paths to more easily find the type of information you're seeking.
In addition to the already well documented features, they've been adding guidance and best practices. For example, there are now Platform Experience documents that provide guidance for using EF Core on different platforms. Some examples are Getting Started with WPF and EF Core, Getting Started with Xamarin and EF Core and guidance for Blazor and EF Core.
The EF team has also begun to have bi-weekly live community standups online. In a time when we're not able to travel and convene, it's wonderful to be able to not only visit with the team members (and some of their cats) but also to get such great insight into the work they and other members of the community are doing. These happen on YouTube with past and upcoming standups listed on this playlist: http://bit.ly/EFCoreStandups.
Another exciting aspect of EF Core 5 is the number of pull requests (PRs) that have come from the community and are now a part of EF Core. Although some of the PRs are very close to ready-for-primetime when submitted to the GitHub repository, the team has also worked closely with developers to help them spruce up PRs that weren't quite ready to be merged. What I truly love about this is how much attention and recognition the EF Core team has paid to these contributors. The weekly status updates that the team shares on GitHub https://github.com/dotnet/efcore/issues/19549 include a list of the pull requests. For example, you can see PRs from six developers in the community listed in the August 6, 2020 update. Even cooler is to see the long list of contributors including their photos and links to their GitHub accounts at the bottom of the announcement post for the first preview of EF Core 5.
The team is also excited about the community involvement. Speaking with Arthur Vickers, I could hear the joy in his voice as we talked about it. He tells me:
“In 2012, the EF team became one of the trailblazers for .NET open source at Microsoft. Since then, it's just exploded! It's been very satisfying to me personally to see so many diverse people working together to build better products, a stronger .NET community, and ultimately a rich and open ecosystem. It's a fantastic time to be working on .NET!” —Arthur Vickers
New and Improved Many-to-Many Support
As I mentioned earlier, and you may be quite familiar with, proper many-to-many support has existed since the outset of Entity Framework. What I mean by “proper” is where you aren't required to have a join entity in your code and EF infers the connection between the two ends. And as I explained above, it wasn't implemented in EF Core, EF Core 2, or EF Core 3, which left us tangling with a join entity. This was the most requested feature, as per the “thumbs up” on the relevant GitHub issue.
EF Core 5 now has true many-to-many support so that you can write your classes in a natural way and EF Core will understand the mapping when it comes to creating and inferring the database schema, building SQL for queries and updates, and materializing results.
This is, to me, one of the more fascinating features, so I'll dig into this more deeply than any other feature in this article.
Here's an example of the natural way, in a relationship where people could reside or otherwise be tied to a variety of addresses, and an address might have a number of residents. Each Person has a list of Addresses and Address has a list of person types, called Residents.
public class Person
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public List<Address> Addresses { get; set; }
}
public class Address
{
public int Id { get; set; }
public string Street { get; set; }
public string PostalCode { get; set; }
public List<Person> Residents { get; set; }
}
The minimal requirement for EF Core 5 to recognize the relationship is that you must make the DbContext aware of one of the ends (e.g., create a DbSet for either entity). EF Core 5 can then infer the relationship in the model. For a simple, conventional many-to-many, there's no more configuration or effort needed. I've chosen to create DbSets for both types.
public DbSet<Person> People { get; set; }
public DbSet<Address> Addresses { get; set;}
When I add a migration to create the database, EF Core 5 infers a join table between People and Addresses in the database. The migration has CreateTable
methods for Addresses
, People
, and AddressPerson
. In the database created based on that migration (Figure 2), you can see that the join table has two columns, AddressesId
and ResidentsId
. EF Core picked up the name of the Residents
property when choosing the join table schema. The primary key is composed from both columns and each column is also a foreign key back to one of the other tables.
This mapping is the same as what we got by convention in earlier versions of Entity Framework based on the same set up.
A serious limitation about many-to-many support in EF6, however, is that there is no way to have additional columns in that join table. For example, the date on which a person took up residency at an address. In that case, you would have to create an explicit entity to map to the join table and manually manage every detail about the relationship in your code ? just as we've had to do in EF Core. If you already had production code with the simpler many-to-many relationship, this also meant breaking a lot of code. This problem was a result of the house of cards (my words) on which many-to-many was originally built. The details are fairly interesting (to some of us) but I won't be delving into those internals in this article.
There are, however, two pieces of the EF Core 5 many-to-many support that you should know about. One is called skip navigations, which allows you to skip over the inferred join entity between Address and Person when writing your code. The other is property-bag entity types. These are new to EF Core 5 and are a type of dictionary. Property bag entities are used to solve a number of mapping quandaries, not just many-to-many. But for many-to-many, they allow you to store additional data into the join table while still benefiting from the skip navigation. The ability to have extra data in the join table is a very big difference from EF6 and, I hope you will agree, is one of the reasons that many-to-many support in EF Core was worth the wait.
The ability to have extra data in the join table is a very big difference from EF and I hope you will agree is one of the reasons that many-to-many support was worth the wait.
When building up objects or querying, you don't have to make any reference to the join ? just work with the direct navigation properties and EF Core will figure out how to persist and query. For example, here, I'm creating a new person and adding a few addresses:
var person = new Person
{
FirstName="Jeremy",
LastName="Likness"
};
person.Addresses.Add(new Address { Street="999 Main" });
person.Addresses.Add(new Address { Street="1000 Main" });
Then, I add the person to a context instance and save to the database:
context.People.Add(person);
context.SaveChanges();
EF Core first inserts the person and the addresses, then grabs the database-generated IDs from those new rows. Then it uses those values to insert the two relevant rows to the PersonAddress join table. By the way, I used the new streamlined logging capability in EF Core 5 to see the SQL. I'll show you how that works after I finish digging into many-to-many.
Also notable is how EF Core batches commands when performing these inserts. I found that if you reach the batch command threshold (at least four commands for SQL Server) when inserting or updating the ends (person and address), the person and address inserts are batched together. The join table inserts will be batched in their own command if there are at least four of those.
When querying from either end, there's no need to acknowledge the join thanks to skip navigations. You can eager load Addresses for People and eager load Residents (i.e., Person types) for Addresses, and EF Core will work out the correct queries and materialize the results.
context.People.Include(p => p.Addresses)
Projection queries and explicit loading also take advantage of the many-to-many capability.
In the end, the bottom line for simple scenarios is that many-to-many works just like it did in EF6.
The bottom line for simple scenarios is that many-to-many works just like it did in EF6.
Many-To-Many with Additional Columns
As mentioned earlier, the new property-bag entity type enables EF Core 5 to store additional data in the join table. And it avoids the detrimental side effect of trying to achieve that in EF6. In EF6, if you already had a many-to-many relationship and then decided to track additional data in the join table, it meant abandoning the many-to-many support and creating an explicit join entity (that maps directly to the join table) with a one-to-many relationship between that entity and each of the ends. This also meant the painstaking effort of navigating through that entity for every query and object interaction that involved the relationship. This is what carried through to EF Core as the only way to support many-to-many.
As an example, let's keep track of the date on which the person was associated with a particular address. Even though I have already implemented the simpler many-to-many, the existing code and mappings will continue to work. EF Core 5 lets you do this without impacting the existing relationship or breaking a lot of code.
With EF Core 5, you do need to create an explicit entity that maps to the join table, because you need somewhere to capture that additional data (referred to as a payload). I'll do this with a class called Resident and add a LocatedDate
property to track the new detail.
public class Resident
{
public int PersonId { get; set; }
public int AddressId { get; set; }
public DateTime LocatedDate { get; private set; }
}
Unlike audit data, which you can configure using EF Core's shadow properties with no need to expose in your business logic, you'll be able to access this LocatedDate easily in your code.
I'll configure the mapping in DbContext.OnModelCreating
.
modelBuilder.Entity<Person>()
.HasMany(p => p.Addresses).
WithMany(a => a.Residents)
.UsingEntity<Resident>(
r => r.HasOne<Address>().WithMany(),
r => r.HasOne<Person>().WithMany())
.Property(r => r.LocatedDate)
.HasDefaultValueSql("getdate()");
This mapping looked confusing to me at first but after applying it a few times, it now makes sense. Let me break it down for you so that you don't even need to wait until your second pass.
I start with the Person end, Entity<Person>
, which has many addresses via its Addresses
property. Then using WithMany
, I explain that Addresses
has many Residents (e.g., its List<Person>
property). So far, this is an explicit way to map what EF is already determining through convention. And in fact, there is even a way to configure the many-to-many without even coding the navigations in the classes. Next comes the “how to do it” mapping: UsingEntity
. This uses the join entity, Resident
, and then specifies that every resident connects one Address
and one Person
. Each of those might have many resident properties, which is what the WithMany
method represents. There's no need to specify the resident type in the WithMany
method; EF Core will be able to infer that by convention.
Finally, I'm using a familiar mapping to specify that the database column that maps to the Resident.LocatedDate
property should have a default value of the current date, using SQL Server's getdate()
function.
There's one more critical change you must make in the mappings. By convention, EF Core originally named the join table AddressPerson
. With the new Resident
entity, EF Core convention will want that table to be named Residents. This causes the migration to drop the AddressPerson
table and recreate it, causing you to lose data. To prevent this, I've included a mapping to ensure that the new Resident entity still maps to that AddressPerson
table.
modelBuilder.Entity<Resident>().ToTable("AddressPerson");
Do keep in mind that there are a lot of other ways you can configure the mappings for the many-to-many relationship. That's part of the beauty of this re-imagined many-to-many in EF Core 5.
When I add a new migration, EF Core creates logic to enhance the AddressPerson
join table, modifying the column names and fixing up the names of foreign keys and indexes. Like the table name, you do have agency over these names using the fluent API, but I'll just let it use convention and make the changes. After calling update-database, Figure 3 shows the schema of the modified table.
Now, when I create any new relationship between a Person and an Address, the new row in the join table will have the LocatedDate populated by SQL Server.
I can also query the Residents
with a DbContext.Set<Resident>
if I want to read that value. There are more ways to enhance the many-to-many relationship as well that you can learn about from the docs or perhaps a future article here in CODE Magazine. Let's look at some other EF Core 5 features now.
Simplified Logging Returns with More Smarts
Logging in Entity Framework and EF Core has had a long and lurid history. Well, not really lurid, but it sounded good, right? It was rough to get at the SQL created by EF in early days but then we got a super simple Database.Log
method in EF6. This didn't make the transition to EF Core but instead, EF Core tied directly into the logging framework in .NET Core. This was a big advance. The pattern to tap into EF Core's logs was to configure the DbContext options to use a LoggingFactory. In ASP.NET, this is fairly simple because the LoggingFactory is pre-defined. In other apps, there's a bit more ceremony.
Now in EF Core 5, the team has merged the intelligence of the .NET Core logging framework and the simplicity of telling EF Core to “just do it.” In fact, you don't even need to know that there's a LoggingFactory
involved. Witness the loveliness of this code. In a console app where I'm explicitly specifying the optionsBuilder
settings in OnConfiguring
, I need only to append a LogTo
method, specify the target as a delegate, e.g., Console.Writeline
(notice it's not a method), and then specify how much detail to provide with a LogLevel
enum.
optionsBuilder.UseSqlServer(myConnectionString).LogTo(Console.WriteLine, LogLevel.Information);
This syntax is even simpler than EF6 (which required some additional settings in a config file) and much more flexible.
If you are working in ASP.NET Core, you can continue to take advantage of the built-in logging while filtering EF Core's output in the appsettings.json
file.
Finally, Filtered Include!
Here's a treat we've been asking for since the beginning of time, well, of EF time: the ability to filter when eager loading with the Include
method. The way it's always worked is that if you use Include to pull in related objects, it's an all or nothing grab. Include had no way to filter or sort. To get around that, we could instead use the Select
method to create a projection query. Explicit (after the fact) loading also allows filtering and sorting.
Thanks again to some of the underlying capabilities recently brought into EF Core, EF Core 5 finally lets you use Where
and other LINQ methods on the related properties specified in the Include
method.
I've enhanced my domain to include wildlife sightings at each address and it's been a very busy morning at my house with bears, cubs, snakes, and squirrels all running about!
Here is a simple query with Include without the new capability:
context.Addresses.Include(a => a.WildlifeSightings).FirstOrDefault();
This returns every sighting for the first address:
8/15/2020 10:27:31 AM: Bear
8/15/2020 10:27:31 AM: Bear Cub #1
8/15/2020 10:27:31 AM: Bear Cub #2
8/15/2020 10:27:31 AM: Bear Cub #3
8/15/2020 10:27:31 AM: Squirrel
8/15/2020 10:27:31 AM: Garter Snake
Now you can use LINQ methods inside the Include
method. To filter to return only the bear sightings, I'll add the Where
method to the related WildlifeSightings
property.
context.Addresses.Include(a => a.WildlifeSightings.Where(w=>w.Description.Contains("Bear"))).FirstOrDefault();
If you follow me on Twitter, you may know that there has indeed been a mother bear with three cubs living in the woods in my neighborhood this past summer. I saw the momma from a distance one day, but I've not been lucky enough to see the cubs. Back to EF Core 5, which generated the correct SQL to filter on only the bear sightings. The SQL Statement is a single query and has added a filter using LEFT JOIN
to access the related data. Here are the results ? the squirrel and snake are gone.
8/15/2020 10:27:31 AM: Bear
8/15/2020 10:27:31 AM: Bear Cub #1
8/15/2020 10:27:31 AM: Bear Cub #2
8/15/2020 10:27:31 AM: Bear Cub #3
To filter and sort, it's just some more, familiar LINQ inside the Include
method. Let's check out anything but bears now and sort them by name.
context.Addresses.Include(a => a.WildlifeSightings.OrderBy(w=>w.Description).Where(w => !w.Description.Contains("Bear"))).FirstOrDefault();
EF Core 5 generates SQL that returns only the snake and squirrel in alphabetical order.
8/15/2020 10:31:14 AM: Garter Snake
8/15/2020 10:31:14 AM: Squirrel
Tweak Performance with Split Queries
I mentioned the LEFT JOIN
used for the filtered Include query above. The default for eager loaded queries is to build a single query. The more complexity you build into an eager loaded query, for example if you are piling on Include
or ThenInclude
methods when the Includes
are pointing to collections, there's a good chance of the query performance degrading. This can be dramatic and I've worked with clients to solve this type of performance problem many times.
Earlier in EF Core's lifetime, the team tried to improve the performance by splitting up the query. However, users discovered that this path occasionally returned inconsistent results. In response, in EF Core 3, the team reverted back to a single query. Now in EF Core 5, although the single query remains the default, you have the option to force the query to be split up with the AsSplitQuery
method. Like the AsNoTracking
method, there's also a way to apply this to the context itself, not only particular queries. For example, here is the SQL from the simple Include
above before I added the filter. Notice the LEFT JOIN
to retrieve the related data.
SELECT [t].[Id], [t].[PostalCode], [t].[Street], [w].[Id], [w].[AddressId], [w].[DateTime], [w].[Description]
FROM (SELECT TOP(1) [a].[Id], [a].[PostalCode], [a].[Street] FROM [Addresses] AS [a]) AS [t]
LEFT JOIN [WildlifeSighting] AS [w] ON [t].[Id] = [w].[AddressId]
ORDER BY [t].[Id], [w].[Id]
Adding AsSplitQuery into the LINQ query:
context.Addresses.AsSplitQuery().Include(a => a.WildlifeSightings).FirstOrDefault();
This results in a pair of select statements, eliminating the LEFT JOIN
.
SELECT [t].[Id], [t].[PostalCode], [t].[Street]
FROM (SELECT TOP(1) [a].[Id], [a].[PostalCode], [a].[Street] FROM [Addresses] AS [a]) AS [t]
ORDER BY [t].[Id]
SELECT [w].[Id], [w].[AddressId], [w].[DateTime], [w].[Description], [t].[Id]
FROM (SELECT TOP(1) [a].[Id] FROM [Addresses] AS [a]) AS [t]
INNER JOIN [WildlifeSighting] AS [w] ON [t].[Id] = [w].[AddressId]ORDER BY [t].[Id]
I'm not a DBA and don't have the skill to determine if this particular split query will result in better performance, but this is a very simple example. However, I believe that if you are diligent about profiling your queries and have someone on your team who can identify where performance will benefit from split queries, then you'll be in a position to leverage this feature. What the EF Core team has done is given you agency over this decision rather than forcing a single behavior on you.
I also tested AsSplitQuery()
with filtered includes and across the many-to-many relationship with success.
Improving the Migration Experience for Production Apps
Creating a database and updating its schema as your model evolves is quite simple on your development computer with your local database. You can use PowerShell commands or the dotnet CLI to execute the migrations. EF Core also makes it easy to share migrations across a development team with migration files included in your source control and features such as idempotent migrations.
However, executing migrations in applications deployed to a server or to the cloud is a much different story. Microsoft hasn't yet landed on a great workflow for that. Some developers look to the Migrate
method from the EF Core APIs during application start up to solve this problem. But if your application is deployed across multiple instances for load balancing, this could create some terrible conflicts if you hit a race condition between different instances attempting to execute the migration. Therefore, it's strongly recommended that you avoid that pattern.
The safest workflows have involved letting migrations create SQL and then using other tools to execute that SQL. For example, for SQL Server, there is tooling like dacpac or RedGate's SQL Change Automation. For a wider variety of relational databases, I've been leveraging RedGate's FlywayDB (http://flywaydb.org) in a Docker container to quickly execute migrations and then disappear. (See my article Hybrid Database Migrations with EF Core and Flyway for more on that pattern.)
The team is focused on improving this experience through better APIs and guidance along with, as per their docs, “longer-term collaboration with other teams to improve end-to-end experiences that go beyond just EF.”
For the EF Core 5 timeframe, they were able to accomplish some specific tasks. For example, improvements have been made to idempotency in generated migration scripts and the EF Core CLI now allows you to pass in the database connection string when updating a database.
There have also been some changes for migrations in SQLite databases. SQLite has myriad limitations around modifying table schema, which has created a lot of problems for migrations in the past. Brice Lambson has been tackling this problem for many years in between working on many other features. He recently noted on GitHub that he originally created the relevant issue before his, now six-year old, child was born. EF Core 5 enables quite a number of previously unsupported migrations such as AlterColumn and AddForeignKey. You can see the list of migrations supported (followed by recommended workarounds) in the document, SQLite EF Core Database Provider Limitations.
Transaction Support in Scripted Migrations
Have you ever run a migration that attempts to add a table that already exists? The migration fails and anything past that problem won't get run.
After much discussion and research, the team has added transactions to SQL generated by the script-migration
and CLI ef migration script
commands.
Although the BEGIN TRANSACTION
and COMMIT
statements are added by default, you can disable their inclusion with the NoTransactions
parameter.
Transactions are used to wrap what should be natural transaction points throughout the script. You can read the discussion about how this was determined in this GitHub issue. I would recommend that you (or in my case, someone with better DBA smarts) inspect the generated script to ensure that it's wrapping at the points that make sense. Personally, I always defer to the algorithm used to insert the commands if I don't have an expert available.
For you SQL Server gurus, here's a quick shout out to the new support for transaction savepoints. You can learn more about that at https://github.com/dotnet/EntityFramework.Docs/issues/2429.
Handy for Testing and Demos: ChangeTracker.Clear
In my years of researching the capabilities of Entity Framework and EF Core, I've often wanted to create a context and use it to try various things in one go, rather than creating and disposing short-lived contexts as I would do in a production app. However, one of the problems with that is that the context never forgets about entities that it's tracking and if I'm not careful, my results will be wrong and lead me astray.
For these scenarios ? and I'll stress that this isn't recommended for production code ? I've always wished that I could easily purge the change tracker's cache without having to instantiate a new context. EF Core 5 finally gives me the tool to fulfill that wish: the ChangeTracker.Clear()
method. It just tells a DbContext's ChangeTracker to forget everything it knows and act as though it was newly instantiated. Thanks team!
New Support for DbContext and Dependency Injection in Blazor
ASP.NET Core has made it so easy to let your Web app spin up DbContext instances as needed without hard coding new instances all over your application, creating tight coupling and having to manage their lifetime.
But when Blazor, Microsoft's client-side UI framework, showed up on the scene, a lot of developers struggled to find a way to get the same benefit. Doing so involved creating your own class that implements IDbContextFactory
. Not only was this more complex than the IServiceCollection.AddDbContext
method in ASP.NET Core's startup class, it wasn't discoverable, although it's documented.
The Blazor team got together with the EF Core team to tackle this problem and now there's a nice solution that can be used in Blazor and other application types with the ease that we already experience with ASP.NET Core.
The new IServiceCollection.AddDbContextFactory
is the magic potion. There's also a version for when you are pooling DbContexts
: the AddPooledDbContextFactory
.
Like ASP.NET Core, Blazor has a startup class with a ConfigureServices
method. So, it's easy to use the new method to a factory into the DI services rather than a DbContext. The code looks similar to AddDbContext
.
services.AddDbContextFactory<PeopleContext>(opt => opt.UseSqlServer($"Data Source={myconnectionstring}"));
Like AddDbContext
, you specify the options (e.g., provider, connection string, and things like EnableSensitiveLogging
) for the DbContext
instances that the factory will spin up for you.
Then, in code where you're using that injected factory, you only need to call its CreateContext
method to get a ready-to-use instance of your context.
var context = _contextFactory.CreateDbContext()
Don't forget (as I often do) that your DbContext
class will need a constructor that accepts the DbContextOptions
. This isn't new, but something you've needed to do for using DbContext in dependency injection since the beginning of ASP.NET Core. As a reminder, this is what that constructor looks like:
public PeopleContext(DbContextOptions<PeopleContext> options) : base(options)
{
}
But there's a big difference between using AddDbContext
and AddDbContextFactory
. AddDbContext
scopes the lifetime of the DbContexts
that get created to the request that triggers its creation (e.g., a request to a controller method). But when you use the factory to create those contexts, you'll be responsible for the lifetime of each DbContext
instance. In other words, you'll trigger the factory to create those instances as needed and then be responsible for disposing them.
In the Blazor app setup, EF Core migrations commands will be able to find the provider and connection string just as they do with ASP.NET Core.
The Return of Table-Per-Type Inheritance
TPT inheritance (where inherited types are stored in their own tables in the database) was a feature of EF from the start through to EF6. Like the many-to-many support, EF Core didn't have the needed building blocks to create a smarter implementation with better-performing SQL queries. However, it was one of the top requested features and now it's back in EF Core 5.
In the 300+ comment GitHub issue for TPT, EF team engineer Smit Patel shares (in https://github.com/dotnet/efcore/issues/2266#issuecomment-653661902) sample output comparing a TPT query in EF6 and EF Core 5 (Figure 4). It's surely more readable but there are also performance benefits as well, described in this same comment.
Setting up TPT works just as it did with EF6.
By default, you create an inherited type, e.g., ScaryWildlifeSighting
that inherits from WildlifeSighting
like this:
public class ScaryWildlifeSighting : WildlifeSighting
{
public string Experience { get; set; }
}
The convention for EF Core is to treat this as a Table-Per-Hierarchy mapping, where the properties of ScaryWildlifeSighting
are added to the WildlifeSightings
table.
You can change this to TPT by letting EF Core know to put ScaryWildlifeSighting
data into its own table using the [Table("ScaryWildlifeSightings")]
data annotation or the ToTable
mapping in OnModelCreating
:
modelBuilder.Entity<ScaryWildlifeSighting>().ToTable("ScarySightings");
And you can create, update, and query the related data just as you would have in EF6. A query to find all of the scary sightings with the phrase “peed my pants,” for example, would look like:
context.WildlifeSightings.OfType<ScaryWildlifeSighting>().Where(s => s.Experience.Contains("peed")).ToList();
So Many More Interesting and Useful Features
Obviously, I can't tell you about all 255 features and enhancements that have been added to EF Core 5. And it's so hard to pick which ones to list here in the final bit of this article.
I must give a shout out to a few things:
- Many enhancements have been made to the Cosmos DB provider.
- There are more ways to tap into
SaveChanges
besides overriding the method. For example, there are events and an interceptor. - If you were impacted by the regression in EF Core 3.0 that caused serious problems with the performance of queries involving owned entities, you'll be happy (as am I) to know that this was fixed for EF Core 5.
Index properties are an interesting feature that are something like dynamic types but specific to EF Core. You might not use them directly (although you could) but they are also a building block that other features, such as many-to-many, rely upon.
And now I will let Brice Lambson from the EF team take over because he posted two great tweets about his favorite features in EF Core 5!
In his first tweet (https://twitter.com/bricelambs/status/1295789002876784645) he listed features that bring additional EF6 parity to EF Core 5. I've added notes to his list to provide some insight for you:
- Simple logging (covered above)
- Get the SQL of a query
- Example:
Console.WriteLine(someLinqQuery.ToQueryString())
;
- Example:
HasPrecision
- Example:
modelBuilder.Entity<Address>().Property(b=>b.SqFeet).HasPrecision(16,4)
- Example:
- Defining query
- EF Core 5 makes it easier to specify a defining query of an entity type as SQL (like EF6) and at the same time, they deprecated some of the APIs around using LINQ for defining queries.
- Pluralization while scaffolding
- Enabled by the Humanizer project now being used as the pluralization service.
- Specify connection string in tools
- The migrations CLI now has a parameter called
--connection
to which you can pass the string - Get-Migration
- This, along with the CLI version (dotnet ef migrations list), will list the status of your migrations, e.g., what's been applied, what's pending, and if there are model changes that still need to be migrated.
In the second tweet (https://twitter.com/bricelambs/status/1295789904077582336), Brice listed some new features that he is especially fond of.
- Collation
- Indexer properties
AddDbContextFactory
(covered above)- Ignore in Migrations
- You can edit a migration and force it to ignore a table that the migration wants to create. There's an interesting discussion in the relevant GitHub issue.
- SQLite Computed columns
- The SQLite provider now supports HasComputedColumn. But there's something else I want to point out. EF (and EF Core) have had computed column support for a long time for SQL Server. And if you wanted stored columns, you could use SQL Server's PERSISTED attribute so that searches don't need to build that value on the fly. Now there's a “stored” attribute on EF Core's HasComputedColumn mapping, which is a simpler and more discoverable way to force the database to store the value so that search and indexing can benefit.
- SQLite Table rebuilds (mentioned as part of the SQLite migrations above)
- Better SQL (the team is always improving the SQL translations)
- Simplifications for Discriminator, NULL and CASE
- Transactions and EXEC in migrations script
- Better LINQ translation errors
Microsoft Continues to Invest in EF Core, So You Can, Too
In the old days of Entity Framework, there was a theme of “evolution, not revolution” when it came to new versions. Moving from EF to EF Core was certainly a big revolution, however, that wasn't an update of EF. EF Core 3 was the first time that there was what we would call a revolution. But now that we're past that, EF Core is back on a path of evolving in great ways. EF Core 5 is a very sophisticated ORM and most importantly, the investment made into this version is more proof that Microsoft is dedicated to keeping EF Core around for a good long time to come. I hope you'll dig into the excellent documentation as well as the resources in GitHub to continue to discover what this version brings.
Remember also to check out the team's standups at http://bit.ly/EFCoreStandups and watch out for an update to my Getting Started with EF Core course on Pluralsight sometime after EF Core 5 is released.