Загрузка связанных сущностей

Entity Framework поддерживает три способа загрузки связанных данных— безотложную загрузку, отложенную загрузку и явную загрузку. Методы, представленные в этом разделе, также применимы к моделям, созданным с помощью Code First и конструктора EF.

Безотложная загрузка

Безотложная загрузка — это процесс, в котором запрос для одного типа сущности также загружает связанные сущности в рамках запроса. Безотложная загрузка достигается с помощью метода Include. Например, приведенные ниже запросы загружают блоги и все записи, связанные с каждым блогом.

using (var context = new BloggingContext())
{
    // Load all blogs and related posts.
    var blogs1 = context.Blogs
                        .Include(b => b.Posts)
                        .ToList();

    // Load one blog and its related posts.
    var blog1 = context.Blogs
                       .Where(b => b.Name == "ADO.NET Blog")
                       .Include(b => b.Posts)
                       .FirstOrDefault();

    // Load all blogs and related posts
    // using a string to specify the relationship.
    var blogs2 = context.Blogs
                        .Include("Posts")
                        .ToList();

    // Load one blog and its related posts
    // using a string to specify the relationship.
    var blog2 = context.Blogs
                       .Where(b => b.Name == "ADO.NET Blog")
                       .Include("Posts")
                       .FirstOrDefault();
}

Примечание

Include — это метод расширения в пространстве имен System.Data.Entity, поэтому убедитесь, что вы используете это пространство имен.

Безотложная загрузка нескольких уровней

Кроме того, можно с нетерпением загружать несколько уровней связанных сущностей. В приведенных ниже запросах показаны примеры того, как это сделать для свойств коллекции и ссылочной навигации.

using (var context = new BloggingContext())
{
    // Load all blogs, all related posts, and all related comments.
    var blogs1 = context.Blogs
                        .Include(b => b.Posts.Select(p => p.Comments))
                        .ToList();

    // Load all users, their related profiles, and related avatar.
    var users1 = context.Users
                        .Include(u => u.Profile.Avatar)
                        .ToList();

    // Load all blogs, all related posts, and all related comments  
    // using a string to specify the relationships.
    var blogs2 = context.Blogs
                        .Include("Posts.Comments")
                        .ToList();

    // Load all users, their related profiles, and related avatar  
    // using a string to specify the relationships.
    var users2 = context.Users
                        .Include("Profile.Avatar")
                        .ToList();
}

Примечание

В настоящее время невозможно отфильтровать связанные сущности. Включить всегда будет включать все связанные сущности.

Отложенная загрузка

Отложенная загрузка — это процесс, при котором сущность или коллекция сущностей автоматически загружаются из базы данных при первом обращении к свойству, ссылающемся на сущность или сущности. При использовании типов сущностей POCO отложенная загрузка достигается путем создания экземпляров производных прокси-типов, а затем переопределения виртуальных свойств для добавления перехватчика загрузки. Например, при использовании класса сущности блога, определенного ниже, связанные записи загружаются при первом обращении к свойству навигации Posts:

public class Blog
{
    public int BlogId { get; set; }
    public string Name { get; set; }
    public string Url { get; set; }
    public string Tags { get; set; }

    public virtual ICollection<Post> Posts { get; set; }
}

Включение отложенной загрузки для сериализации

Отложенная загрузка и сериализация не смешиваются хорошо, и если вы не осторожны, вы можете в конечном итоге запросить всю базу данных только потому, что отложенная загрузка включена. Большинство сериализаторов работают путем доступа к каждому свойству экземпляра типа. Доступ к свойствам активирует отложенную загрузку, поэтому больше сущностей сериализуются. Доступ к этим свойствам сущностей выполняется, и загружается еще больше сущностей. Рекомендуется отключить отложенную загрузку перед сериализизируемой сущностью. В следующих разделах показано, как это сделать.

Отключение отложенной загрузки для определенных свойств навигации

Отложенная загрузка коллекции Post может быть отключена, сделав свойство Posts не виртуальным:

public class Blog
{
    public int BlogId { get; set; }
    public string Name { get; set; }
    public string Url { get; set; }
    public string Tags { get; set; }

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

Загрузка коллекции "Записи" по-прежнему может быть достигнута с помощью неотложной загрузки (см. раздел "Неустранимая загрузка выше") или метода Load (см. раздел "Явная загрузка ниже").

Отключение отложенной загрузки для всех сущностей

Отложенную загрузку можно отключить для всех сущностей в контексте, задав флаг для свойства Configuration. Пример:

public class BloggingContext : DbContext
{
    public BloggingContext()
    {
        this.Configuration.LazyLoadingEnabled = false;
    }
}

Загрузка связанных сущностей по-прежнему может быть достигнута с помощью неотложной загрузки (см. раздел "Безотложная загрузка выше") или метода Load (см. раздел "Явная загрузка ниже").

Явная загрузка

Даже если отложенная загрузка отключена, все равно можно лениво загружать связанные сущности, но это необходимо сделать с помощью явного вызова. Для этого используется метод Load для записи связанной сущности. Пример:

using (var context = new BloggingContext())
{
    var post = context.Posts.Find(2);

    // Load the blog related to a given post.
    context.Entry(post).Reference(p => p.Blog).Load();

    // Load the blog related to a given post using a string.
    context.Entry(post).Reference("Blog").Load();

    var blog = context.Blogs.Find(1);

    // Load the posts related to a given blog.
    context.Entry(blog).Collection(p => p.Posts).Load();

    // Load the posts related to a given blog
    // using a string to specify the relationship.
    context.Entry(blog).Collection("Posts").Load();
}

Примечание

Метод Reference следует использовать, если сущность имеет свойство навигации к другой отдельной сущности. С другой стороны, метод Collection следует использовать, если сущность имеет свойство навигации для коллекции других сущностей.

Метод query предоставляет доступ к базовому запросу, который Entity Framework будет использовать при загрузке связанных сущностей. Затем можно использовать LINQ для применения фильтров к запросу перед его выполнением с вызовом метода расширения LINQ, такого как ToList, Load и т. д. Метод Query можно использовать как со свойствами навигации по ссылке, так и к коллекции, но наиболее полезен для коллекций, где его можно использовать для загрузки только части коллекции. Пример:

using (var context = new BloggingContext())
{
    var blog = context.Blogs.Find(1);

    // Load the posts with the 'entity-framework' tag related to a given blog.
    context.Entry(blog)
           .Collection(b => b.Posts)
           .Query()
           .Where(p => p.Tags.Contains("entity-framework"))
           .Load();

    // Load the posts with the 'entity-framework' tag related to a given blog
    // using a string to specify the relationship.
    context.Entry(blog)
           .Collection("Posts")
           .Query()
           .Where(p => p.Tags.Contains("entity-framework"))
           .Load();
}

При использовании метода запроса обычно рекомендуется отключить отложенную загрузку для свойства навигации. Это связано с тем, что в противном случае вся коллекция может загружаться автоматически с помощью отложенного механизма загрузки либо до, либо после выполнения отфильтрованного запроса.

Примечание

Хотя связь может быть указана как строка вместо лямбда-выражения, возвращенный IQueryable не является универсальным, если используется строка и поэтому метод Cast обычно необходим, прежде чем все полезное можно сделать с ним.

Иногда полезно знать, сколько сущностей связано с другой сущностью в базе данных без фактической загрузки всех этих сущностей. Для этого можно использовать метод Query с методом LINQ Count. Пример:

using (var context = new BloggingContext())
{
    var blog = context.Blogs.Find(1);

    // Count how many posts the blog has.
    var postCount = context.Entry(blog)
                           .Collection(b => b.Posts)
                           .Query()
                           .Count();
}