GeneratedEntityFramework 8.0.1
See the version list below for details.
dotnet add package GeneratedEntityFramework --version 8.0.1
NuGet\Install-Package GeneratedEntityFramework -Version 8.0.1
<PackageReference Include="GeneratedEntityFramework" Version="8.0.1" />
paket add GeneratedEntityFramework --version 8.0.1
#r "nuget: GeneratedEntityFramework, 8.0.1"
// Install GeneratedEntityFramework as a Cake Addin #addin nuget:?package=GeneratedEntityFramework&version=8.0.1 // Install GeneratedEntityFramework as a Cake Tool #tool nuget:?package=GeneratedEntityFramework&version=8.0.1
GeneratedEntityFramework - Generated interface implementations for Entity Framework Core
GeneratedEntityFramework is a.NET source generator that automatically generates the DbSet
implementations for a
DbContext
based on the properties of an interface. This greatly facilitates integration of Entity Framework within a
Clean Architecture (CA) or a Vertical Slice Architecture (VSA) by segmenting one or more DbContexts into one or more
interfaces.
Requirements
This source generator will generate source that is only compatible with EntityFrameworkCore and Microsoft's Dependency Injection abstraction.
Getting Started
The Basics
Usually, a DbContext will contain or more DbSets:
public class BloggingContext : DbContext
{
public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }
}
In a typical Clean Architecture project, those DbSets are often presented to the application as an interface:
public class BloggingContext : DbContext, IBloggingContext
{
public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }
}
public interface IBloggingContext
{
public DbSet<Blog> Blogs { get; }
public DbSet<Post> Posts { get; }
}
Keeping both the DbContext and the interface up to date with each other can quickly become tedious as each
DbSet added to the interface must also be duplicated by hand in the DbContext and vice versa.
GeneratedEntityFramework's source generator solves this
by source generating a partial
implementation of the DbContext based on it's association with one or more
interfaces.
How to use the source generator
The are three different ways for the source generator to detect which implementations to generate for the DbContext.
Inheriting from the interface
If the DbContext is marked with the GeneratedDbContext
attribute, it will generate the implementations for all
of the inherited interfaces.
[GeneratedDbContext]
public partial class BloggingContext : DbContext, IBloggingContext
{
}
public interface IBloggingContext
{
public DbSet<Blog> Blogs { get; }
public DbSet<Post> Posts { get; }
}
Specifying the interface
If the DbContext is marked with the GeneratedDbContext
attribute and the interface is specified as the constructor
parameter, it will generate the implementations for the specified interface and automatically add the interface as
an inherited interface to the partial DbContext.
[GeneratedDbContext<IBloggingContext>]
public partial class BloggingContext : DbContext
{
}
public interface IBloggingContext
{
public DbSet<Blog> Blogs { get; }
public DbSet<Post> Posts { get; }
}
Specifying the DbContext
If the interface is marked with the DbContext
attribute and the DbContext is specified as the constructor
parameter, it will generate the implementations for the specified interface and automatically add the interface as
an inherited interface to the partial DbContext.
public partial class BloggingContext : DbContext
{
}
[DbContext<BloggingContext>]
public interface IBloggingContext
{
public DbSet<Blog> Blogs { get; }
public DbSet<Post> Posts { get; }
}
Notes about the attributes
Generic attributes are available from .NET 7.0 onwards, in order to use the GeneratedDbContext
and DbContext
attributes with .NET 6.0 and prior, simply use the constructor with a typeof
value of the interface or DbContext
as follows:
[GeneratedDbContext(typeof(IBloggingContext))]
public partial class BloggingContext : DbContext { }
[DbContext(typeof(BloggingContext))]
public interface IBloggingContext { }
The GeneratedDbContext
and DbContext
attributes can be used multiple times in any combinations, the source generator
will automatically eliminate any duplication and redundancies. This is incredibly useful for Vertical Sliced Architecture
(VSA) as this allows different slices to define segmented interfaces while merging of all the implementations over a single
DbContext. For example, the following will produce a single implementation of each of the DbSets on the DbContext:
[GeneratedDbContext<IBloggingContext>]
[GeneratedDbContext<IBlogsContext>]
[GeneratedDbContext<IPostsContext>]
public partial class BloggingContext : DbContext
{
}
[DbContext<BloggingContext>]
public interface IBloggingContext
{
public DbSet<Blog> Blogs { get; }
public DbSet<Post> Posts { get; }
}
[DbContext<BloggingContext>]
public interface IBlogsContext
{
public DbSet<Blog> Blogs { get; }
}
[DbContext<BloggingContext>]
public interface IPostsContext
{
public DbSet<Post> Posts { get; }
}
IQueryable properties
One of the issues with specifying DbSet
in the interface is that it creates high coupling with Entity Framework and
negates the benefits of using an abstraction to access the database. As an alternative, if the interface specifies an
IQueryable
property instead of a DbSet
one, then a private DbSet property will be generated as a backing field
within the DbContext. Thus, the following:
[GeneratedDbContext<IBloggingContext>]
public partial class BloggingContext : DbContext
{
}
public interface IBloggingContext
{
public IQueryable<Blog> Blogs { get; }
public IQueryable<Post> Posts { get; }
}
Would generate the following partial DbContext:
public partial class BloggingContext : IBloggingContext
{
private DbSet<Blog> DbSet__Blogs { get; set; } = default!;
public IQueryable<Blog> Blogs => DbSet__Blogs;
private DbSet<Post> DbSet__Posts { get; set; } = default!;
public IQueryable<Post> Posts => DbSet__Posts;
}
If a DbSet
exists on the interface for the same entity type as the IQueryable
, that will be used instead of
creating a backing field. This allows both DbSet and IQueryable properties to be combined as needed like so:
[GeneratedDbContext<IBloggingContext>]
public partial class BloggingContext : DbContext
{
}
public interface IBloggingContext
{
public DbSet<Blog> BlogsDbSet { get; }
public IQueryable<Blog> Blogs { get; }
}
Would instead generate the following partial DbContext:
public partial class BloggingContext : IBloggingContext
{
public DbSet<Blog> BlogsDbSet { get; set; } = default!;
public IQueryable<Blog> Blogs => BlogsDbSet;
}
AsNoTracking on IQueryable properties
The AsNoTracking
attribute can be added to IQueryable
properties and .AsNoTracking()
will added to the implementation.
[GeneratedDbContext<IBloggingContext>]
public partial class BloggingContext : DbContext
{
}
public interface IBloggingContext
{
public DbSet<Blog> BlogsDbSet { get; }
[AsNoTracking]
public IQueryable<Blog> Blogs { get; }
}
Will generate the following partial DbContext:
public partial class BloggingContext : IBloggingContext
{
public DbSet<Blog> BlogsDbSet { get; set; } = default!;
public IQueryable<Blog> Blogs => BlogsDbSet.AsNoTracking();
}
Registering the interfaces
In the GeneratedEntityFramework
namespace, an extension method named AddDbContextInterfaces
is added which can be used
with the IServiceCollection
to register all of the interfaces against their respective DbContexts.
public static void AddDbContextInterfaces(this global::Microsoft.Extensions.DependencyInjection.IServiceCollection services)
{
services.AddScoped<IBlogsContext>(sp => sp.GetRequiredService<BloggingContext>());
services.AddScoped<IPostsContext>(sp => sp.GetRequiredService<BloggingContext>());
}
With a host builder, simply call the extension method on the service collection.
builder.Services.AddDbContextInterfaces();
By default, the service lifetime is the same as the Entity Framework default of Scoped. To specify a different service lifetime,
add the DbContextInterfaceLifetime
attribute to the DbContext and any associated interfaces will be registered with the
specified lifetime.
[GeneratedDbContext<IBloggingContext>]
[DbContextInterfaceLifetime(ServiceLifetime.Transient)]
public partial class BloggingContext : DbContext
{
}
Learn more about Target Frameworks and .NET Standard.
-
.NETStandard 2.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.