Entity Framework Core 6 - 3 pytania i odpowiedzi ze szkolenia (listopad 2022)

Seria pytań uczestników, które pojawiły się podczas szkolenia Entity Framework Core 6 realizowanego w dniach 14-16.11.2022 r.


Jak wykonać kilka operacji w ramach transakcji, jeśli korzystam ze wzorca repozytorium i nie mam dostępu do kontekstu?

W takim przypadku należy skorzystać z mechanizmu transakcji rozproszonych (Distributed Transaction).

Przykład:

1. Interfejs

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).

Jak pobrać zachłannie strukturę drzewiastą?

Niestety standardowo nie ma efektywnego rozwiązania.

Przykład:

Model:

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

Czy można w Bundle inicjować złożone dane?

Niestety nie ma takiej możliwości. Aplikacja Bundle generowana jest tylko na podstawie migracji:


  modelBuilder.Entity()
              .HasData(new List
          {
              new Color { Id = 1, Name = "Red"},
              new Color { Id = 2, Name = "Blue"},
              new Color { Id = 3, Name = "Green"},
          });