web-dev-qa-db-ja.com

熱心なフェッチにもかかわらず、「デタッチされたエンティティのナビゲーションプロパティを遅延ロードする試みが行われた」

Entity Framework Core 2.1.2で遅延読み込みを有効にしていて、AsNoTrackingを使用してクエリを実行しています。 Includeを使用して、ナビゲーションプロパティ(コレクション)を取り込みます。

私のすべてのエンティティのコレクションに少なくとも1つの子がある場合、それはすべて正常に機能します。

ただし、エンティティに子がない場合、エラーが発生します。

System.InvalidOperationException: 'Microsoft.EntityFrameworkCore.Infrastructure.DetachedLazyLoadingWarning:警告のために生成されたエラー:ナビゲーションプロパティ' Children 'をタイプ' 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のバグですか? (私は そこの問題 も投稿しました。)

7

後世のために、これが EF Coreチームからの応答 です。

これは、NoTrackingクエリではレイジーロードがサポートされていないためです(#10042)。レイジーロードが必要ないように見える場合は、スローしないようにしました。振り返ってみると、常に投げた方がいいかもしれません。警告は、DbContextOptionsBuilderのConfigureWarningsを使用してスローしないように構成できます。

誰かのために使用する場合に備えて、レイジーロードを使用せず、常に追跡されていないセットを返すように構成された2番目の "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)
    {
        ...
    }
}

}

3

バグはありません。 .AsNoTrackingを使用して変更を追跡していないため、遅延読み込みは機能しません。クエリで.Include( "ChildEntity")を使用するか、.AsNoTrackingの使用をあきらめることができます。