Tips & Tricks About Entity Framework Core 7.x Every .NET Developer Should Know
Tags: database, efcore, dotnet, entityframework
Published: 2023-07-16 11:15:02

## 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();
```

**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");
```

**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; }
}
```
**Database Seeding**. You can seed data to your database in EF Core

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

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

```
context.ChangeTracker.AutoDetectChangesEnabled = false;
```

```
var products = context.Products.AsNoTracking().ToList();
```

**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);
```

**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);
```

**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"));
```

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

```
modelBuilder.Entity<Blog>()
    .ToView("View_Blogs")
    .HasNoKey();
```

**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);
```

**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")
        );
    });
}
```

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();
```


## 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();
```
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();
```
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();
}
```
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();
```

**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;
```

**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");
```

**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;
```

**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"));
```

```
await db.Posts
.Where(p => p.Id == "3fa85f64-5717-4562-b3fc-2c963f66afa6")
.ExecuteDeleteAsync();
```

**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);
        });
}
```

```
var authorsByCity = await db.Authors.Where(author => author
.Contact.Address.City == city).ToListAsync();
```

```
var authorExists = await db.Authors
.Where(author => author.Id == id)
.FirstOrDefaultAsync();
authorExists.Contact.Address.Street = "1523 Stellar Dr";
await db.SaveChangesAsync();
```

**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();
```

**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();
```

```
context.People
    .Where(p => p.LastName == "Lehrman")
    .ExecuteUpdate(s => s.SetProperty(c => c.LastName, c => "Lerman"));
```

**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)
    );

```