Nawigacje po relacjach

Relacje programu EF Core są definiowane przez klucze obce. Nawigacje są nakładane na klucze obce, aby zapewnić naturalny, obiektowy widok do odczytywania i manipulowania relacjami. Korzystając z nawigacji, aplikacje mogą pracować z grafami jednostek bez obaw o to, co dzieje się z wartościami klucza obcego.

Ważne

Wiele relacji nie może udostępniać nawigacji. Każdy klucz obcy może być skojarzony z co najwyżej jedną nawigacją od podmiotu zabezpieczeń do zależnego, a co najwyżej jedną nawigację z zależności od podmiotu zabezpieczeń.

Napiwek

Nie ma potrzeby tworzenia nawigacji wirtualnych, chyba że są one używane przez serwery proxy z opóźnieniem ładowania lub śledzenia zmian.

Nawigacje referencyjne

Nawigacje są dostępne w dwóch formach — odwołanie i kolekcja. Nawigacje referencyjne to proste odwołania do obiektów do innej jednostki. Reprezentują one strony "jeden" relacji jeden do wielu i jeden do jednego . Na przykład:

public Blog TheBlog { get; set; }

Nawigacje referencyjne muszą mieć zestaw, chociaż nie musi być publiczne. Nawigacje referencyjne nie powinny być automatycznie inicjowane do wartości domyślnej innej niż null; Jest to równoważne twierdzeniu, że jednostka istnieje, gdy nie.

W przypadku korzystania z typów odwołań dopuszczanych wartości null w języku C# nawigacje referencyjne muszą mieć wartość null dla opcjonalnych relacji:

public Blog? TheBlog { get; set; }

Nawigacja referencyjna dla wymaganych relacji może być dopuszczana do wartości null lub nie może być równa null.

Nawigacje po kolekcjach

Nawigacje kolekcji to wystąpienia typu kolekcji .NET; oznacza to, że dowolny typ implementujący ICollection<T>. Kolekcja zawiera wystąpienia powiązanego typu jednostki, z których może istnieć dowolna liczba. Reprezentują one strony "wiele" relacji jeden do wielu i wiele do wielu . Na przykład:

public ICollection<Post> ThePosts { get; set; }

Nawigacje kolekcji nie muszą mieć zestawu. Często inicjuje się kolekcję w tekście, usuwając w ten sposób konieczność sprawdzania, czy właściwość ma wartość null. Na przykład:

public ICollection<Post> ThePosts { get; } = new List<Post>();

Napiwek

Nie należy przypadkowo tworzyć właściwości ciała wyrażenia, takiej jak public ICollection<Post> ThePosts => new List<Post>();. Spowoduje to utworzenie nowego, pustego wystąpienia kolekcji przy każdym uzyskiwaniu dostępu do właściwości i dlatego będzie bezużyteczne jako nawigacja.

Typy kolekcji

Bazowe wystąpienie kolekcji musi implementować ICollection<T>metodę i musi mieć metodę roboczą Add . Często jest używany program List<T> lub HashSet<T>. List<T> jest wydajny w przypadku niewielkiej liczby powiązanych jednostek i utrzymuje stabilną kolejność. HashSet<T> ma bardziej wydajne wyszukiwanie dla dużej liczby jednostek, ale nie ma stabilnej kolejności. Możesz również użyć własnej implementacji kolekcji niestandardowej.

Ważne

Kolekcja musi używać równości odwołań. Podczas tworzenia HashSet<T> elementu nawigacji dla kolekcji upewnij się, że używasz elementu ReferenceEqualityComparer.

Tablice nie mogą być używane na potrzeby nawigacji kolekcji, ponieważ mimo że implementują ICollection<T>metodę , Add metoda zgłasza wyjątek po wywołaniu.

Mimo że wystąpienie kolekcji musi być wystąpieniem ICollection<T>, kolekcja nie musi być widoczna jako taka. Na przykład często można uwidocznić nawigację jako IEnumerable<T>element , który udostępnia widok tylko do odczytu, którego nie można losowo modyfikować za pomocą kodu aplikacji. Na przykład:

public class Blog
{
    public int Id { get; set; }
    public IEnumerable<Post> ThePosts { get; } = new List<Post>();
}

Odmiana tego wzorca obejmuje metody manipulowania kolekcją zgodnie z potrzebami. Na przykład:

public class Blog
{
    private readonly List<Post> _posts = new();

    public int Id { get; set; }

    public IEnumerable<Post> Posts => _posts;

    public void AddPost(Post post) => _posts.Add(post);
}

Kod aplikacji nadal może rzutować ujawnioną kolekcję na obiekt ICollection<T> , a następnie manipulować nim. Jeśli jest to problem, jednostka może zwrócić defensywną kopię kolekcji. Na przykład:

public class Blog
{
    private readonly List<Post> _posts = new();

    public int Id { get; set; }

    public IEnumerable<Post> Posts => _posts.ToList();

    public void AddPost(Post post) => _posts.Add(post);
}

Dokładnie zastanów się, czy wartość uzyskana z tego poziomu jest wystarczająco wysoka, że przewyższa obciążenie związane z tworzeniem kopii kolekcji za każdym razem, gdy uzyskuje się dostęp do nawigacji.

Napiwek

Ten ostatni wzorzec działa, ponieważ domyślnie program EF uzyskuje dostęp do kolekcji za pośrednictwem pola zapasowego. Oznacza to, że sama platforma EF dodaje i usuwa jednostki z rzeczywistej kolekcji, podczas gdy aplikacje wchodzą w interakcję tylko z defensywną kopią kolekcji.

Inicjowanie nawigacji kolekcji

Nawigacje po kolekcjach mogą być inicjowane przez typ jednostki — z niecierpliwością:

public class Blog
{
    public ICollection<Post> Posts { get; } = new List<Post>();
}

Lub leniwie:

public class Blog
{
    private ICollection<Post>? _posts;

    public ICollection<Post> Posts => _posts ??= new List<Post>();
}

Jeśli platforma EF musi dodać jednostkę do nawigacji kolekcji, na przykład podczas wykonywania zapytania, zainicjuje kolekcję, jeśli jest obecnie null. Utworzone wystąpienie zależy od uwidocznionego typu nawigacji.

  • Jeśli nawigacja jest uwidoczniona jako HashSet<T>element , zostanie utworzone wystąpienie HashSet<T> funkcji using ReferenceEqualityComparer .
  • W przeciwnym razie, jeśli nawigacja jest uwidoczniona jako typ betonowy z konstruktorem bez parametrów, tworzone jest wystąpienie tego typu konkretnego. Dotyczy List<T>to programu , ale także innych typów kolekcji, w tym niestandardowych typów kolekcji.
  • W przeciwnym razie, jeśli nawigacja jest uwidoczniona jako IEnumerable<T>, ICollection<T>lub ISet<T>, zostanie utworzone wystąpienie HashSet<T> użycia ReferenceEqualityComparer .
  • W przeciwnym razie, jeśli nawigacja jest uwidoczniona jako IList<T>, zostanie utworzone wystąpienie klasy List<T> .
  • W przeciwnym razie jest zgłaszany wyjątek.

Uwaga

Jeśli są używane jednostki powiadomień, w tym serwery proxy śledzenia zmian, są ObservableCollection<T> używane i ObservableHashSet<T> są używane zamiast List<T> elementów i HashSet<T>.

Ważne

Zgodnie z opisem w dokumentacji śledzenia zmian program EF śledzi tylko jedno wystąpienie dowolnej jednostki z daną wartością klucza. Oznacza to, że kolekcje używane jako nawigacje muszą używać semantyki równości odwołań. Typy jednostek, które nie zastępują równości obiektów, będą domyślnie uzyskiwać tę wartość. Pamiętaj, aby używać ReferenceEqualityComparer elementu podczas tworzenia elementu HashSet<T> do użycia jako nawigacji, aby upewnić się, że działa on dla wszystkich typów jednostek.

Konfigurowanie nawigacji

Nawigacje są uwzględniane w modelu w ramach konfigurowania relacji. Oznacza to, zgodnie z konwencją lub przy użyciu HasOnemetody , HasManyitp. w interfejsie API tworzenia modelu. Większość konfiguracji skojarzonych z nawigacjami odbywa się przez skonfigurowanie samej relacji.

Istnieją jednak pewne typy konfiguracji, które są specyficzne dla samych właściwości nawigacji, a nie są częścią ogólnej konfiguracji relacji. Ten typ konfiguracji jest wykonywany przy użyciu Navigation metody . Aby na przykład wymusić na platformie EF dostęp do nawigacji za pośrednictwem jej właściwości, zamiast używać pola zapasowego:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .Navigation(e => e.Posts)
        .UsePropertyAccessMode(PropertyAccessMode.Property);

    modelBuilder.Entity<Post>()
        .Navigation(e => e.Blog)
        .UsePropertyAccessMode(PropertyAccessMode.Property);
}

Uwaga

Nie Navigation można użyć wywołania do utworzenia właściwości nawigacji. Służy tylko do konfigurowania właściwości nawigacji, która została wcześniej utworzona przez zdefiniowanie relacji lub z konwencji.

Wymagane nawigacje

Jeśli relacja jest wymagana, wymagana jest nawigacja od zależnego od podmiotu zabezpieczeń, co z kolei oznacza, że właściwość klucza obcego jest niepusta. Z drugiej strony nawigacja jest opcjonalna, jeśli klucz obcy jest dopuszczany do wartości null, a zatem relacja jest opcjonalna.

Nawigacje odwołania od podmiotu zabezpieczeń do zależności są różne. W większości przypadków jednostka główna może zawsze istnieć bez żadnych jednostek zależnych. Oznacza to, że wymagana relacja nie wskazuje, że zawsze będzie istnieć co najmniej jedna jednostka zależna. W modelu EF nie ma możliwości, a także nie ma standardowego sposobu w relacyjnej bazie danych, aby upewnić się, że podmiot zabezpieczeń jest skojarzony z określoną liczbą zależności. Jeśli jest to konieczne, należy zaimplementować ją w logice aplikacji (biznesowej).

Istnieje jeden wyjątek od tej reguły — gdy podmiot zabezpieczeń i typy zależne współużytkują tę samą tabelę w relacyjnej bazie danych lub zawarte w dokumencie. Może się to zdarzyć w przypadku typów należących do użytkownika lub typów innych niż należące do tej samej tabeli. W takim przypadku właściwość nawigacji od podmiotu zabezpieczeń do zależności może być oznaczona jako wymagana, co oznacza, że zależność musi istnieć.

Konfiguracja właściwości nawigacji zgodnie z wymaganiami jest wykonywana Navigation przy użyciu metody . Na przykład:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .Navigation(e => e.BlogHeader)
        .IsRequired();
}