Entity Framework is a powerful tool with so many features it's hard to know them all. I am constantly getting surprised by just how smart it can be. Sometimes you might want it to do just a little bit more.
Unfortunately, there is no real documentation about how to add functionality to Entity Framework. What's there is usually either outdated or not that helpful. I want to emphasize here that I am not putting any blame on the EF team - they have been making huge strides with each release of EF Core. And the regular docs are being updated. With all that, it's not surprising that there is no time to document intricate details that very few people will see.
Useful resources
That does not mean there is no information about how to approach this. This is the Internet, after all. I wanted to highlight some places that helped me while developing the auditing solution described in a previous post.
The resource that has the most information is, naturally, the EF Core repository:
There's a lot of information that can be found in the reported issues. What's more, the code itself is usually pretty easy to follow, even if you haven't seen it before.
Another great place is this StackOverflow answer:
I found it after looking pouring through the EF Core sources and stitching together a mental image of how it's all connected. Of course, all this time it was already figured out and explained on SO.
Last but not least, if you're planning on customising Entity Framework, make sure to check out the Laraue.EfCoreTriggers
project:
It's very impressive and helped me understand the internals of EF Core better than I had before. Even though my project took a slightly different route, it's still an amazing project.
Even though there's a lot of information at the links above, I believe I still have some tricks worth sharing.
Design Time Services
There are some services that EF uses only at design time (think migration scaffolding etc.). There's a complete list of them at List of services. A good example of those services are implementations of IMigrationsCodeGenerator
and ICSharpMigrationOperationGenerator
, which are responsible for generating the code that's placed in the migration .cs
file.
Replacing the services with your own implementations is comically easy. The only thing that needs to be done is to implement the IDesignTimeServices
interface:
Entity Framework will use magic reflection to find the implementation and execute the ConfigureDesignTimeServices
method. Cool.
BUT, if you look at the code doing just that in the DesignTimeServicesBuilder
class:
You will notice that only the startup assembly is scanned. That's very unfortunate if you plan of releasing your extensions as a library or even if you just wanted to isolate all that EF-specific code in a separate project.
However, if you spend some more time looking at the DesignTimeServicesBuilder
you will find this bit of code:
This means that the startup assembly just needs to include the DesignTimeServicesReferenceAttribute
assembly attribute pointing to the IDesignTimeServices
implementation. That's so much better. Here's an example of how the attribute should be used:
[assembly: DesignTimeServicesReference("EFCore.AuditExtensions.Common.EfCore.DesignTimeServices, EFCore.AuditExtensions.Common")]
Note: If your startup project is separated from the project containing your DbContext and you use the -s|--startup-project
and -p|--project
flags when using the dotnet-ef tool, the DesignTimeServicesReference
attribute can be located in the project specified with the -p|--project
flag (because of line #4 in the above snippet).
If you want to read the background behind the attribute and another example, visit the links below:
Cover photo by Daniel McCullough on Unsplash