Entity Framework Core 3.1 – poziom zaawansowany - 3 pytania i odpowiedzi ze szkolenia (sierpień 2021)

Seria pytań uczestników, które pojawiły się podczas szkolenia Entity Framework Core 3.1 – poziom zaawansowany realizowanego w dniach 19-20.08.2021 r.


W jaki sposób anulować długotrwające zapytania w EF Core?

Generalnie powinno działać anulowanie zadań za pomocą CancellationToken jak w przykładzie:



private static async Task CancelTestAsync(ShopContext context)
        {
            CancellationTokenSource cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));

            CancellationToken token = cts.Token;

            context.Database.SetCommandTimeout(TimeSpan.FromSeconds(0));

            var query = (from a in context.Customers
                        from b in context.Customers
                        select new { a, b.FirstName });


            var customers = await query.ToListAsync(token);
        }
 

Niestety EF Core 5 nie przerywa takiej operacji na bazie danych.
Można jednak stworzyć obejście z użyciem metody SqlCommand.Cancel() z ADO.NET



SqlConnection connection = (SqlConnection)context.Database.GetDbConnection();
  var sql = query.ToQueryString();
  SqlCommand  command = new SqlCommand(sql, connection);
 token.Register(() => command.Cancel());
connection.Open();
  var reader = command.ExecuteReader();
  try
  {
      while (reader.Read())
      {
          // TODO: Map
      }
  }
  catch (SqlException e)
  {
      Debug.WriteLine("Cancelled.");
  }
  finally
  {
      connection.Close();
  }

Niestety mapowanie na obiekty trzeba napisać ręcznie.

W jaki efektywnie sposób usuwać obiekt wg id?

W EF Core do usuwania obiektów służy metoda Remove(TEntity entity);



public void Remove(int id)
{
    Customer customer = context.Customers.Find(id);
    context.Customers.Remove(customer);
    context.SaveChanges();
    
}
 

Niestety takie podejście wysyła 2 zapytania do bazy danych.
Można to zoptymalizować tworząc fake'owy obiekt z ustawionym id:



public void Remove(int id)
{
    Customer customer = new Customer { Id = id};
    context.Customers.Remove(customer);
    context.SaveChanges();
    
}
 

Dzięki temu otrzymujemy tylko jedno zapytanie do bazy danych.
Jeśli chcemy uzyskać uniwersalną metodę to można utworzyć metodą rozszerzającą Remove, która przyjmuje tylko sam identyfikator:



public static class DbSetExtensions
    {
        public static void Remove(this DbSet dbset, TValue id)
            where TEntity : class, new()
            where TValue : struct

        {
            var context = dbset.GetService().Context;

            var keyName = GetKeyName(context);

            TEntity entity = new();

            entity.GetType().GetProperty(keyName).SetValue(entity, id);

            context.Remove(entity);
        }

        private static string GetKeyName(DbContext context)
        {
            var keyName = context.Model.FindEntityType(typeof(TEntity)).FindPrimaryKey()
                .Properties
                .Select(x => x.Name)
                .Single();

            return keyName;
        }
    }
 

Dzięki temu możemy usuwać dowolne encje w prosty sposób za pomocą samego identyfikatora


public void Remove(int id)
{
    context.Customers.Remove(id);
    context.SaveChanges();
}
 

Czy w EF Core jest wsparcie do tworzenia tabel historycznych?

Od SQL Server 2016 jest świetny mechanizm Temporal Tables(nie mylić z Temporary Tables).

Niestety EF Core 5 go nie wspiera. Jedynie rozwiązanie to utworzenie własnej migracji, która utworzy tabelę historyczną na podstawie tabeli oryginalnej oraz modyfikowanie zapytania SQL za pomocą interceptorów.

Natomiast możliwe, że nadchodzący EF Core 6 będzie obsługiwał ten mechanizm.