Получение "попытки выполнить ленивую загрузку свойства навигации на отдельном объекте", несмотря на нетерпеливую выборку - PullRequest
0 голосов
/ 31 августа 2018

Я использую Entity Framework Core 2.1.2 с отложенной загрузкой и выполняю запрос, используя AsNoTracking. Я использую «Включить», чтобы добавить свойство навигации (коллекцию).

Если все мои сущности имеют хотя бы одного ребенка в своей коллекции, то все работает нормально.

Однако, если у какой-либо из моих сущностей нет детей, я получаю ошибку:

System.InvalidOperationException: ошибка, сгенерированная для предупреждения «Microsoft.EntityFrameworkCore.Infrastructure.DetachedLazyLoadingWarning: Предпринята попытка лениво загрузить свойство навигации «Дети» в отдельная сущность типа 'ParentProxy'. Ленивая загрузка не поддерживается для отдельных объектов или объектов, которые загружены 'AsNoTracking ()'. '

Вот воспроизведение проблемы (ее можно запустить из консольного приложения после использования NuGet для добавления Microsoft.EntityFrameworkCore 2.1.2, Microsoft.EntityFrameworkCore.Proxies 2.1.2, Microsoft.EntityFrameworkCore.InMemory 2.1.2):

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore;

namespace LazyLoadingIssue
{
    public class Parent
    {
        public int Id { get; set; }
        public string ParentName { get; set; }
        public virtual ICollection<Child> Children { get; set; }
    }

    public class Child
    {
        public int Id { get; set; }
        public int ParentId { get; set; }
        public virtual Parent Parent { get; set; }
        public string ChildName { get; set; }
    }

    public class Program
    {
        public static void Main(string[] args)
        {
            SetupDatabase(setupToFail: true);
            PerformTest();

            Console.WriteLine("Press any key to finish");
            Console.ReadLine();
        }

        private static void PerformTest()
        {
            using (var db = new MyContext())
            {
                try
                {
                    IQueryable<Parent> parents = db.Rounds.Include(r => r.Children).AsNoTracking();
                    foreach (Parent parent in parents)
                    {
                        Console.WriteLine($"Parent (Id={parent.Id}) '{parent.ParentName}'");
                        foreach (Child child in parent.Children)
                        {
                            Console.WriteLine($"  - Child (Id={child.Id}, ParentId={child.ParentId}) '{child.ChildName}'");
                        }
                    }

                    Console.WriteLine("** WORKED **");
                }
                catch (Exception ex)
                {
                    Console.WriteLine("** FAILED **");
                    Console.WriteLine(ex);
                }
            }
        }

        private static void SetupDatabase(bool setupToFail)
        {
            using (var db = new MyContext())
            {
                db.Database.EnsureDeleted();
                db.Database.EnsureCreated();

                var parent1 = new Parent
                {
                    ParentName = "First sample parent (has children)",
                    Children = new List<Child>
                    {
                        new Child {ChildName = "child-1"},
                        new Child {ChildName = "child-2"},
                        new Child {ChildName = "child-3"}
                    }
                };
                var parent2 = new Parent
                {
                    ParentName = $"Second sample parent ({(setupToFail ? "with no children" : "has children")})",
                    Children = new List<Child>()
                };
                if (!setupToFail)
                    parent2.Children.Add(new Child {ChildName = "child-4"});
                db.AddRange(parent1, parent2);
                db.SaveChanges();
            }
        }
    }


    public class MyContext : DbContext
    {
        public DbSet<Parent> Rounds { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder
//                .UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=_ModelApp;Trusted_Connection=True;Connect Timeout=5;ConnectRetryCount=0")
                .UseInMemoryDatabase(databaseName: "_modelApp")
                .UseLazyLoadingProxies()
                ;
        }
    }

}

Я что-то не так делаю? Или это ошибка в EF Core? (Я уже опубликовал проблему там тоже.)

Ответы [ 2 ]

0 голосов
/ 03 сентября 2018

Для потомков вот ответ от команды EF Core :

Это потому, что отложенная загрузка не поддерживается для запросов NoTracking (# 10042) но мы старались не бросать его, если оно выглядело как ленивая загрузка не нужна. Оглядываясь назад, возможно, было бы лучше всегда бросай. Обратите внимание, что предупреждение может быть настроено, чтобы не бросать используя ConfigureWarnings в DbContextOptionsBuilder.

На всякий случай, если это кому-то пригодится, я в итоге создал второй «ReadOnlyRepository», настроенный так, чтобы он не использовал отложенную загрузку и всегда возвращал неотслеживаемые наборы. Я использую этот репозиторий для запросов, в которых я никогда не собираюсь сохранять изменения в любой из сущностей, где набор результатов может быть большим и когда он должен работать хорошо.

public class ReadOnlyRepository : MainDbContextBase, IReadOnlyRepository
{
    public ReadOnlyRepository(IConfigurationSettings configurationSettings)
        : base(configurationSettings, false)
    {
    }

    public IQueryable<T> Retrieve<T>() where T : class, IAmAnAggregateRoot
    {
        return GetDbSet<T>().AsNoTracking();
    }
}

public class MainDbContextBase : DbContext
{
    private readonly IConfigurationSettings configurationSettings;
    private readonly bool useLazyLoading;

    protected MainDbContextBase(IConfigurationSettings configurationSettings, bool useLazyLoading)
    {
        this.configurationSettings = configurationSettings;
        this.useLazyLoading = useLazyLoading;
    }

    protected DbSet<T> GetDbSet<T>() where T : class
    {
        return Set<T>();
    }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        if (!optionsBuilder.IsConfigured)
        {
            optionsBuilder
                .UseLazyLoadingProxies(useLazyLoading)
                .UseSqlServer(configurationSettings.ConnectionString);
        }
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        ...
    }
}

}

0 голосов
/ 01 сентября 2018

Там нет ошибки. Поскольку вы не отслеживаете изменения с помощью .AsNoTracking, отложенная загрузка никогда не будет работать. Вы можете использовать .Include ("ChildEntity") в своем запросе или отказаться от использования .AsNoTracking.

...