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.
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 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();
}
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.