尽管急切获取 "attempt was made to lazy-load navigation property on detached entity"

Getting "attempt was made to lazy-load navigation property on detached entity" despite eager fetch

我正在使用启用延迟加载的 Entity Framework Core 2.1.2,并且正在使用 AsNoTracking 执行查询。我正在使用 Include 引入我的导航 属性(一个集合)。

如果我所有的实体在他们的集合中至少有一个 child 那么一切正常。

但是,如果我的任何实体没有 children,那么我会得到一个错误:

System.InvalidOperationException: Error generated for warning 'Microsoft.EntityFrameworkCore.Infrastructure.DetachedLazyLoadingWarning: An attempt was made to lazy-load navigation property 'Children' on detached entity of type 'ParentProxy'. Lazy-loading is not supported for detached entities or entities that are loaded with '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 中的错误? (我也发了 an issue there。)

没有错误。 由于您没有使用 .AsNoTracking 跟踪更改,因此延迟加载永远不会起作用。 您可以在查询中使用 .Include("ChildEntity") 或放弃使用 .AsNoTracking.

为了后代,这里是 response from the EF Core team:

This is because lazy-loading isn't supported for NoTracking queries (#10042) but we tried to not make it throw if it looked like lazy-loading wasn't needed. In retrospect it might have been better to always throw. Note that the warning can be configured to not throw using ConfigureWarnings in the DbContextOptionsBuilder.

以防万一它对某人有用,我最终做的是创建第二个 "ReadOnlyRepository" 配置为不使用延迟加载并始终 return 未跟踪集。我使用此存储库进行查询,我永远不会对任何实体进行持久更改,结果集可能很大并且需要良好执行。

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)
    {
        ...
    }
}

}