简体   繁体   中英

Asynchronous projecting entities with child collection properties using EF Core

Using EF Core, I want to asynchronously obtain a list of FooModel which have a collection property of ChildModel.

public class FooModel
{    
    public Guid Id { get; set; }    
    public string Name { get; set; }
    public List<ChildModel> Childs { get; set; }
}

While the synchronous version returns without issues, the async one will cause the application to freeze.

//Async version.
public static async Task<List<FooModel>> ListFooModelAsync()
{
    using (var db = new AppDbContext())
    {
        var foo_items = await db.Foos
            .Include(e => e.Childs)
            .Select(e => new FooModel
            {
                Id = e.Id,            
                Name = e.Name,
                Childs = e.Childs.Select(
                    child => new ChildModel { Id = child.Id, Name = child.Name })
                    .ToList()
            })
            .ToListAsync()
            .ConfigureAwait(false);
        return foo_items;
    }
}

I think the ToList() call on Childs is causing a deadlock somewhere in the pipeline.

If I remove the .ToList() in the Childs construction line it wont freeze and return the list of FooModel, but its Childs collection will be of type Microsoft.EntityFrameworkCore.Query.Internal.AsyncLinqOperatorProvider.EnumerableAdapter . As soon as I try to use the result in the client the application stop responding, supposedly because EF tries to resolve Childs collection but there is not a DbContext available at that point.

Any thought on how to resolve this?

//Sync version works fine.
public static List<FooModel> ListFooModel()
{
    using (var db = new AppDbContext())
    {
        var foo_items = db.Foos
            .Include(e => e.Childs)
            .Select(e => new FooModel
            {
                Id = e.Id,            
                Name = e.Name,
                Childs = e.Childs.Select(
                    child => new ChildModel { Id = child.Id, Name = child.Name })
                    .ToList()
            })
            .ToList();
        return foo_items;
    }
}

You can break the fetching from the database and the re-shaping into separate queries like this:

    public static async Task<List<FooModel>> ListFooModelAsync()
    {
        using (var db = new AppDbContext())
        {
            var foo_items = await db.Foos
                .Include(e => e.Childs)
                .ToListAsync();

            var results = foo_items
                .Select(e => new FooModel
                {
                    Id = e.Id,
                    Name = e.Name,
                    Childs = e.Childs.Select(
                        child => new ChildModel { Id = child.Id, Name = child.Name })
                        .ToList()
                }).ToList();

            return results;
        }
    }
public static async Task<List<FooModel>> ListFooModelAsync()
{
    using (var db = new AppDbContext())
    {
        var foo_items = await db.Foos
            .Select(e => new FooModel
            {
                Id = e.Id,            
                Name = e.Name,
                Childs = e.Childs.Select(
                    child => new ChildModel { Id = child.Id, Name = child.Name })
            }).ToList()
            .ToListAsync();
        return foo_items;
    }
}

I'm doing this very type of nested projection in my app (slightly modified in my answer). If this is still causing deadlocks for you, maybe you need to examine how you are calling this procedure. I'm am full async from Controller down to Database so maybe that your issue? Note- I removed the Include, you shouldn't need it since you are projecting all the columns you are returning.

EDIT:

Initially I only had EF6 available and what I had posted was working. You're right, EF Core seems to be working differently. I added the ToList() to my code, so it's basically the same as your first post, but this works just fine for me, however, the execution basically makes n+1 DB calls. Maybe it will be addressed at some point and you can drop the inner ToList. Another thought extending what David Browne had to say would be to make two separate projection queries and just quickly join them, so basically only 2 DB reads, like this:

var foo_outer = await db.Foos
                .Select(e => new FooModel
        {
            Id = e.Id,            
            Name = e.Name,
            Childs = new List<ChildModel>()
        }).ToListAsync();


var foo_inner = await db.Childs
                .Where(x => foo_outer.Select(y => y.id).Contains(x.FoosForeignKey))
                .Select(x => new
                {
                    Id = x.Id,
                    Name = x.Name,
                    FooKey= x.FoosForeignKey
                }).ToListAsync();

var foo_items= foo_outer.Select(x => new
            {
                Id = x.Id,
                Name = x.Name,
                Childs = foo_inner.Where(y => y.FooKey == x.Id).ToList()
            }); 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM