Skip to content

Instantly share code, notes, and snippets.

@fernandoescolar
Last active March 5, 2021 11:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fernandoescolar/53df9ac1bf71ff032c1b9284f6890530 to your computer and use it in GitHub Desktop.
Save fernandoescolar/53df9ac1bf71ff032c1b9284f6890530 to your computer and use it in GitHub Desktop.
Entity Framework Core vs. C#9 records
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace EfCoreVsRecords
{
public record Item(Guid Id, string Name, bool IsDone)
{
private List<ItemTask> _tasks = new List<ItemTask>();
public IEnumerable<ItemTask> Tasks
{
get => new ReadOnlyCollection<ItemTask>(_tasks);
init => _tasks = value.ToList();
}
}
public record ItemTask(Guid Id, string Name);
public class ItemTypeConfiguration : IEntityTypeConfiguration<Item>
{
public void Configure(EntityTypeBuilder<Item> builder)
{
builder.HasKey(x => x.Id);
builder.Property(x => x.Name).HasMaxLength(512).IsRequired();
builder.HasMany(x => x.Tasks).WithOne().OnDelete(DeleteBehavior.Cascade);
}
}
public class ItemTaskTypeConfiguration : IEntityTypeConfiguration<ItemTask>
{
public void Configure(EntityTypeBuilder<ItemTask> builder)
{
builder.HasKey(x => x.Id);
builder.Property(p => p.Id).IsRequired().ValueGeneratedNever();
builder.Property(x => x.Name).HasMaxLength(512).IsRequired();
}
}
public class TodoContext : DbContext
{
public TodoContext(DbContextOptions<TodoContext> options)
: base(options)
{
}
public DbSet<Item> Items { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.ApplyConfigurationsFromAssembly(GetType().Assembly);
}
}
}
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="5.0.3" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="1.3.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
</Project>
using Microsoft.EntityFrameworkCore;
using System;
using System.Linq;
using System.Threading.Tasks;
using Xunit;
namespace EfCoreVsRecords
{
public class Test
{
private const string SqlConnectionString = @"Server=.\SQLEXPRESS;Database=Test;Trusted_Connection=True;MultipleActiveResultSets=true";
private const string TodoItemName = "create a blog post";
private const string TodoItemTaskName = "develop a demo";
private const string TodoItemTaskUpdatedName = "develop a unit test";
[Fact]
public async Task IntegrationTest()
{
EnsureDatabaseIsCreated();
var id = Guid.NewGuid();
await CreateItemAsync(id);
await AssertItemAsync(id, expectedIsDone: false);
await AddItemTaskAsync(id);
await AssertItemTaskAsync(id, expectedName: TodoItemTaskName);
await MarkItemAsDoneAsync(id);
await AssertItemAsync(id, expectedIsDone: true);
await AssertItemTaskAsync(id, expectedName: TodoItemTaskName);
await ModifyItemTaskAsync(id);
await AssertItemTaskAsync(id, expectedName: TodoItemTaskUpdatedName);
await DeleteItemAsync(id);
await AssertItemDoesNotExistsAsync(id);
}
private static async Task CreateItemAsync(Guid id)
{
using var context = CreateContext();
var item = new Item(id, TodoItemName, false);
context.Items.Add(item);
await context.SaveChangesAsync();
}
private static async Task MarkItemAsDoneAsync(Guid id)
{
using var context = CreateContext();
var item = await context.Items.AsNoTracking().SingleOrDefaultAsync(x => x.Id == id);
item = item with { IsDone = true };
context.Items.Attach(item).State = EntityState.Modified;
await context.SaveChangesAsync();
}
private static async Task AddItemTaskAsync(Guid id)
{
using var context = CreateContext();
var item = await context.Items.AsNoTracking().Include(nameof(Item.Tasks)).SingleOrDefaultAsync(x => x.Id == id);
var task = new ItemTask(Guid.NewGuid(), TodoItemTaskName);
var list = item.Tasks.ToList();
list.Add(task);
item = item with { Tasks = list };
context.Items.Attach(item).State = EntityState.Modified;
context.Set<ItemTask>().Attach(task).State = EntityState.Added;
await context.SaveChangesAsync();
}
private static async Task ModifyItemTaskAsync(Guid id)
{
using var context = CreateContext();
var item = await context.Items.AsNoTracking().Include(nameof(Item.Tasks)).SingleOrDefaultAsync(x => x.Id == id);
var task = item.Tasks.FirstOrDefault();
task = task with { Name = TodoItemTaskUpdatedName };
context.Set<ItemTask>().Attach(task).State = EntityState.Modified;
await context.SaveChangesAsync();
}
private static async Task DeleteItemAsync(Guid id)
{
using var context = CreateContext();
var item = await context.Items.FindAsync(id);
context.Items.Remove(item);
await context.SaveChangesAsync();
}
private static async Task AssertItemAsync(Guid id, bool expectedIsDone)
{
using var context = CreateContext();
var item = await context.Items.FindAsync(id);
Assert.Equal(TodoItemName, item.Name);
Assert.Equal(expectedIsDone, item.IsDone);
}
private static async Task AssertItemTaskAsync(Guid id, string expectedName)
{
using var context = CreateContext();
var item = await context.Items.Include(nameof(Item.Tasks)).SingleOrDefaultAsync(x => x.Id == id);
Assert.NotEmpty(item.Tasks);
var task = item.Tasks.First();
Assert.Equal(expectedName, task.Name);
}
private static async Task AssertItemDoesNotExistsAsync(Guid id)
{
using var context = CreateContext();
var item = await context.Items.FindAsync(id);
Assert.Null(item);
}
private static void EnsureDatabaseIsCreated()
{
using var context = CreateContext();
if (context != null && context.Database != null)
{
context.Database.EnsureCreated();
}
}
private static TodoContext CreateContext()
{
var options = new DbContextOptionsBuilder<TodoContext>();
options.UseSqlServer(SqlConnectionString);
return new TodoContext(options.Options);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment