DEV Community

Matteo Maggiolini
Matteo Maggiolini

Posted on • Edited on

Tips & Tricks About Entity Framework Core 7.x Every .NET Developer Should Know

First of all, some common tricks

Eager Loading: To load related data at the same time as its parent, use the Include method.

var blogs = context.Blogs
    .Include(blog => blog.Posts)
    .ToList();
Enter fullscreen mode Exit fullscreen mode

Shadow Properties: Shadow properties are properties that aren't defined in your entity class but are defined for that entity in the EF Core model.

modelBuilder.Entity<Blog>()
    .Property<DateTime>("LastUpdated");
Enter fullscreen mode Exit fullscreen mode

Concurrency Control: It's important to handle conflicts when multiple users are updating the same record. You can use the [ConcurrencyCheck] data annotation attribute.

public class Blog
{
    public int BlogId { get; set; }
    [ConcurrencyCheck]
    public string Url { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

Database Seeding. You can seed data to your database in EF Core

modelBuilder.Entity<Blog>()
.HasData(new Blog {BlogId = 1, Url = "http://sample.com"});
Enter fullscreen mode Exit fullscreen mode

Disable Automatic Detect Changes: For better performance, consider turning off the AutoDetectChanges behavior when you don’t need it.

context.ChangeTracker.AutoDetectChangesEnabled = false;
Enter fullscreen mode Exit fullscreen mode
var products = context.Products.AsNoTracking().ToList();
Enter fullscreen mode Exit fullscreen mode

Streaming instead of buffering: Streaming allows you to process data as it is being read from the database, rather than buffering it all in memory first. This can help reduce memory usage and improve performance.

using var context = new MyDbContext();
using var stream = context.Blogs.AsNoTracking().Select(b => b.Url)
.AsStream();
await stream.CopyToAsync(Response.Body);
Enter fullscreen mode Exit fullscreen mode

Compiled queries: Compiled queries can help improve performance by caching the query execution plan.

private static readonly Func<MyDbContext, int, Blog> _blogById =
    EF.CompileQuery((MyDbContext context, int id) =>
        context.Blogs.FirstOrDefault(b => b.Id == id));

var blog = _blogById(context, 1);
Enter fullscreen mode Exit fullscreen mode

InMemory database provider for testing: The InMemory database provider allows you to create an in-memory database for testing purposes. It provides a lightweight and fast way to test your code without the need for a real database. Here's an example of using the InMemory provider:

services.AddDbContext<MyDbContext>(options =>
    options.UseInMemoryDatabase("TestDatabase"));
Enter fullscreen mode Exit fullscreen mode

Database Views: Database views can provide a simplified and optimized representation of your data.

modelBuilder.Entity<Blog>()
    .ToView("View_Blogs")
    .HasNoKey();
Enter fullscreen mode Exit fullscreen mode

Query Filters: Query filters allow you to apply global filters to your queries, which can be useful for implementing soft delete or multi-tenancy scenarios. Here's an example of using query filters:

modelBuilder.Entity<Blog>()
    .HasQueryFilter(b => !b.IsDeleted);
Enter fullscreen mode Exit fullscreen mode

DbContext pooling can improve the performance of your web applications by reusing the same context instance across requests. This reduces the overhead of creating and disposing DbContext instances. To enable DbContext pooling in an ASP.NET Core application, use the AddDbContextPool method when configuring services in the Startup class:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContextPool<MyDbContext>(options =>
    {
        options.UseSqlServer(
        Configuration.GetConnectionString("DefaultConnection")
        );
    });
}
Enter fullscreen mode Exit fullscreen mode

The AsSplitQuery method can be used to avoid the Cartesian explosion problem when including multiple navigational properties. This method configures EF Core to load the collections in the query results through separate database queries:

var customersWithOrdersAndProducts = context.Customers
    .Include(c => c.Orders)
        .ThenInclude(o => o.Products)
    .AsSplitQuery()
    .ToList();
Enter fullscreen mode Exit fullscreen mode

Now let's dive into ef core 7 specific features

Inserting a single row

var blog = new Blog { Name = "MyBlog" };
ctx.Blogs.Add(blog);
await ctx.SaveChangesAsync();
Enter fullscreen mode Exit fullscreen mode

In EF Core 6.0, this code would start a transaction, execute the command, and then commit the transaction. In EF Core 7.0, the transaction is removed, resulting in a 25% improvement in performance on localhost and a 45% improvement on a remote server.

Inserting multiple rows

for (var i = 0; i < 4; i++)
{
    var blog = new Blog { Name = "Foo" + i };
    ctx.Blogs.Add(blog);
}
await ctx.SaveChangesAsync();
Enter fullscreen mode Exit fullscreen mode

In EF Core 6.0, this would use a MERGE statement to insert four rows, which is significantly faster than four separate INSERT statements. In EF Core 7.0, the transaction is removed, and the temporary table is also removed, resulting in a 61% improvement in performance on a remote server and a 74% improvement on localhost.

Using HiLo feature for integer keys:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>().Property(b => b.Id).UseHiLo();
}
Enter fullscreen mode Exit fullscreen mode

Once HiLo is enabled, the SaveChanges output is efficient, and resembles the GUID scenario.

SQL Server Temporal Tables. EF Core 7.0 now supports SQL Server Temporal Tables. This allows you to keep a history of data changes directly in the database

modelBuilder.Entity<YourEntity>()
    .UseTemporalTable();
Enter fullscreen mode Exit fullscreen mode

Compiled Models. EF Core 7.0 introduces compiled models, which can significantly improve startup performance. Here's how you can use it:

var options = new DbContextOptionsBuilder<YourContext>()
    .UseModel(YourCompiledModel.Instance)
    .Options;
Enter fullscreen mode Exit fullscreen mode

Table-per-Type (TPT) Mapping. EF Core 7.0 introduces support for Table-per-Type (TPT) mapping.

modelBuilder.Entity<YourBaseEntity>()
    .ToTable("YourBaseTable");

modelBuilder.Entity<YourDerivedEntity>()
    .ToTable("YourDerivedTable");
Enter fullscreen mode Exit fullscreen mode

SQLite Online Schema Migrations. EF Core 7.0 introduces support for SQLite online schema migrations.

var options = new DbContextOptionsBuilder<YourContext>()
.UseSqlite("YourConnectionString", b => 
b.MigrationsAssembly("YourAssembly"))
    .Options;
Enter fullscreen mode Exit fullscreen mode

ExecuteUpdate and ExecuteDelete Methods. EF Core 7 introduces two new methods, ExecuteUpdate and ExecuteDelete, that perform changes and deletions of records without loading the entities in memory, resulting in performance gains. However, changes must be specified explicitly as they are not automatically detected by EF Core.

await db.Posts
    .Where(p => p.Id == "3fa85f64-5717-4562-b3fc-2c963f66afa6")
    .ExecuteUpdateAsync(s => s
    .SetProperty(b => b.AuthorName, "John Smith")
    .SetProperty(b => b.Title, b =>  "EF7 is here!")
    .SetProperty(b => b.Text, b =>  "\t")
    .SetProperty(b => 
     b.LastUpdateDate, "2022-30-11 17:29:46.5028235"));
Enter fullscreen mode Exit fullscreen mode
await db.Posts
.Where(p => p.Id == "3fa85f64-5717-4562-b3fc-2c963f66afa6")
.ExecuteDeleteAsync();
Enter fullscreen mode Exit fullscreen mode

JSON Columns. EF7 has native support for JSON columns, which allows mapping .NET types to JSON documents. This includes LINQ queries that can be used in aggregations and will be converted to the appropriate query constructs for JSON. It's also possible to update and save changes to JSON documents.

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Author>().OwnsOne(
        author => author.Contact, ownedNavigationBuilder =>
        {
            ownedNavigationBuilder.ToJson();
            ownedNavigationBuilder
            .OwnsOne(contactDetails => contactDetails.Address);
        });
}
Enter fullscreen mode Exit fullscreen mode
var authorsByCity = await db.Authors.Where(author => author
.Contact.Address.City == city).ToListAsync();
Enter fullscreen mode Exit fullscreen mode
var authorExists = await db.Authors
.Where(author => author.Id == id)
.FirstOrDefaultAsync();
authorExists.Contact.Address.Street = "1523 Stellar Dr";
await db.SaveChangesAsync();
Enter fullscreen mode Exit fullscreen mode

New Query Options. EF7 brought new features to queries like GroupBy as the final operator. The example below shows how grouping can be done using the GroupBy extension method:

var groupByAuthor = db.Posts.GroupBy(p => p.AuthorName).ToList();
Enter fullscreen mode Exit fullscreen mode

Bulk Updates and Deletes. EF Core 7 introduces the ability to perform bulk updates and deletes. This allows you to express something similar to a LINQ query to push the changes directly to the database. The new ExecuteDelete and ExecuteUpdate methods are appended to LINQ queries in the same way that you'd apply a LINQ execution method.

context.People
    .Where(p => p.PersonId == 1)
    .ExecuteDelete();
Enter fullscreen mode Exit fullscreen mode
context.People
    .Where(p => p.LastName == "Lehrman")
    .ExecuteUpdate(s => s.SetProperty(c => c.LastName, c => "Lerman"));
Enter fullscreen mode Exit fullscreen mode

Mapping Stored Procedures. EF Core 7 introduces the ability to map stored procedures to entities. This allows you to use stored procedures to perform database operations when you call SaveChanges. The new InsertUsingStoredProcedure, UpdateUsingStoredProcedure, and DeleteUsingStoredProcedure methods allow you to map stored procedures to entities.

modelBuilder.Entity<Person>()
    .InsertUsingStoredProcedure("PeopleInsert",
        spbuilder => spbuilder
            .HasParameter(p => p.PersonId, pb => pb.IsOutput()
            .HasName("id"))
            .HasParameter(p => p.FirstName)
            .HasParameter(p => p.LastName)
    );

Enter fullscreen mode Exit fullscreen mode

Top comments (0)