Last active
March 5, 2021 11:38
-
-
Save fernandoescolar/53df9ac1bf71ff032c1b9284f6890530 to your computer and use it in GitHub Desktop.
Entity Framework Core vs. C#9 records
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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