Dane lokalne

Uruchomienie zapytania LINQ bezpośrednio względem zestawu dbSet zawsze spowoduje wysłanie zapytania do bazy danych, ale można uzyskać dostęp do danych, które są obecnie w pamięci przy użyciu właściwości DbSet.Local. Możesz również uzyskać dostęp do dodatkowych informacji, które program EF śledzi jednostki przy użyciu metod DbContext.Entry i DbContext.ChangeTracker.Entrys. Techniki przedstawione w tym temacie dotyczą modeli utworzonych przy użyciu podejścia „najpierw kod” i narzędzia EF Designer.

Używanie ustawień lokalnych do wyszukiwania danych lokalnych

Właściwość Local zestawu DbSet zapewnia prosty dostęp do jednostek zestawu, które są obecnie śledzone przez kontekst i nie zostały oznaczone jako Usunięte. Uzyskiwanie dostępu do właściwości Local nigdy nie powoduje wysłania zapytania do bazy danych. Oznacza to, że jest on zwykle używany po wykonaniu zapytania. Metodę rozszerzenia Load można użyć do wykonania zapytania, aby kontekst śledził wyniki. Przykład:

using (var context = new BloggingContext())
{
    // Load all blogs from the database into the context
    context.Blogs.Load();

    // Add a new blog to the context
    context.Blogs.Add(new Blog { Name = "My New Blog" });

    // Mark one of the existing blogs as Deleted
    context.Blogs.Remove(context.Blogs.Find(1));

    // Loop over the blogs in the context.
    Console.WriteLine("In Local: ");
    foreach (var blog in context.Blogs.Local)
    {
        Console.WriteLine(
            "Found {0}: {1} with state {2}",
            blog.BlogId,  
            blog.Name,
            context.Entry(blog).State);
    }

    // Perform a query against the database.
    Console.WriteLine("\nIn DbSet query: ");
    foreach (var blog in context.Blogs)
    {
        Console.WriteLine(
            "Found {0}: {1} with state {2}",
            blog.BlogId,  
            blog.Name,
            context.Entry(blog).State);
    }
}

Gdybyśmy mieli dwa blogi w bazie danych — "ADO.NET Blog" z wartością BlogId 1 i "Blog programu Visual Studio" z identyfikatorem BlogId 2 — możemy oczekiwać następujących danych wyjściowych:

In Local:
Found 0: My New Blog with state Added
Found 2: The Visual Studio Blog with state Unchanged

In DbSet query:
Found 1: ADO.NET Blog with state Deleted
Found 2: The Visual Studio Blog with state Unchanged

Ilustruje to trzy punkty:

  • Nowy blog "Mój nowy blog" znajduje się w kolekcji Lokalnej, mimo że nie został jeszcze zapisany w bazie danych. Ten blog ma klucz podstawowy o wartości zero, ponieważ baza danych nie wygenerowała jeszcze rzeczywistego klucza dla jednostki.
  • Blog "ADO.NET" nie jest uwzględniany w kolekcji lokalnej, mimo że jest nadal śledzony przez kontekst. Jest to spowodowane tym, że usunęliśmy go z zestawu DbSet, co oznacza je jako usunięte.
  • Gdy zestaw DbSet jest używany do wykonywania zapytania w blogu oznaczonym do usunięcia (ADO.NET Blog) jest uwzględniony w wynikach, a nowy blog (Mój nowy blog), który nie został jeszcze zapisany w bazie danych, nie jest uwzględniony w wynikach. Dzieje się tak, ponieważ zestaw dbSet wykonuje zapytanie względem bazy danych, a zwracane wyniki zawsze odzwierciedlają to, co znajduje się w bazie danych.

Używanie ustawień lokalnych do dodawania i usuwania jednostek z kontekstu

Właściwość Local w zestawie DbSet zwraca element ObservableCollection ze zdarzeniami podłączonymi tak, że pozostaje zsynchronizowany z zawartością kontekstu. Oznacza to, że jednostki można dodawać lub usuwać z kolekcji lokalnej lub z zestawu DbSet. Oznacza to również, że zapytania, które przeprowadzą nowe jednostki do kontekstu, spowodują zaktualizowanie kolekcji lokalnej przy użyciu tych jednostek. Przykład:

using (var context = new BloggingContext())
{
    // Load some posts from the database into the context
    context.Posts.Where(p => p.Tags.Contains("entity-framework")).Load();  

    // Get the local collection and make some changes to it
    var localPosts = context.Posts.Local;
    localPosts.Add(new Post { Name = "What's New in EF" });
    localPosts.Remove(context.Posts.Find(1));  

    // Loop over the posts in the context.
    Console.WriteLine("In Local after entity-framework query: ");
    foreach (var post in context.Posts.Local)
    {
        Console.WriteLine(
            "Found {0}: {1} with state {2}",
            post.Id,  
            post.Title,
            context.Entry(post).State);
    }

    var post1 = context.Posts.Find(1);
    Console.WriteLine(
        "State of post 1: {0} is {1}",
        post1.Name,  
        context.Entry(post1).State);  

    // Query some more posts from the database
    context.Posts.Where(p => p.Tags.Contains("asp.net")).Load();  

    // Loop over the posts in the context again.
    Console.WriteLine("\nIn Local after asp.net query: ");
    foreach (var post in context.Posts.Local)
    {
        Console.WriteLine(
            "Found {0}: {1} with state {2}",
            post.Id,  
            post.Title,
            context.Entry(post).State);
    }
}

Zakładając, że mamy kilka wpisów oznaczonych etykietą "entity-framework" i "asp.net", dane wyjściowe mogą wyglądać mniej więcej tak:

In Local after entity-framework query:
Found 3: EF Designer Basics with state Unchanged
Found 5: EF Code First Basics with state Unchanged
Found 0: What's New in EF with state Added
State of post 1: EF Beginners Guide is Deleted

In Local after asp.net query:
Found 3: EF Designer Basics with state Unchanged
Found 5: EF Code First Basics with state Unchanged
Found 0: What's New in EF with state Added
Found 4: ASP.NET Beginners Guide with state Unchanged

Ilustruje to trzy punkty:

  • Nowy wpis "Co nowego w programie EF", który został dodany do kolekcji Local, zostanie śledzony przez kontekst w stanie Dodano. W związku z tym zostanie wstawiony do bazy danych po wywołaniu funkcji SaveChanges.
  • Wpis usunięty z kolekcji lokalnej (Przewodnik dla początkujących ef) jest teraz oznaczony jako usunięty w kontekście. W związku z tym zostanie on usunięty z bazy danych po wywołaniu funkcji SaveChanges.
  • Dodatkowy wpis (ASP.NET Przewodnik dla początkujących) załadowany do kontekstu z drugim zapytaniem jest automatycznie dodawany do kolekcji Lokalnej.

Jedną z ostatnich rzeczy, które należy zwrócić uwagę na local jest to, że ponieważ jest to wydajność ObservableCollection nie jest doskonała dla dużej liczby jednostek. W związku z tym, jeśli masz do czynienia z tysiącami jednostek w kontekście, może nie być zalecane, aby użyć lokalnego.

Używanie lokalnego powiązania danych WPF

Właściwość Local w zestawie DbSet może służyć bezpośrednio do powiązania danych w aplikacji WPF, ponieważ jest to wystąpienie funkcji ObservableCollection. Zgodnie z opisem w poprzednich sekcjach oznacza to, że będzie on automatycznie synchronizowany z zawartością kontekstu, a zawartość kontekstu będzie automatycznie synchronizowana z nim. Należy pamiętać, że musisz wstępnie wypełnić kolekcję lokalną danymi, aby istniały jakiekolwiek powiązania, ponieważ funkcja Local nigdy nie powoduje kwerendy bazy danych.

Nie jest to odpowiednie miejsce dla pełnego przykładu powiązania danych WPF, ale kluczowe elementy to:

  • Konfigurowanie źródła powiązania
  • Wiązanie go z właściwością Local zestawu
  • Wypełnij pole Local przy użyciu zapytania do bazy danych.

Powiązanie WPF z właściwościami nawigacji

Jeśli wykonujesz powiązanie danych wzorca/szczegółów, możesz powiązać widok szczegółów z właściwością nawigacji jednej z jednostek. Łatwym sposobem na wykonanie tej pracy jest użycie obiektu ObservableCollection dla właściwości nawigacji. Przykład:

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

    public int BlogId { get; set; }
    public string Name { get; set; }

    public virtual ObservableCollection<Post> Posts
    {
        get { return _posts; }
    }
}

Używanie ustawień lokalnych do czyszczenia jednostek w funkcji SaveChanges

W większości przypadków jednostki usunięte z właściwości nawigacji nie zostaną automatycznie oznaczone jako usunięte w kontekście. Jeśli na przykład usuniesz obiekt Post z kolekcji Blog.Posts, ten wpis nie zostanie automatycznie usunięty po wywołaniu funkcji SaveChanges. Jeśli chcesz go usunąć, być może trzeba będzie znaleźć te zwisające jednostki i oznaczyć je jako usunięte przed wywołaniem SaveChanges lub w ramach zastąpienia SaveChanges. Przykład:

public override int SaveChanges()
{
    foreach (var post in this.Posts.Local.ToList())
    {
        if (post.Blog == null)
        {
            this.Posts.Remove(post);
        }
    }

    return base.SaveChanges();
}

Powyższy kod używa kolekcji Local do znajdowania wszystkich wpisów i oznacza wszystkie wpisy, które nie mają odwołania do blogu jako usunięte. Wywołanie ToList jest wymagane, ponieważ w przeciwnym razie kolekcja zostanie zmodyfikowana przez wywołanie Usuń podczas wyliczania. W większości innych sytuacji można wykonywać zapytania bezpośrednio względem właściwości Local bez wcześniejszego użycia funkcji ToList.

Używanie powiązania danych Local i ToBindingList dla powiązania danych formularzy systemu Windows

Formularze systemu Windows nie obsługują powiązania danych o pełnej wierności przy użyciu funkcji ObservableCollection bezpośrednio. Jednak nadal można użyć właściwości DbSet Local do powiązania danych, aby uzyskać wszystkie korzyści opisane w poprzednich sekcjach. Jest to osiągane za pomocą metody rozszerzenia ToBindingList, która tworzy implementację IBindingList wspieraną przez obiekt Local ObservableCollection.

Nie jest to odpowiednie miejsce dla pełnego przykładu powiązania danych windows Forms, ale kluczowe elementy to:

  • Konfigurowanie źródła powiązania obiektu
  • Powiąż ją z właściwością Local zestawu przy użyciu metody Local.ToBindingList()
  • Wypełnianie ustawień lokalnych przy użyciu zapytania do bazy danych

Uzyskiwanie szczegółowych informacji o śledzonych jednostkach

Wiele przykładów z tej serii używa metody Entry, aby zwrócić wystąpienie DbEntityEntry dla jednostki. Ten obiekt wpisu działa następnie jako punkt wyjścia do zbierania informacji o jednostce, takiej jak jej bieżący stan, a także do wykonywania operacji na jednostce, takich jak jawne ładowanie powiązanej jednostki.

Metody Entrys zwracają obiekty DbEntityEntry dla wielu lub wszystkich jednostek śledzonych przez kontekst. Dzięki temu można zbierać informacje lub wykonywać operacje na wielu jednostkach, a nie tylko na jednym wpisie. Przykład:

using (var context = new BloggingContext())
{
    // Load some entities into the context
    context.Blogs.Load();
    context.Authors.Load();
    context.Readers.Load();

    // Make some changes
    context.Blogs.Find(1).Title = "The New ADO.NET Blog";
    context.Blogs.Remove(context.Blogs.Find(2));
    context.Authors.Add(new Author { Name = "Jane Doe" });
    context.Readers.Find(1).Username = "johndoe1987";

    // Look at the state of all entities in the context
    Console.WriteLine("All tracked entities: ");
    foreach (var entry in context.ChangeTracker.Entries())
    {
        Console.WriteLine(
            "Found entity of type {0} with state {1}",
            ObjectContext.GetObjectType(entry.Entity.GetType()).Name,
            entry.State);
    }

    // Find modified entities of any type
    Console.WriteLine("\nAll modified entities: ");
    foreach (var entry in context.ChangeTracker.Entries()
                              .Where(e => e.State == EntityState.Modified))
    {
        Console.WriteLine(
            "Found entity of type {0} with state {1}",
            ObjectContext.GetObjectType(entry.Entity.GetType()).Name,
            entry.State);
    }

    // Get some information about just the tracked blogs
    Console.WriteLine("\nTracked blogs: ");
    foreach (var entry in context.ChangeTracker.Entries<Blog>())
    {
        Console.WriteLine(
            "Found Blog {0}: {1} with original Name {2}",
            entry.Entity.BlogId,  
            entry.Entity.Name,
            entry.Property(p => p.Name).OriginalValue);
    }

    // Find all people (author or reader)
    Console.WriteLine("\nPeople: ");
    foreach (var entry in context.ChangeTracker.Entries<IPerson>())
    {
        Console.WriteLine("Found Person {0}", entry.Entity.Name);
    }
}

Zauważysz, że w przykładzie wprowadzimy klasę Author i Reader — obie te klasy implementują interfejs IPerson.

public class Author : IPerson
{
    public int AuthorId { get; set; }
    public string Name { get; set; }
    public string Biography { get; set; }
}

public class Reader : IPerson
{
    public int ReaderId { get; set; }
    public string Name { get; set; }
    public string Username { get; set; }
}

public interface IPerson
{
    string Name { get; }
}

Załóżmy, że mamy następujące dane w bazie danych:

Blog z identyfikatorem BlogId = 1 i nazwą = "ADO.NET Blog"
Blog z identyfikatorem BlogId = 2 i nazwą = "Blog programu Visual Studio"
Blog z identyfikatorem BlogId = 3 i nazwą = Blog ".NET Framework"
Autor z identyfikatorem AuthorId = 1 i nazwą = "Joe Bloggs"
Czytelnik z identyfikatorem ReaderId = 1 i nazwą = "John Doe"

Dane wyjściowe z uruchamiania kodu będą następujące:

All tracked entities:
Found entity of type Blog with state Modified
Found entity of type Blog with state Deleted
Found entity of type Blog with state Unchanged
Found entity of type Author with state Unchanged
Found entity of type Author with state Added
Found entity of type Reader with state Modified

All modified entities:
Found entity of type Blog with state Modified
Found entity of type Reader with state Modified

Tracked blogs:
Found Blog 1: The New ADO.NET Blog with original Name ADO.NET Blog
Found Blog 2: The Visual Studio Blog with original Name The Visual Studio Blog
Found Blog 3: .NET Framework Blog with original Name .NET Framework Blog

People:
Found Person John Doe
Found Person Joe Bloggs
Found Person Jane Doe

Te przykłady ilustrują kilka punktów:

  • Metody Wpisy zwracają wpisy dla jednostek we wszystkich stanach, w tym usuniętych. Porównaj to z lokalizacją lokalną, która wyklucza usunięte jednostki.
  • Wpisy dla wszystkich typów jednostek są zwracane, gdy jest używana metoda niegenerycznych wpisów. Gdy metoda wpisów ogólnych jest używana, wpisy są zwracane tylko dla jednostek, które są wystąpieniami typu ogólnego. Zostało to użyte powyżej, aby uzyskać wpisy dla wszystkich blogów. Służy również do pobierania wpisów dla wszystkich jednostek, które implementują IPerson. Pokazuje to, że typ ogólny nie musi być rzeczywistym typem jednostki.
  • Funkcja LINQ to Objects może służyć do filtrowania zwracanych wyników. Zostało to użyte powyżej, aby znaleźć jednostki dowolnego typu, o ile są modyfikowane.

Należy pamiętać, że wystąpienia DbEntityEntry zawsze zawierają jednostkę inną niż null. Wpisy relacji i wpisy wycinków nie są reprezentowane jako wystąpienia DbEntityEntry, więc nie ma potrzeby filtrowania tych wpisów.