EF Core не возвращает более 3 дочерних объектов - PullRequest
2 голосов
/ 11 июля 2020
• 1000 .Children.Children.Children .. Однако, когда у меня более 3 дочерних объектов, он возвращает только первый дочерний объект, т.е. File.Children

Свойство child записывается в моем классе объекта следующим образом:

public virtual ICollection<Files> Children { get; set; }

Что я здесь упускаю?

Ответы [ 3 ]

1 голос
/ 12 июля 2020

Вот решение.

private static async Task<IReadOnlyList<File>> GetRootFilesAndChildren(IQueryable<File> source, int levels = 3)
{
    var dataSource = source.Where(x => x.ParentFileId == null); //root files

    if (levels == 0)
        return await dataSource.ToListAsync();

    var dataSourceIncludable = dataSource.Include(x => x.Children);
    for (var i = 1; i < levels; i++)
        dataSourceIncludable = dataSourceIncludable.ThenInclude(x => x.Children);

    var files = await dataSourceIncludable.ToListAsync();
    return files;
}

EDIT Я забыл включить модели и конфигурацию.

public class File
{
    public int FileId { get; set; }
    public string Name { get; set; }
    public int? ParentFileId { get; set; }
    public File ParentFile { get; set; }
    public virtual ICollection<File> Children { get; set; }
}

DbContext для SQLite.

public class AppDbContext : DbContext
{
    public DbSet<File> Files { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder options)
        => options.UseSqlite("Data Source=demo.db");

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        modelBuilder.Entity<File>(b =>
        {
            b.ToTable("Files");

            b.HasMany(x => x.Children)
                .WithOne(x => x.ParentFile)
                .IsRequired(false);
        });
    }
}
1 голос
/ 12 июля 2020

Проблема в том, что .Include не будет генерировать рекурсивный SQL. Он просто соединяет набор внешних отношений для детей с вашими файлами, которые должны быть на одну глубину вложенности.

Если бы вы вытащили весь набор файлов с .Include дочерними элементами, то у вас будет набор кромочных элементов. Чтобы превратить набор ребер в связанный граф, вам нужно будет вручную скомпоновать это в вашем коде.

Создайте метод, который будет перебирать каждый объект Files, и если он связан с Children установите, затем подключите его туда. Это создаст ваш граф объектов и позволит вам перемещаться так, как вы ожидаете.

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

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

Context.Files.Include(
    p => p.Children.Select( 
        pc => pc.Children.Select( 
            pcc => pcc.Children
        )
    )
);
0 голосов
/ 12 июля 2020

В зависимости от вашего сценария, есть несколько способов достичь sh того, что вы хотите.

Самый простой способ - просто загрузить все сущности (EF Core выполнит исправление отношений за вас), а затем просто работать с родителем, который вам действительно нужен:

public class File
{
    public int FileId {get; set;}
    public string Name {get; set;}
    public int ParentFolderId {get; set;}

    public Folder ParentFolder {get; set;}

    public string Path => ParentFolder?.Path + "/" + Name;
}

public class Folder
{
    public int FolderId {get; set;}
    public string Name {get; set;}
    public int ParentFolderId {get; set;}
    
    public Folder ParentFolder {get; set;}
    public ICollection<Folder> SubFolders {get; set;} = new HashSet<Folder>();
    public ICollection<Files> Files {get; set;} = new HashSet<File>();

    public string Path => ParentFolder?.Path + "/" + Name;
}

context.Files.Load();
context.Folders.Load();

var rootFolderICareAbout = context.Folders.Local.FirstOrDefault(f => f.Path == "/My/Folder/Path");

Вот еще один способ, который работает с одним классом модели:

using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;

namespace IssueConsoleTemplate
{
    public class FileOrFolder
    {
        public int FileOrFolderId { get; set; }
        public string Name { get; set; }
        public bool IsFolder { get; set; }
        public int? ParentId { get; set; }

        public FileOrFolder Parent { get; set; }
        public ICollection<FileOrFolder> Children { get; set; } = new HashSet<FileOrFolder>();

        public bool HasAncestor(FileOrFolder parent) => Parent == parent || (Parent?.HasAncestor(parent) ?? false);
    }

    public class Context : DbContext
    {
        public DbSet<FileOrFolder> FileOrFolders { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder
                .UseSqlServer(@"Data Source=.\MSSQL14;Integrated Security=SSPI;Initial Catalog=So62854494")
                .UseLoggerFactory(
                    LoggerFactory.Create(
                        b => b
                            .AddConsole()
                            .AddFilter(level => level >= LogLevel.Information)))
                .EnableSensitiveDataLogging()
                .EnableDetailedErrors();
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<FileOrFolder>(
                entity =>
                {
                    entity.HasOne(f => f.Parent)
                        .WithMany(f => f.Children)
                        .HasForeignKey(f => f.ParentId);

                    entity.HasData(
                        new FileOrFolder {FileOrFolderId = 1, Name = "RootFolder", IsFolder = true, ParentId = null},
                        new FileOrFolder {FileOrFolderId = 2, Name = "RootFile1", IsFolder = false, ParentId = 1},
                        new FileOrFolder {FileOrFolderId = 3, Name = "RootFile2", IsFolder = false, ParentId = 1},
                        new FileOrFolder {FileOrFolderId = 4, Name = "SubFolder", IsFolder = true, ParentId = 1},
                        new FileOrFolder {FileOrFolderId = 5, Name = "SubFile1", IsFolder = false, ParentId = 4},
                        new FileOrFolder {FileOrFolderId = 6, Name = "SubFile2", IsFolder = false, ParentId = 4});
                });
        }
    }
    
    internal static class Program
    {
        private static void Main()
        {
            using var context = new Context();
            
            context.Database.EnsureDeleted();
            context.Database.EnsureCreated();

            var rootFolder = context.FileOrFolders.Single(f => f.ParentId == null);

            var subFilesOrFolders = context.FileOrFolders
                .Include(f => f.Parent)
                .AsEnumerable()
                .Where(f => f.HasAncestor(rootFolder))
                .ToList();
            
            Debug.Assert(subFilesOrFolders.Count == 5);
            Debug.Assert(subFilesOrFolders.First(f => f.IsFolder).Name == "SubFolder");
            Debug.Assert(subFilesOrFolders.First(f => f.IsFolder).Children.Count == 2);
        }
    }
}

Это быстрые и простые решения. Для повышения производительности необходимо выполнять иерархические запросы.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...