Seria pytań uczestników, które pojawiły się podczas szkolenia Entity Framework Core 6 realizowanego w dniach 14-16.11.2022 r.
public interface IAccountRepository
{
void Withdraw(decimal amount, string owner);
void Deposit(decimal amount, string owner);
}
2. Implementacja repozytorium
internal class DbAccountRepository : IAccountRepository
{
private readonly ApplicationDbContext context;
public DbAccountRepository(ApplicationDbContext context)
{
this.context = context;
}
public void Deposit(decimal amount, string owner)
{
var recipient = context.Accounts.Single(a => a.Owner == owner);
recipient.Balance += amount;
if (recipient.Balance > Account.BalanceLimit)
throw new ApplicationException($"Balance over limit {Account.BalanceLimit}");
context.SaveChanges();
}
public void Withdraw(decimal amount, string owner)
{
var sender = context.Accounts.Single(a => a.Owner == owner);
sender.Balance -= amount;
context.SaveChanges();
}
}
3. Wykonanie operacji w ramach transakcji
decimal amount = 100;
IAccountRepository accountRepository = new DbAccountRepository(context);
using (TransactionScope transaction = new TransactionScope())
{
try
{
accountRepository.Withdraw(amount, "John");
accountRepository.Deposit(amount, "Mark");
transaction.Complete();
}
catch (ApplicationException e)
{
Console.WriteLine("Rollbacked transaction.");
}
}
Transakcja zostanie zatwierdzona na końcu bloku using, w zależności od tego, czy była wywołana metoda Complete(). Jeśli tak, to transakcja zostanie zatwierdzona (Commit), a w przeciwnym przypadku wycofana (Rollback).
public class Node
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection Children { get; set; }
}
Context:
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions options) : base(options)
{
}
public DbSet Nodes { get; set; }
}
Jeśli spróbujemy pobieranie zachłanne z użyciem metody Include:
var node = context.Nodes
.Include(p=>p.Children)
.Find(1);
to pobierze tylko dzieci pierwszego poziomu.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity().Navigation(p => p.Children).AutoInclude();
}
using var context = new ApplicationDbContext();
var node = context.Nodes.Find(1);
Próba z zastosowaniem AutoInclude również daje taki sam efekt.
Można rozwiązać to z jawnym ładowaniem danych:
var node = context.Nodes.Find(1);
context.Entry(node).Collection(p => p.Children).Load();
foreach (var subnode in node.Children)
{
context.Entry(subnode).Collection(p => p.Children).Load();
}
Ale to z kolei wygeneruje bardzo dużą ilość zapytań do bazy danych.
W przypadku, gdy mamy do czynienia ze strukturą drzewiastą, polecam zastosowanie specjalnego typu hierarchyid obsługiwanego przez SQL Server od wersji 2016: https://www.meziantou.net/sql-server-discovering-the-hierarchyid-data-type.htm
Niestety ten mechanizm obsługiwany jest dopiero od EF Core 3. Przykład użycia:www.meziantou.net/using-hierarchyid-with-entity-framework-core.htm
modelBuilder.Entity()
.HasData(new List
{
new Color { Id = 1, Name = "Red"},
new Color { Id = 2, Name = "Blue"},
new Color { Id = 3, Name = "Green"},
});