简体   繁体   中英

Load Collection Items In EF Core (OwnsMany)

How to query and filter data in a collection property that configured to its owner by OwnsMany function?

This is the sample I've tried:

using (var context = new BloggingContext())
{
    context.Database.OpenConnection();
    context.Database.EnsureCreated();

    context.Blogs.Add(sampleBlog);
    context.SaveChanges();

    var blog = context.Blogs.Single(b => b.BlogId == 1);

    var goodPosts = context.Entry(blog)
        .Collection(b => b.Posts)
        .Query()
        .Where(p => p.Title == "...")
        .ToList();
}

and Model classes:

public class Post
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

}
public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    public List<Post> Posts { get; set; }
}

and dbContext class:

public class BloggingContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Blog>().OwnsMany(x => x.Posts, post =>
        {
            post.ToTable("Posts");
            post.HasKey("Id");

            post.Property(x => x.Title);
            post.Property(x => x.Content);
        });
        modelBuilder.Entity<Blog>()
            .ToTable("Blogs");
    }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        var connection = new SqliteConnection("Data Source=:memory:");
        optionsBuilder.UseSqlite(connection);
    }
}

I'm getting this exception:

System.Reflection.TargetInvocationException: 'Exception has been thrown by the target of an invocation.'

Inner Exception:

ArgumentException: Expression of type 'System.Collections.Generic.List`1[Post]' cannot be used for return type 'Post'

Unfortunately you are hitting EF Core bug/defect/issue, and more specifically in their Query() method implementation for owned entity type collections.

It is reproducible in both latest at this time official EF Core 3.1.7 version and EF Core 5 preview, so it's worth reporting it to their GitHub Issue Tracker . The code after Query() is irrelevant, the issue is reproduced with just

context.Entry(blog).Collection(b => b.Posts).Query();

Now the workaround. The method in question is mainly for explicit loading. But navigations to owned entity types are always eager loaded, probably that's why they've missed that case when added collections of owned entity types feature (it works for regular one-to-many navigations and regular/owned reference navigations). Whatever the reason is, until they fix it you should not use Query() method, but compose the query manually with LINQ. For instance, use the following equivalent of the sample Query method:

context.Blogs.Where(b => b.BlogId == blog.BlogId).SelectMany(b => b.Posts)

But now it imposes another error/requirement:

A tracking query projects owned entity without corresponding owner in result. Owned entities cannot be tracked without their owner. Either include the owner entity in the result or make query non-tracking using AsNoTracking() .

So you also need to add AsNoTracking() to your goodPosts query.

Or, in this particular case, since owned entity collection is already loaded/tracked along with owner, use LINQ to Objects for querying the materialized collection in memory, which is achieved by replacing Query() with CurrentValue :

var goodPosts = context.Entry(blog)
    .Collection(b => b.Posts)
    .CurrentValue
    .Where(p => p.Title == "...")
    .ToList();

or directly using the navigation property:

var goodPosts = blog.Posts
    .Where(p => p.Title == "...")
    .ToList();

Great answer by @Ivan-Stoev.

Regarding on AsNoTracking():

If you want to update parent entity and/or it's child entity use this

var blog = context.Blogs.FirstOrDefault(b => b.BlogId == 1);
//Manipulating the blog here.
context.SaveChange();

But if you need to read only the data and no updates for the blog then use this

var blog = context.Blogs.AsNoTracking().FirstOrDefault(b => b.BlogId == 1);
//What ever code you have here.

A good additional article that you can check on is this Breaking changes included in EF Core 3.0 .

Happy coding, cheers!

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