Udostępnij za pośrednictwem


Nowe funkcje w rozwiązaniu EF Core 2.0

.NET Standard 2.0

Rozwiązanie EF Core jest teraz przeznaczone dla platformy .NET Standard 2.0, co oznacza, że może współpracować z platformą .NET Core 2.0, .NET Framework 4.6.1 i innymi bibliotekami, które implementują platformę .NET Standard 2.0. Aby uzyskać więcej informacji na temat tego, co jest obsługiwane, zobacz Obsługiwane implementacje platformy .NET.

Modelowanie

Dzielenie tabeli

Teraz można zamapować dwa lub więcej typów jednostek na tę samą tabelę, w której będą współużytkowane kolumny klucza podstawowego, a każdy wiersz będzie odpowiadać co najmniej dwu jednostkom.

Aby użyć dzielenia tabel, relacja identyfikująca (gdzie właściwości klucza obcego tworzą klucz podstawowy) musi być skonfigurowana między wszystkimi typami jednostek współużytkujących tabelę:

modelBuilder.Entity<Product>()
    .HasOne(e => e.Details).WithOne(e => e.Product)
    .HasForeignKey<ProductDetails>(e => e.Id);
modelBuilder.Entity<Product>().ToTable("Products");
modelBuilder.Entity<ProductDetails>().ToTable("Products");

Aby uzyskać więcej informacji na temat tej funkcji, przeczytaj sekcję dotyczącą dzielenia tabel.

Typy własnościowe

Własnościowy typ jednostki może współdzielić ten sam typ platformy .NET z innym własnościowym typem jednostki, ale ponieważ nie można go zidentyfikować tylko po samym typie platformy .NET, musi istnieć nawigacja do niego z innego typu jednostki. Jednostka zawierająca nawigację definiującą jest właścicielem. Podczas wykonywania zapytań względem właściciela domyślnie będą dołączane typy własnościowe.

Zgodnie z konwencją towarzyszący klucz podstawowy zostanie utworzony dla typu własnościowego i zostanie zamapowany na tę samą tabelę co właściciel przy użyciu dzielenia tabeli. Dzięki temu można używać typów własnościowych w sposób zbliżony do tego, jak typy złożone są używane w rozwiązaniu EF6:

modelBuilder.Entity<Order>().OwnsOne(p => p.OrderDetails, cb =>
    {
        cb.OwnsOne(c => c.BillingAddress);
        cb.OwnsOne(c => c.ShippingAddress);
    });

public class Order
{
    public int Id { get; set; }
    public OrderDetails OrderDetails { get; set; }
}

public class OrderDetails
{
    public StreetAddress BillingAddress { get; set; }
    public StreetAddress ShippingAddress { get; set; }
}

public class StreetAddress
{
    public string Street { get; set; }
    public string City { get; set; }
}

Aby uzyskać więcej informacji na temat tej funkcji, przeczytaj sekcję dotyczącą własnościowych typów jednostek.

Filtry zapytań na poziomie modelu

Rozwiązanie EF Core 2.0 ma nową funkcję, którą nazywany filtrami zapytań na poziomie modelu. Ta funkcja umożliwia stosowanie predykatów zapytań LINQ (wyrażenie logiczne zwykle przekazywane do operatora zapytania Where LINQ) bezpośrednio w typach jednostek w modelu metadanych (zwykle w zdarzeniu OnModelCreating). Takie filtry są automatycznie stosowane do wszystkich zapytań LINQ obejmujących te typy jednostek, w tym typy jednostek przywoływane pośrednio, na przykład za pomocą właściwości Include, lub bezpośrednie odwołania do właściwości nawigacji. Oto niektóre typowe zastosowania tej funkcji:

  • Usuwanie nietrwałe — typy jednostek definiują właściwość IsDeleted.
  • Wielodostępność — typ jednostki definiuje właściwość TenantId.

Oto prosty przykład pokazujący funkcję dla dwóch scenariuszy wymienionych powyżej:

public class BloggingContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }

    public int TenantId { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Post>().HasQueryFilter(
            p => !p.IsDeleted
            && p.TenantId == this.TenantId);
    }
}

Definiujemy filtr na poziomie modelu, który implementuje wielodostępność i usuwanie nietrwałe dla wystąpień typu jednostki Post. Zwróć uwagę na użycie właściwości na poziomie wystąpienia DbContext: TenantId. Filtry na poziomie modelu będą używać wartości z poprawnego wystąpienia kontekstu (czyli wystąpienia kontekstu, które wykonuje zapytanie).

Filtry mogą być wyłączone dla poszczególnych zapytań LINQ przy użyciu operatora IgnoreQueryFilters().

Ograniczenia

  • Odwołania do nawigacji są niedozwolone. Ta funkcja może zostać dodana zależnie od opinii.
  • Filtry można zdefiniować tylko w głównym typie jednostki w hierarchii.

Mapowanie skalarnych funkcji bazy danych

Program EF Core 2.0 zawiera ważną kontrybucję Paula Middletona, która umożliwia mapowanie funkcji skalarnych bazy danych na metody zastępcze, dzięki czemu mogą być używane w zapytaniach LINQ i tłumaczone na język SQL.

Poniżej przedstawiono krótki opis sposobu użycia funkcji:

Zadeklaruj metodę statyczną w elemencie DbContext i dodaj do niej adnotację za pomocą elementu DbFunctionAttribute:

public class BloggingContext : DbContext
{
    [DbFunction]
    public static int PostReadCount(int blogId)
    {
        throw new NotImplementedException();
    }
}

Metody takie jak te są automatycznie rejestrowane. Po zarejestrowaniu wywołania metody w zapytaniu LINQ mogą być tłumaczone na wywołania funkcji w języku SQL:

var query =
    from p in context.Posts
    where BloggingContext.PostReadCount(p.Id) > 5
    select p;

Należy zwrócić uwagę na kilka rzeczy:

  • Zgodnie z konwencją nazwa metody jest używana jako nazwa funkcji (w tym przypadku funkcji zdefiniowanej przez użytkownika) podczas generowania kodu SQL, ale można przesłonić nazwę i schemat podczas rejestracji metody.
  • Obecnie obsługiwane są tylko funkcje skalarne.
  • Mapowaną funkcję trzeba utworzyć w bazie danych. Podczas migracji rozwiązania EF Core nie zostaną one utworzone.

Konfiguracja typu niezależnego dla podejścia „najpierw kod”

W rozwiązaniu EF6 można było hermetyzować konfigurację „najpierw kod” określonego typu jednostki przez wyprowadzenie z klasy EntityTypeConfiguration. W rozwiązaniu EF Core 2.0 przywracamy ten wzorzec:

class CustomerConfiguration : IEntityTypeConfiguration<Customer>
{
    public void Configure(EntityTypeBuilder<Customer> builder)
    {
        builder.HasKey(c => c.AlternateKey);
        builder.Property(c => c.Name).HasMaxLength(200);
    }
}

...
// OnModelCreating
builder.ApplyConfiguration(new CustomerConfiguration());

Wysoka wydajność

Buforowanie obiektów DbContext

Podstawowy wzorzec korzystania z rozwiązania EF Core w aplikacji ASP.NET Core zwykle obejmuje zarejestrowanie niestandardowego typu DbContext w systemie wstrzykiwania zależności, a następnie uzyskanie wystąpień tego typu za pomocą parametrów konstruktora w kontrolerach. Oznacza to, że dla każdego żądania jest tworzone nowe wystąpienie obiektu DbContext.

W wersji 2.0 wprowadzamy nowy sposób rejestrowania niestandardowych typów DbContext we wstrzyknięciu zależności, co w sposób transparentny wprowadza pulę wystąpień obiektów DbContext wielokrotnego użytku. Aby użyć puli obiektów DbContext, użyj elementu AddDbContextPool zamiast elementu AddDbContext podczas rejestracji usługi:

services.AddDbContextPool<BloggingContext>(
    options => options.UseSqlServer(connectionString));

Jeśli ta metoda zostanie użyta, w chwili zażądania wystąpienia obiektu DbContext przez kontroler najpierw sprawdzimy, czy w puli jest dostępne wystąpienie. Po zakończeniu przetwarzania żądania każdy stan wystąpienia zostanie zresetowany, a wystąpienie zostanie zwrócone do puli.

Jest to koncepcyjnie podobne do sposobu działania puli połączeń w dostawcach ADO.NET i ma tę przewagę, że daje pewną oszczędność na koszcie inicjowania wystąpienia obiektu DbContext.

Ograniczenia

Nowa metoda wprowadza kilka ograniczeń dotyczących tego, co można zrobić w metodzie OnConfiguring() obiektu DbContext.

Ostrzeżenie

Unikaj używania buforowania obiektów DbContext w przypadku utrzymywania własnego stanu (na przykład pól prywatnych) w pochodnej klasie DbContext, który nie powinien być współdzielony między żądaniami. Przed dodaniem wystąpienia obiektu DbContext do puli rozwiązanie EF Core zresetuje tylko stan, o którym wie.

Jawnie kompilowane zapytania

Jest to druga opcjonalna funkcja wydajności zaprojektowana w celu oferowania korzyści w scenariuszach o dużej skali.

Ręcznie lub wprost kompilowane interfejsy API zapytań są dostępne w poprzednich wersjach rozwiązania EF, a także w rozwiązaniu LINQ to SQL, aby umożliwić aplikacjom buforowanie tłumaczenia zapytań, dzięki czemu można je przetworzyć tylko raz i wykonać wiele razy.

Chociaż ogólnie rozwiązanie EF Core może automatycznie kompilować i buforować zapytania na podstawie skrótowej reprezentacji wyrażeń zapytań, ten mechanizm może służyć do uzyskania niewielkiego wzrostu wydajności, pomijając obliczanie skrótu i wyszukiwanie w pamięci podręcznej oraz umożliwiając aplikacji używanie już skompilowanego zapytania za pośrednictwem wywołania delegata.

// Create an explicitly compiled query
private static Func<CustomerContext, int, Customer> _customerById =
    EF.CompileQuery((CustomerContext db, int id) =>
        db.Customers
            .Include(c => c.Address)
            .Single(c => c.Id == id));

// Use the compiled query by invoking it
using (var db = new CustomerContext())
{
   var customer = _customerById(db, 147);
}

Śledzenie zmian

Metoda Attach może śledzić graf nowych i istniejących jednostek

Rozwiązanie EF Core obsługuje automatyczne generowanie wartości kluczy za pomocą różnych mechanizmów. W przypadku korzystania z tej funkcji wartość jest generowana, jeśli właściwość klucza jest wartością domyślną CLR — zwykle zero lub null. Oznacza to, że graf jednostek można przekazać do metody DbContext.Attach lub DbSet.Attach, a rozwiązanie EF Core oznaczy jednostki z już ustawionym kluczem jako Unchanged, a jednostki bez ustawionego klucza jako Added. Ułatwia to dołączanie grafu zawierającego nowe i istniejące jednostki podczas korzystania z wygenerowanych kluczy. Metody DbContext.Update i DbSet.Update działają w ten sam sposób, z tą różnicą, że jednostki z ustawionym kluczem są oznaczone jako Modified zamiast Unchanged.

Zapytanie

Ulepszone tłumaczenie zapytań LINQ

Umożliwia pomyślne wykonanie większej liczby zapytań, a większa liczba logiki jest obliczana w bazie danych (a nie w pamięci) i mniej danych jest niepotrzebnie pobieranych z bazy danych.

Ulepszenia operatora GroupJoin

Poprawiają one język SQL generowany dla sprzężeń grupowanych. Sprzężenia grupowane są najczęściej wynikiem podzapytań dotyczących opcjonalnych właściwości nawigacji.

Interpolacja ciągów w funkcjach FromSql i ExecuteSqlCommand

Język C# 6 wprowadził interpolację ciągów, czyli funkcję, która umożliwia osadzanie wyrażeń języka C# bezpośrednio w literałach ciągów, zapewniając wygodny sposób tworzenia ciągów w czasie wykonywania. W rozwiązaniu EF Core 2.0 dodaliśmy specjalną obsługę ciągów interpolowanych do naszych dwóch podstawowych interfejsów API, które akceptują nieprzetworzone ciągi SQL: FromSql i ExecuteSqlCommand. Ta nowa obsługa umożliwia użycie interpolacji ciągów języka C# w sposób „bezpieczny”. Oznacza to sposób, który chroni przed typowymi pomyłkami wstrzykiwania kodu SQL, które mogą wystąpić podczas dynamicznego konstruowania bazy danych SQL w czasie wykonywania.

Oto przykład:

var city = "London";
var contactTitle = "Sales Representative";

using (var context = CreateContext())
{
    context.Set<Customer>()
        .FromSql($@"
            SELECT *
            FROM ""Customers""
            WHERE ""City"" = {city} AND
                ""ContactTitle"" = {contactTitle}")
            .ToArray();
  }

W tym przykładzie w ciągu formatu SQL są osadzone dwie zmienne. Rozwiązanie EF Core utworzy następujący kod SQL:

@p0='London' (Size = 4000)
@p1='Sales Representative' (Size = 4000)

SELECT *
FROM ""Customers""
WHERE ""City"" = @p0
    AND ""ContactTitle"" = @p1

EF.Functions.Like()

Dodaliśmy właściwość EF.Functions, która może być używana przez rozwiązanie EF Core lub dostawców do definiowania metod mapujących na funkcje bazy danych lub operatory, aby można je było wywołać w zapytaniach LINQ. Pierwszym przykładem takiej metody jest Like():

var aCustomers =
    from c in context.Customers
    where EF.Functions.Like(c.Name, "a%")
    select c;

Należy pamiętać, że metoda Like() ma implementację w pamięci, która może być przydatna podczas pracy z bazą danych w pamięci lub w przypadku konieczności wykonania oceny predykatu po stronie klienta.

Zarządzanie bazami danych

Wpięcie pluralizacji na potrzeby tworzenia szkieletu klasy DbContext

Rozwiązanie EF Core 2.0 wprowadza nową usługę IPluralizer, która służy do singularyzacji nazw typów jednostek i pluralizacji nazw klasy DbSet. Domyślna implementacja jest typu no-op, więc jest to tylko wpięcie, w którym można łatwo podłączyć własny pluralizator.

Oto jak dla dewelopera wygląda możliwość wpięcia własnego pluralizatora:

public class MyDesignTimeServices : IDesignTimeServices
{
    public void ConfigureDesignTimeServices(IServiceCollection services)
    {
        services.AddSingleton<IPluralizer, MyPluralizer>();
    }
}

public class MyPluralizer : IPluralizer
{
    public string Pluralize(string name)
    {
        return Inflector.Inflector.Pluralize(name) ?? name;
    }

    public string Singularize(string name)
    {
        return Inflector.Inflector.Singularize(name) ?? name;
    }
}

Inne

Przenoszenie dostawcy ADO.NET SQLite do pliku SQLitePCL.raw

Zapewnia to bardziej niezawodne rozwiązanie w pakiecie Microsoft.Data.Sqlite do dystrybucji natywnych plików binarnych SQLite na różnych platformach.

Tylko jeden dostawca na model

Znacznie rozszerza sposób interakcji dostawców z modelem i upraszcza sposób pracy konwencji, adnotacji i płynnych interfejsów API z różnymi dostawcami.

Rozwiązanie EF Core 2.0 tworzy teraz inny interfejs IModel dla każdego używanego dostawcy. Jest to zwykle transparentne dla aplikacji. Ułatwiło to uproszczenie interfejsów API metadanych niższego poziomu, tak aby każdy dostęp do typowych pojęć dotyczących metadanych relacyjnych zawsze odbywał się za pośrednictwem wywołania metody .Relational zamiast .SqlServer, .Sqlite itp.

Skonsolidowane rejestrowanie i diagnostyka

Mechanizmy rejestrowania (oparte na interfejsie ILogger) i diagnostyki (oparte na klasie DiagnosticSource) współdzielą teraz więcej kodu.

Identyfikatory zdarzeń dla komunikatów wysyłanych do interfejsu ILogger uległy zmianie w wersji 2.0. Identyfikatory zdarzeń są teraz unikatowe w całym kodzie rozwiązania EF Core. Te komunikaty są teraz również zgodne ze standardowym wzorcem rejestrowania strukturalnego używanym przez np. wzorzec MVC.

Kategorie rejestratorów również uległy zmianie. Istnieje teraz dobrze znany zestaw kategorii, do których uzyskuje się dostęp za pośrednictwem klasy DbLoggerCategory.

Zdarzenia klasy DiagnosticSource używają teraz tych samych nazw identyfikatorów zdarzeń co odpowiednie komunikaty interfejsu ILogger.