Uzyskiwanie dostępu do śledzonych jednostek
Istnieją cztery główne interfejsy API służące do uzyskiwania dostępu do jednostek śledzonych przez element DbContext:
- DbContext.EntryEntityEntry<TEntity> Zwraca wystąpienie dla danego wystąpienia jednostki.
- ChangeTracker.Entries zwraca EntityEntry<TEntity> wystąpienia dla wszystkich śledzonych jednostek lub dla wszystkich śledzonych jednostek danego typu.
- DbContext.Find, , DbContext.FindAsyncDbSet<TEntity>.Findi DbSet<TEntity>.FindAsync znajdź pojedynczą jednostkę według klucza podstawowego, najpierw wyszukując śledzone jednostki, a następnie wysyłając zapytanie do bazy danych w razie potrzeby.
- DbSet<TEntity>.Local Zwraca rzeczywiste jednostki (a nie wystąpienia EntityEntry) dla jednostek typu jednostki reprezentowanej przez zestaw dbset.
Każdy z nich został opisany bardziej szczegółowo w poniższych sekcjach.
Napiwek
W tym dokumencie przyjęto założenie, że stany jednostki i podstawy śledzenia zmian platformy EF Core są zrozumiałe. Aby uzyskać więcej informacji na temat tych tematów, zobacz Change Tracking in EF Core (Śledzenie zmian w programie EF Core ).
Napiwek
Możesz uruchomić i debugować cały kod podany w tym dokumencie, pobierając przykładowy kod z serwisu GitHub.
Używanie wystąpień DbContext.Entry i EntityEntry
Dla każdej śledzonej jednostki program Entity Framework Core (EF Core) śledzi następujące elementy:
- Ogólny stan jednostki. Jest to jeden z
Unchanged
elementów ,Modified
,Added
lubDeleted
; zobacz Change Tracking in EF Core (Śledzenie zmian w programie EF Core ), aby uzyskać więcej informacji. - Relacje między śledzonych jednostek. Na przykład blog, do którego należy wpis.
- "bieżące wartości" właściwości.
- "oryginalne wartości" właściwości, gdy te informacje są dostępne. Oryginalne wartości to wartości właściwości, które istniały podczas wykonywania zapytań o jednostkę z bazy danych.
- Które wartości właściwości zostały zmodyfikowane od czasu ich odpyt.
- Inne informacje o wartościach właściwości, takie jak to, czy wartość jest tymczasowa.
Przekazanie wystąpienia jednostki w celu DbContext.Entry uzyskania EntityEntry<TEntity> dostępu do tych informacji dla danej jednostki. Przykład:
using var context = new BlogsContext();
var blog = context.Blogs.Single(e => e.Id == 1);
var entityEntry = context.Entry(blog);
W poniższych sekcjach pokazano, jak używać jednostki EntityEntry do uzyskiwania dostępu do stanu jednostki i manipulowania nim, a także stanu właściwości i nawigacji jednostki.
Praca z jednostką
Najczęstszym zastosowaniem programu EntityEntry<TEntity> jest uzyskanie dostępu do bieżącej EntityState jednostki. Przykład:
var currentState = context.Entry(blog).State;
if (currentState == EntityState.Unchanged)
{
context.Entry(blog).State = EntityState.Modified;
}
Metoda Entry może być również używana w jednostkach, które nie są jeszcze śledzone. Nie rozpoczyna to śledzenia jednostki; stan jednostki to nadal Detached
. Jednak zwrócona jednostka EntityEntry może następnie służyć do zmiany stanu jednostki, w którym to momencie jednostka zostanie śledzona w danym stanie. Na przykład następujący kod rozpocznie śledzenie wystąpienia bloga jako Added
:
var newBlog = new Blog();
Debug.Assert(context.Entry(newBlog).State == EntityState.Detached);
context.Entry(newBlog).State = EntityState.Added;
Debug.Assert(context.Entry(newBlog).State == EntityState.Added);
Napiwek
W przeciwieństwie do platformy EF6 ustawienie stanu pojedynczej jednostki nie spowoduje śledzenia wszystkich połączonych jednostek. Dzięki temu ustawienie stanu w ten sposób jest operacją niższego poziomu niż wywoływanie Add
operacji , Attach
lub Update
, które działają na całym grafie jednostek.
W poniższej tabeli przedstawiono podsumowanie sposobów użycia jednostki EntityEntry do pracy z całą jednostką:
Element członkowski EntityEntry | opis |
---|---|
EntityEntry.State | Pobiera i ustawia EntityState jednostkę. |
EntityEntry.Entity | Pobiera wystąpienie jednostki. |
EntityEntry.Context | Element DbContext śledzący tę jednostkę. |
EntityEntry.Metadata | IEntityType metadanych typu jednostki. |
EntityEntry.IsKeySet | Określa, czy jednostka ma ustawioną wartość klucza. |
EntityEntry.Reload() | Zastępuje wartości właściwości wartościami odczytanymi z bazy danych. |
EntityEntry.DetectChanges() | Wymusza wykrywanie zmian tylko dla tej jednostki; zobacz Wykrywanie zmian i powiadomienia. |
Praca z jedną właściwością
Kilka przeciążeń zezwalania EntityEntry<TEntity>.Property na dostęp do informacji o poszczególnych właściwościach jednostki. Na przykład przy użyciu silnie typizowanego, płynnego interfejsu API:
PropertyEntry<Blog, string> propertyEntry = context.Entry(blog).Property(e => e.Name);
Zamiast tego nazwę właściwości można przekazać jako ciąg. Przykład:
PropertyEntry<Blog, string> propertyEntry = context.Entry(blog).Property<string>("Name");
Zwrócony PropertyEntry<TEntity,TProperty> element może następnie służyć do uzyskiwania dostępu do informacji o właściwości. Na przykład może służyć do pobierania i ustawiania bieżącej wartości właściwości w tej jednostce:
string currentValue = context.Entry(blog).Property(e => e.Name).CurrentValue;
context.Entry(blog).Property(e => e.Name).CurrentValue = "1unicorn2";
Obie metody właściwości użyte powyżej zwracają silnie typizowane wystąpienie ogólne PropertyEntry<TEntity,TProperty> . Użycie tego typu ogólnego jest preferowane, ponieważ umożliwia dostęp do wartości właściwości bez typów wartości pól. Jeśli jednak typ jednostki lub właściwości nie jest znany w czasie kompilacji, można uzyskać zamiast tego nieogólne PropertyEntry :
PropertyEntry propertyEntry = context.Entry(blog).Property("Name");
Umożliwia to dostęp do informacji o właściwości dla dowolnej właściwości niezależnie od typu, kosztem typów wartości boxingu. Przykład:
object blog = context.Blogs.Single(e => e.Id == 1);
object currentValue = context.Entry(blog).Property("Name").CurrentValue;
context.Entry(blog).Property("Name").CurrentValue = "1unicorn2";
Poniższa tabela zawiera podsumowanie informacji o właściwości uwidocznionych przez właściwośćEntry:
Element członkowski PropertyEntry | opis |
---|---|
PropertyEntry<TEntity,TProperty>.CurrentValue | Pobiera i ustawia bieżącą wartość właściwości. |
PropertyEntry<TEntity,TProperty>.OriginalValue | Pobiera i ustawia oryginalną wartość właściwości, jeśli jest dostępna. |
PropertyEntry<TEntity,TProperty>.EntityEntry | Odwołanie wsteczne EntityEntry<TEntity> do elementu dla jednostki. |
PropertyEntry.Metadata | IProperty metadane właściwości . |
PropertyEntry.IsModified | Wskazuje, czy ta właściwość jest oznaczona jako zmodyfikowana i umożliwia zmianę tego stanu. |
PropertyEntry.IsTemporary | Wskazuje, czy ta właściwość jest oznaczona jako tymczasowa i umożliwia zmianę tego stanu. |
Uwagi:
- Oryginalna wartość właściwości to wartość, którą właściwość miała podczas wykonywania zapytania o jednostkę z bazy danych. Jednak oryginalne wartości nie są dostępne, jeśli jednostka została odłączona, a następnie jawnie dołączona do innej klasy DbContext, na przykład z elementem
Attach
lubUpdate
. W takim przypadku zwrócona oryginalna wartość będzie taka sama jak bieżąca wartość. - SaveChanges Zaktualizuje tylko właściwości oznaczone jako zmodyfikowane. Ustaw IsModified wartość true, aby wymusić aktualizację danej wartości właściwości przez program EF Core lub ustawić ją na wartość false, aby zapobiec aktualizowaniu wartości właściwości przez program EF Core.
- Wartości tymczasowe są zwykle generowane przez generatory wartości platformy EF Core. Ustawienie bieżącej wartości właściwości spowoduje zastąpienie wartości tymczasowej daną wartością i oznaczenie właściwości jako nie tymczasowej. Ustaw IsTemporary wartość true, aby wymusić tymczasowe ustawienie wartości nawet po jej jawnym ustawieniu.
Praca z pojedynczą nawigacją
Kilka przeciążeń elementów EntityEntry<TEntity>.Reference, EntityEntry<TEntity>.Collectioni EntityEntry.Navigation umożliwia dostęp do informacji o poszczególnych nawigacji.
Dostęp do nawigacji odwołań do pojedynczej powiązanej jednostki jest uzyskiwany za pośrednictwem Reference metod. Nawigacja referencyjna wskazuje strony "jeden" relacji jeden-do-wielu i obie strony relacji jeden do jednego. Przykład:
ReferenceEntry<Post, Blog> referenceEntry1 = context.Entry(post).Reference(e => e.Blog);
ReferenceEntry<Post, Blog> referenceEntry2 = context.Entry(post).Reference<Blog>("Blog");
ReferenceEntry referenceEntry3 = context.Entry(post).Reference("Blog");
Nawigacje mogą być również kolekcjami powiązanych jednostek, gdy są używane dla relacji "wiele do wielu" i relacji wiele-do-wielu. Metody Collection są używane do uzyskiwania dostępu do nawigacji kolekcji. Przykład:
CollectionEntry<Blog, Post> collectionEntry1 = context.Entry(blog).Collection(e => e.Posts);
CollectionEntry<Blog, Post> collectionEntry2 = context.Entry(blog).Collection<Post>("Posts");
CollectionEntry collectionEntry3 = context.Entry(blog).Collection("Posts");
Niektóre operacje są wspólne dla wszystkich nawigacji. Dostęp do nich można uzyskać zarówno w przypadku nawigacji odwołań, jak i kolekcji przy użyciu EntityEntry.Navigation metody . Należy pamiętać, że dostęp do wszystkich nawigacji jest dostępny tylko wtedy, gdy dostęp do wszystkich nawigacji jest dostępny. Przykład:
NavigationEntry navigationEntry = context.Entry(blog).Navigation("Posts");
W poniższej tabeli przedstawiono podsumowanie sposobów używania elementów ReferenceEntry<TEntity,TProperty>, CollectionEntry<TEntity,TRelatedEntity>i NavigationEntry:
Element członkowski NavigationEntry | opis |
---|---|
MemberEntry.CurrentValue | Pobiera i ustawia bieżącą wartość nawigacji. Jest to cała kolekcja nawigacji kolekcji. |
NavigationEntry.Metadata | INavigationBase metadane nawigacji. |
NavigationEntry.IsLoaded | Pobiera lub ustawia wartość wskazującą, czy powiązana jednostka lub kolekcja została w pełni załadowana z bazy danych. |
NavigationEntry.Load() | Ładuje powiązaną jednostkę lub kolekcję z bazy danych; zobacz Jawne ładowanie powiązanych danych. |
NavigationEntry.Query() | Zapytanie EF Core służy do ładowania tej nawigacji jako elementu IQueryable , który może być bardziej złożony. Zobacz Jawne ładowanie powiązanych danych. |
Praca ze wszystkimi właściwościami jednostki
EntityEntry.Properties zwraca wartość dla IEnumerable<T> PropertyEntry każdej właściwości jednostki. Może to służyć do wykonywania akcji dla każdej właściwości jednostki. Aby na przykład ustawić dowolną właściwość DateTime na :DateTime.Now
foreach (var propertyEntry in context.Entry(blog).Properties)
{
if (propertyEntry.Metadata.ClrType == typeof(DateTime))
{
propertyEntry.CurrentValue = DateTime.Now;
}
}
Ponadto entityEntry zawiera kilka metod pobierania i ustawiania wszystkich wartości właściwości w tym samym czasie. Te metody używają PropertyValues klasy, która reprezentuje kolekcję właściwości i ich wartości. WłaściwościValue można uzyskać dla bieżących lub oryginalnych wartości lub wartości, które są obecnie przechowywane w bazie danych. Przykład:
var currentValues = context.Entry(blog).CurrentValues;
var originalValues = context.Entry(blog).OriginalValues;
var databaseValues = context.Entry(blog).GetDatabaseValues();
Te obiekty PropertyValues nie są bardzo przydatne samodzielnie. Można je jednak połączyć w celu wykonywania typowych operacji potrzebnych podczas manipulowania jednostkami. Jest to przydatne podczas pracy z obiektami transferu danych i rozwiązywania optymistycznych konfliktów współbieżności. W poniższych sekcjach przedstawiono kilka przykładów.
Ustawianie bieżących lub oryginalnych wartości z jednostki lub obiektu DTO
Bieżące lub oryginalne wartości jednostki można zaktualizować, kopiując wartości z innego obiektu. Rozważmy na przykład BlogDto
obiekt transferu danych (DTO) z tymi samymi właściwościami co typ jednostki:
public class BlogDto
{
public int Id { get; set; }
public string Name { get; set; }
}
Może to służyć do ustawiania bieżących wartości śledzonej jednostki przy użyciu polecenia PropertyValues.SetValues:
var blogDto = new BlogDto { Id = 1, Name = "1unicorn2" };
context.Entry(blog).CurrentValues.SetValues(blogDto);
Ta technika jest czasami używana podczas aktualizowania jednostki przy użyciu wartości uzyskanych z wywołania usługi lub klienta w aplikacji n-warstwowej. Należy pamiętać, że użyty obiekt nie musi być tego samego typu co jednostka, o ile ma właściwości, których nazwy pasują do tych z jednostki. W powyższym przykładzie wystąpienie obiektu DTO BlogDto
służy do ustawiania bieżących wartości śledzonej Blog
jednostki.
Należy pamiętać, że właściwości zostaną oznaczone jako zmodyfikowane tylko wtedy, gdy zestaw wartości różni się od bieżącej wartości.
Ustawianie bieżących lub oryginalnych wartości ze słownika
W poprzednim przykładzie ustawiono wartości z jednostki lub wystąpienia DTO. To samo zachowanie jest dostępne, gdy wartości właściwości są przechowywane jako pary nazwa/wartość w słowniku. Przykład:
var blogDictionary = new Dictionary<string, object> { ["Id"] = 1, ["Name"] = "1unicorn2" };
context.Entry(blog).CurrentValues.SetValues(blogDictionary);
Ustawianie bieżących lub oryginalnych wartości z bazy danych
Bieżące lub oryginalne wartości jednostki można zaktualizować przy użyciu najnowszych wartości z bazy danych przez wywołanie GetDatabaseValues() lub GetDatabaseValuesAsync użycie zwróconego obiektu w celu ustawienia bieżących lub oryginalnych wartości albo obu tych wartości. Przykład:
var databaseValues = context.Entry(blog).GetDatabaseValues();
context.Entry(blog).CurrentValues.SetValues(databaseValues);
context.Entry(blog).OriginalValues.SetValues(databaseValues);
Tworzenie sklonowanego obiektu zawierającego bieżące, oryginalne lub wartości bazy danych
Obiekt PropertyValues zwrócony z wartości CurrentValues, OriginalValues lub GetDatabaseValues może służyć do utworzenia klonu jednostki przy użyciu polecenia PropertyValues.ToObject(). Przykład:
var clonedBlog = context.Entry(blog).GetDatabaseValues().ToObject();
Zwróć uwagę, że ToObject
zwraca nowe wystąpienie, które nie jest śledzone przez obiekt DbContext. Zwrócony obiekt nie ma również żadnych relacji ustawionych na inne jednostki.
Sklonowany obiekt może być przydatny do rozwiązywania problemów związanych z współbieżnych aktualizacji bazy danych, zwłaszcza w przypadku powiązania danych z obiektami określonego typu. Aby uzyskać więcej informacji, zobacz optymistyczną współbieżność .
Praca ze wszystkimi nawigacjami jednostki
EntityEntry.Navigations zwraca wartość dla IEnumerable<T> NavigationEntry każdej nawigacji jednostki. EntityEntry.References i EntityEntry.Collections zrobić to samo, ale ograniczone odpowiednio do odwołań lub nawigacji kolekcji. Może to służyć do wykonywania akcji dla każdej nawigacji jednostki. Aby na przykład wymusić ładowanie wszystkich powiązanych jednostek:
foreach (var navigationEntry in context.Entry(blog).Navigations)
{
navigationEntry.Load();
}
Praca ze wszystkimi członkami jednostki
Zwykłe właściwości i właściwości nawigacji mają inny stan i zachowanie. W związku z tym często przetwarza się nawigacje i nienawigowania oddzielnie, jak pokazano w powyższych sekcjach. Jednak czasami przydatne może być zrobienie czegoś z dowolnym elementem członkowskim jednostki, niezależnie od tego, czy jest to zwykła właściwość, czy nawigacja. EntityEntry.Member i EntityEntry.Members są one udostępniane w tym celu. Przykład:
foreach (var memberEntry in context.Entry(blog).Members)
{
Console.WriteLine(
$"Member {memberEntry.Metadata.Name} is of type {memberEntry.Metadata.ClrType.ShortDisplayName()} and has value {memberEntry.CurrentValue}");
}
Uruchomienie tego kodu na blogu z przykładu generuje następujące dane wyjściowe:
Member Id is of type int and has value 1
Member Name is of type string and has value .NET Blog
Member Posts is of type IList<Post> and has value System.Collections.Generic.List`1[Post]
Napiwek
Widok debugowania monitora zmian zawiera informacje podobne do tych. Widok debugowania dla całego monitora zmian jest generowany na podstawie poszczególnych EntityEntry.DebugView śledzonych jednostek.
Znajdowanie i znajdowanie asynchroniczne
DbContext.Find, , DbContext.FindAsyncDbSet<TEntity>.Findi DbSet<TEntity>.FindAsync są przeznaczone do wydajnego wyszukiwania pojedynczej jednostki, gdy jest znany jego klucz podstawowy. Znajdź pierwsze sprawdzanie, czy jednostka jest już śledzona, a jeśli tak, zwraca jednostkę natychmiast. Zapytanie bazy danych jest wykonywane tylko wtedy, gdy jednostka nie jest śledzona lokalnie. Rozważmy na przykład ten kod, który wywołuje metodę Find dwa razy dla tej samej jednostki:
using var context = new BlogsContext();
Console.WriteLine("First call to Find...");
var blog1 = context.Blogs.Find(1);
Console.WriteLine($"...found blog {blog1.Name}");
Console.WriteLine();
Console.WriteLine("Second call to Find...");
var blog2 = context.Blogs.Find(1);
Debug.Assert(blog1 == blog2);
Console.WriteLine("...returned the same instance without executing a query.");
Dane wyjściowe z tego kodu (w tym rejestrowanie programu EF Core) w przypadku korzystania z biblioteki SQLite to:
First call to Find...
info: 12/29/2020 07:45:53.682 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
Executed DbCommand (1ms) [Parameters=[@__p_0='1' (DbType = String)], CommandType='Text', CommandTimeout='30']
SELECT "b"."Id", "b"."Name"
FROM "Blogs" AS "b"
WHERE "b"."Id" = @__p_0
LIMIT 1
...found blog .NET Blog
Second call to Find...
...returned the same instance without executing a query.
Zwróć uwagę, że pierwsze wywołanie nie znajduje jednostki lokalnie, dlatego wykonuje zapytanie bazy danych. Z drugiej strony drugie wywołanie zwraca to samo wystąpienie bez wykonywania zapytań względem bazy danych, ponieważ jest już śledzone.
Funkcja Find zwraca wartość null, jeśli jednostka z danym kluczem nie jest śledzona lokalnie i nie istnieje w bazie danych.
Klucze złożone
Funkcja Znajdź może być również używana z kluczami złożonymi. Rozważmy na przykład OrderLine
jednostkę z kluczem złożonym składającym się z identyfikatora zamówienia i identyfikatora produktu:
public class OrderLine
{
public int OrderId { get; set; }
public int ProductId { get; set; }
//...
}
Klucz złożony musi być skonfigurowany, DbContext.OnModelCreating aby zdefiniować kluczowe części i ich kolejność. Przykład:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder
.Entity<OrderLine>()
.HasKey(e => new { e.OrderId, e.ProductId });
}
Zwróć uwagę, że OrderId
jest to pierwsza część klucza i ProductId
jest drugą częścią klucza. Ta kolejność musi być używana podczas przekazywania wartości kluczy do funkcji Znajdź. Przykład:
var orderline = context.OrderLines.Find(orderId, productId);
Uzyskiwanie dostępu do wszystkich śledzonych jednostek przy użyciu biblioteki ChangeTracker.Entries
Do tej pory mieliśmy dostęp tylko do jednego EntityEntry naraz. ChangeTracker.Entries() Zwraca element EntityEntry dla każdej jednostki aktualnie śledzonej przez obiekt DbContext. Przykład:
using var context = new BlogsContext();
var blogs = context.Blogs.Include(e => e.Posts).ToList();
foreach (var entityEntry in context.ChangeTracker.Entries())
{
Console.WriteLine($"Found {entityEntry.Metadata.Name} entity with ID {entityEntry.Property("Id").CurrentValue}");
}
Ten kod generuje następujące dane wyjściowe:
Found Blog entity with ID 1
Found Post entity with ID 1
Found Post entity with ID 2
Zwróć uwagę, że zwracane są wpisy zarówno dla blogów, jak i wpisów. Zamiast tego wyniki można filtrować do określonego ChangeTracker.Entries<TEntity>() typu jednostki przy użyciu przeciążenia ogólnego:
foreach (var entityEntry in context.ChangeTracker.Entries<Post>())
{
Console.WriteLine(
$"Found {entityEntry.Metadata.Name} entity with ID {entityEntry.Property(e => e.Id).CurrentValue}");
}
Dane wyjściowe z tego kodu pokazują, że zwracane są tylko wpisy:
Found Post entity with ID 1
Found Post entity with ID 2
Ponadto użycie przeciążenia ogólnego zwraca wystąpienia ogólne EntityEntry<TEntity> . W tym przykładzie umożliwia to płynne uzyskiwanie dostępu do Id
właściwości.
Typ ogólny używany do filtrowania nie musi być mapowanym typem jednostki; Zamiast tego można użyć niezamapowanego typu podstawowego lub interfejsu. Jeśli na przykład wszystkie typy jednostek w modelu implementują interfejs definiujący ich właściwość klucza:
public interface IEntityWithKey
{
int Id { get; set; }
}
Następnie ten interfejs może służyć do pracy z kluczem dowolnej śledzonej jednostki w sposób silnie typizowane. Przykład:
foreach (var entityEntry in context.ChangeTracker.Entries<IEntityWithKey>())
{
Console.WriteLine(
$"Found {entityEntry.Metadata.Name} entity with ID {entityEntry.Property(e => e.Id).CurrentValue}");
}
Używanie elementu DbSet.Local do wykonywania zapytań o śledzone jednostki
Zapytania ef Core są zawsze wykonywane w bazie danych i zwracają tylko jednostki, które zostały zapisane w bazie danych. DbSet<TEntity>.Local Udostępnia mechanizm wysyłania zapytań do elementu DbContext dla jednostek lokalnych, śledzonych.
Ponieważ DbSet.Local
służy do wykonywania zapytań dotyczących śledzonych jednostek, typowe jest ładowanie jednostek do elementu DbContext, a następnie praca z tymi załadowanymi jednostkami. Dotyczy to szczególnie powiązania danych, ale może być również przydatne w innych sytuacjach. Na przykład w poniższym kodzie baza danych jest najpierw odpytywane dla wszystkich blogów i wpisów. Metoda Load rozszerzenia służy do wykonywania tego zapytania z wynikami śledzonym przez kontekst bez zwracania bezpośrednio do aplikacji. (Użycie ToList
lub podobne ma taki sam efekt, ale z obciążeniem tworzenia zwracanej listy, która nie jest potrzebna tutaj). W tym przykładzie użyto DbSet.Local
metody w celu uzyskania dostępu do lokalnie śledzonych jednostek:
using var context = new BlogsContext();
context.Blogs.Include(e => e.Posts).Load();
foreach (var blog in context.Blogs.Local)
{
Console.WriteLine($"Blog: {blog.Name}");
}
foreach (var post in context.Posts.Local)
{
Console.WriteLine($"Post: {post.Title}");
}
Zwróć uwagę, że w przeciwieństwie do ChangeTracker.Entries()DbSet.Local
klasy funkcja zwraca wystąpienia jednostek bezpośrednio. Jednostka EntityEntry może oczywiście być zawsze uzyskiwana dla zwróconej jednostki przez wywołanie metody DbContext.Entry.
Widok lokalny
DbSet<TEntity>.Local Zwraca widok lokalnie śledzonych jednostek, który odzwierciedla bieżącą część EntityState tych jednostek. W szczególności oznacza to, że:
Added
jednostki są uwzględniane. Należy pamiętać, że nie jest tak w przypadku zwykłych zapytań platformy EF Core, ponieważAdded
jednostki nie istnieją jeszcze w bazie danych, dlatego nigdy nie są zwracane przez zapytanie bazy danych.Deleted
jednostki są wykluczone. Należy pamiętać, że nie dotyczy to zwykłych zapytań platformy EF Core, ponieważDeleted
jednostki nadal istnieją w bazie danych i dlatego są zwracane przez zapytania bazy danych.
Wszystko to oznacza, że DbSet.Local
jest to widok danych, które odzwierciedlają bieżący stan koncepcyjny grafu jednostki, z jednostkami dołączonymi Added
i Deleted
wykluczonymi jednostkami. Jest to zgodne z oczekiwanym stanem bazy danych po wywołaniu funkcji SaveChanges.
Jest to zazwyczaj idealny widok powiązania danych, ponieważ przedstawia użytkownikowi dane w miarę ich zrozumienia na podstawie zmian wprowadzonych przez aplikację.
Poniższy kod pokazuje to, oznaczając jeden wpis jako Deleted
, a następnie dodając nowy wpis, oznaczając go jako Added
:
using var context = new BlogsContext();
var posts = context.Posts.Include(e => e.Blog).ToList();
Console.WriteLine("Local view after loading posts:");
foreach (var post in context.Posts.Local)
{
Console.WriteLine($" Post: {post.Title}");
}
context.Remove(posts[1]);
context.Add(
new Post
{
Title = "What’s next for System.Text.Json?",
Content = ".NET 5.0 was released recently and has come with many...",
Blog = posts[0].Blog
});
Console.WriteLine("Local view after adding and deleting posts:");
foreach (var post in context.Posts.Local)
{
Console.WriteLine($" Post: {post.Title}");
}
Dane wyjściowe z tego kodu to:
Local view after loading posts:
Post: Announcing the Release of EF Core 5.0
Post: Announcing F# 5
Post: Announcing .NET 5.0
Local view after adding and deleting posts:
Post: What’s next for System.Text.Json?
Post: Announcing the Release of EF Core 5.0
Post: Announcing .NET 5.0
Zwróć uwagę, że usunięty wpis jest usuwany z widoku lokalnego, a dodany wpis jest dołączany.
Dodawanie i usuwanie jednostek przy użyciu ustawień lokalnych
DbSet<TEntity>.Local Zwraca wystąpienie LocalView<TEntity>klasy . Jest to implementacja ICollection<T> , która generuje powiadomienia i reaguje na powiadomienia, gdy jednostki są dodawane i usuwane z kolekcji. (Jest to taka sama koncepcja jak ObservableCollection<T>, ale zaimplementowana jako projekcja w istniejących wpisach śledzenia zmian platformy EF Core, a nie jako niezależnej kolekcji).
Powiadomienia widoku lokalnego są przyłączone do śledzenia zmian DbContext w taki sposób, że widok lokalny pozostaje zsynchronizowany z elementem DbContext. Szczególnie:
- Dodanie nowej jednostki w celu
DbSet.Local
powoduje, że będzie ona śledzona przez element DbContext, zazwyczaj wAdded
stanie . (Jeśli jednostka ma już wygenerowaną wartość klucza, jest ona śledzona jakoUnchanged
zamiast tego). - Usunięcie jednostki z
DbSet.Local
powoduje oznaczenie jej jakoDeleted
. - Jednostka, która zostanie śledzona przez obiekt DbContext, zostanie automatycznie wyświetlona w kolekcji
DbSet.Local
. Na przykład wykonanie zapytania w celu automatycznego wprowadzenia większej liczby jednostek powoduje zaktualizowanie widoku lokalnego. - Jednostka oznaczona jako
Deleted
zostanie automatycznie usunięta z kolekcji lokalnej.
Oznacza to, że widok lokalny może służyć do manipulowania śledzonych jednostek po prostu przez dodanie i usunięcie z kolekcji. Zmodyfikujmy na przykład poprzedni przykładowy kod, aby dodać i usunąć wpisy z kolekcji lokalnej:
using var context = new BlogsContext();
var posts = context.Posts.Include(e => e.Blog).ToList();
Console.WriteLine("Local view after loading posts:");
foreach (var post in context.Posts.Local)
{
Console.WriteLine($" Post: {post.Title}");
}
context.Posts.Local.Remove(posts[1]);
context.Posts.Local.Add(
new Post
{
Title = "What’s next for System.Text.Json?",
Content = ".NET 5.0 was released recently and has come with many...",
Blog = posts[0].Blog
});
Console.WriteLine("Local view after adding and deleting posts:");
foreach (var post in context.Posts.Local)
{
Console.WriteLine($" Post: {post.Title}");
}
Dane wyjściowe pozostają niezmienione z poprzedniego przykładu, ponieważ zmiany wprowadzone w widoku lokalnym są synchronizowane z elementem DbContext.
Korzystanie z widoku lokalnego dla powiązań danych windows Forms lub WPF
DbSet<TEntity>.Local stanowi podstawę powiązania danych z jednostkami programu EF Core. Jednak zarówno Formularze systemu Windows, jak i WPF działają najlepiej w przypadku użycia z określonym typem powiadamiania kolekcji, że są one oczekiwane. Widok lokalny obsługuje tworzenie tych określonych typów kolekcji:
- LocalView<TEntity>.ToObservableCollection() Zwraca wartość ObservableCollection<T> dla powiązania danych WPF.
- LocalView<TEntity>.ToBindingList() zwraca wartość dla BindingList<T> powiązania danych formularzy systemu Windows.
Przykład:
ObservableCollection<Post> observableCollection = context.Posts.Local.ToObservableCollection();
BindingList<Post> bindingList = context.Posts.Local.ToBindingList();
Aby uzyskać więcej informacji na temat powiązania danych WPF z programem EF Core, zobacz Rozpoczynanie pracy z platformą Windows Forms , aby uzyskać więcej informacji na temat powiązania danych formularzy systemu Windows z programem EF Core.
Napiwek
Widok lokalny dla danego wystąpienia DbSet jest tworzony leniwie po pierwszym uzyskiwaniu dostępu, a następnie buforowany. Samo tworzenie obiektu LocalView jest szybkie i nie korzysta ze znacznej ilości pamięci. Jednak wywołuje metodę DetectChanges, co może być powolne w przypadku dużej liczby jednostek. Kolekcje utworzone przez ToObservableCollection
program i ToBindingList
są również tworzone leniwie, a następnie buforowane. Obie te metody tworzą nowe kolekcje, które mogą być powolne i korzystać z dużej ilości pamięci, gdy są zaangażowane tysiące jednostek.