Nuta
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować się zalogować lub zmienić katalog.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Wskazówka
Kod w tym dokumencie można znaleźć w witrynie GitHub jako przykład z możliwością uruchomienia.
Kontekst
Śledzenie zmian oznacza, że EF Core automatycznie rozpoznaje, jakie zmiany zostały dokonane przez aplikację na załadowanym obiekcie jednostki, co pozwala na zapisanie tych zmian z powrotem w bazie danych po wywołaniu SaveChanges. EF Core zwykle wykonuje to przez zrobienie migawki instancji podczas jej ładowania z bazy danych i porównanie tej migawki z instancją przekazaną aplikacji.
Program EF Core zawiera wbudowaną logikę tworzenia migawek i porównywania większości standardowych typów używanych w bazach danych, więc użytkownicy zwykle nie muszą się martwić o ten temat. Jednak gdy właściwość jest mapowana za pośrednictwem konwertera wartości, program EF Core musi przeprowadzić porównanie dla dowolnych typów użytkowników, które mogą być złożone. Domyślnie EF Core używa domyślnego porównania równości definiowanego przez typy (np. metoda Equals); do wykonania migawek typy wartości są kopiowane, aby utworzyć migawkę, natomiast w przypadku typów referencyjnych nie ma kopiowania, a to samo wystąpienie jest używane jako migawka.
W przypadkach, gdy wbudowane zachowanie porównania nie jest odpowiednie, użytkownicy mogą udostępnić moduł porównujący wartości, który zawiera logikę tworzenia migawek, porównywania i obliczania kodu skrótu. Na przykład następujące polecenie konfiguruje konwersję wartości dla właściwości List<int>, która ma być konwertowana na ciąg JSON w bazie danych, i definiuje również odpowiedniego porównywacza wartości.
modelBuilder
.Entity<EntityType>()
.Property(e => e.MyListProperty)
.HasConversion(
v => JsonSerializer.Serialize(v, (JsonSerializerOptions)null),
v => JsonSerializer.Deserialize<List<int>>(v, (JsonSerializerOptions)null),
new ValueComparer<List<int>>(
(c1, c2) => c1.SequenceEqual(c2),
c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())),
c => c.ToList()));
Aby uzyskać więcej informacji, zobacz poniższe klasy modyfikowalne .
Należy pamiętać, że porównywacze wartości są również wykorzystywane do ustalenia, czy dwie wartości klucza są takie same podczas rozwiązywania relacji; zostało to wyjaśnione poniżej.
Płytkie versus głębokie porównanie
W przypadku małych, niezmiennych typów wartości, takich jak int, domyślna logika ef Core działa dobrze: wartość jest kopiowana as-is podczas migawek i porównywana z wbudowanym porównaniem równości typu. Podczas implementowania własnego porównania wartości ważne jest, aby rozważyć, czy logika głębokiego lub płytkiego porównania (i migawek) jest odpowiednia.
Rozważ tablice bajtów, które mogą być dowolnie duże. Można je porównać:
- Przy użyciu odwołania taka różnica jest wykrywana tylko wtedy, gdy jest używana nowa tablica bajtów
- Dzięki głębokiemu porównaniu wykrywa się mutację bajtów w tablicy.
Domyślnie program EF Core używa pierwszego z tych podejść dla tablic bajtów innych niż klucz. Oznacza to, że tylko odwołania są porównywane, a zmiana jest wykrywana tylko wtedy, gdy istniejąca tablica bajtów zostanie zamieniona na nową. Jest to pragmatyczna decyzja, która pozwala uniknąć kopiowania całych tablic i porównywania ich bajtów do bajtów podczas wykonywania polecenia SaveChanges. Oznacza to, że typowy scenariusz zamiany jednego obrazu na inny jest obsługiwany w wydajny sposób.
Z drugiej strony równość referencji nie będzie działać, gdy tablice bajtów są używane do reprezentowania kluczy binarnych, ponieważ jest bardzo mało prawdopodobne, że właściwość FK zostanie ustawiona na to samo wystąpienie co właściwość PK, z którą musi być porównana. W związku z tym program EF Core używa głębokich porównań dla tablic bajtów, które działają jako klucze; nie powinno to znacząco wpłynąć na wydajność, ponieważ klucze binarne są zwykle krótkie.
Należy pamiętać, że wybrana logika porównania i zrzutów pamięci powinna być zgodna ze sobą: głębokie porównanie wymaga głębokiego tworzenia zrzutów pamięci w celu poprawnego działania.
Proste niezmienne klasy
Rozważ właściwość, która używa konwertera wartości do mapowania prostej, niezmiennej klasy.
public sealed class ImmutableClass
{
public ImmutableClass(int value)
{
Value = value;
}
public int Value { get; }
private bool Equals(ImmutableClass other)
=> Value == other.Value;
public override bool Equals(object obj)
=> ReferenceEquals(this, obj) || obj is ImmutableClass other && Equals(other);
public override int GetHashCode()
=> Value.GetHashCode();
}
modelBuilder
.Entity<MyEntityType>()
.Property(e => e.MyProperty)
.HasConversion(
v => v.Value,
v => new ImmutableClass(v));
Właściwości tego typu nie wymagają specjalnych porównań ani migawek, ponieważ:
- Równość jest zastępowana, dzięki czemu różne wystąpienia będą prawidłowo porównywane
- Typ jest niezmienny, więc nie ma możliwości mutacji wartości migawki.
W tym przypadku domyślne zachowanie platformy EF Core jest w porządku, tak jak jest.
Proste niezmienne struktury
Mapowanie prostych struktur jest również proste i nie wymaga specjalnych porównań ani migawek.
public readonly struct ImmutableStruct
{
public ImmutableStruct(int value)
{
Value = value;
}
public int Value { get; }
}
modelBuilder
.Entity<EntityType>()
.Property(e => e.MyProperty)
.HasConversion(
v => v.Value,
v => new ImmutableStruct(v));
Program EF Core ma wbudowaną obsługę generowania skompilowanych porównań członek po członku właściwości struktury. Oznacza to, że struktury nie muszą mieć przesłonięć równości dla platformy EF Core, ale nadal możesz zdecydować się na to z innych powodów. Ponadto specjalne migawki nie są potrzebne, ponieważ struktury są niezmienne i zawsze kopiowane element po elemencie. (Dotyczy to również modyfikowalnych struktur, ale należy unikać modyfikowalnych struktur).
Klasy modyfikowalne
Zaleca się używanie niezmiennych typów (klas lub struktur) z konwerterami wartości, jeśli jest to możliwe. Jest to zwykle bardziej wydajne i ma czystszą semantykę niż użycie typu modyfikowalnego. Jednakże mimo to, często używa się właściwości typów, których aplikacja nie może zmienić. Na przykład mapowanie właściwości zawierającej listę liczb:
public List<int> MyListProperty { get; set; }
Klasa List<T>:
- Ma równość referencyjną; dwie listy zawierające te same wartości są traktowane jako różne.
- Jest modyfikowalny; wartości na liście można dodawać i usuwać.
Typowa konwersja wartości we właściwości listy może przekonwertować listę na i z formatu JSON:
modelBuilder
.Entity<EntityType>()
.Property(e => e.MyListProperty)
.HasConversion(
v => JsonSerializer.Serialize(v, (JsonSerializerOptions)null),
v => JsonSerializer.Deserialize<List<int>>(v, (JsonSerializerOptions)null),
new ValueComparer<List<int>>(
(c1, c2) => c1.SequenceEqual(c2),
c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())),
c => c.ToList()));
Konstruktor ValueComparer<T> akceptuje trzy wyrażenia:
- Wyrażenie do sprawdzania równości
- Wyrażenie służące do generowania kodu skrótu
- Wyrażenie do uchwycenia wartości
W takim przypadku porównanie jest wykonywane przez sprawdzenie, czy sekwencje liczb są takie same.
Podobnie kod skrótu jest tworzony na podstawie tej samej sekwencji. (Należy pamiętać, że jest to kod skrótu dla wartości modyfikowalnych, co może powodować problemy. Zamiast tego, jeśli to możliwe, należy być niemodyfikowalnym.)
Migawka jest tworzona przez sklonowanie listy za pomocą funkcji ToList. Ponownie jest to konieczne tylko wtedy, gdy listy zostaną zmutowane. Zamiast tego należy być niezmiennym, jeśli to możliwe.
Uwaga / Notatka
Konwertery wartości i porównania są konstruowane przy użyciu wyrażeń, a nie prostych delegatów. Dzieje się tak, ponieważ EF Core wstawia te wyrażenia do o wiele bardziej złożonego drzewa wyrażeń, które następnie jest kompilowane do delegata kształtującego encję. Koncepcyjnie jest to podobne do inlinowania kompilatora. Na przykład prosta konwersja może być tylko skompilowana w rzutowaniu, a nie wywołanie innej metody w celu przeprowadzenia konwersji.
Kluczowe porównania
W sekcji w tle opisano, dlaczego kluczowe porównania mogą wymagać specjalnej semantyki. Pamiętaj, aby utworzyć porównywarkę, która odpowiednio odpowiada kluczom podczas ustawiania go dla właściwości klucza podstawowego, klucza głównego lub klucza obcego.
Należy użyć SetKeyValueComparer w rzadkich przypadkach, w których różne semantyka jest wymagana w tej samej właściwości.
Uwaga / Notatka
SetStructuralValueComparer został uznany za przestarzały. Użyj SetKeyValueComparer zamiast tego.
Zastępowanie domyślnego komparatora
Czasami domyślne porównanie używane przez program EF Core może nie być odpowiednie. Na przykład mutacja tablic bajtowych nie jest domyślnie wykrywana w programie EF Core. Można to przesłonić, poprzez ustawienie innego komparatora dla właściwości.
modelBuilder
.Entity<EntityType>()
.Property(e => e.MyBytes)
.Metadata
.SetValueComparer(
new ValueComparer<byte[]>(
(c1, c2) => c1.SequenceEqual(c2),
c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())),
c => c.ToArray()));
Program EF Core będzie teraz porównywać sekwencje bajtów i w związku z tym wykrywa mutacje tablic bajtów.