简体   繁体   中英

EF Core One To Many both way navigation is not working

I work on a .NET Core, API with EF-CORE

I created DTO to show it on UI Side. I am filtering data for that dto. The issue is i cant use two way navigation property for each table

I will explain my problem on two examples to be an example.

Lets say i have two model like this:

public class Blog
    {
        public int BlogId { get; set; }
        public string Url { get; set; }
        public List<Post> Posts { get; set; }
    }
 public class Post
    {
        public int PostId { get; set; }
        public string Title { get; set; }
        public string Content { get; set; }
        public Blog Blog { get; set; }
        public int BlogId { get; set; }
    }

Then i am creating query:

this is work:

 var query = await _context.Posts
               .Include(c => c.Blog)
               .Select(c => new DenemeDto
               {
                   DenemeName=c.Title
               }).ToListAsync();

this isn't work:

var query = await _context.Blogs
               .Include(c => c.Posts)
               .Select(c => new DenemeDto
               {
                   DenemeName=c.Title
               }).ToListAsync();
            return Ok(query);

Creation Method:

modelBuilder.Entity("Entities.Concrete.Blog", b =>
                {       b.Property<int>("BlogId")
                        .ValueGeneratedOnAdd()
                        .HasColumnType("int");      
        SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("BlogId"), 1L, 1);
                    b.Property<string>("Url")
                        .IsRequired()
                        .HasColumnType("nvarchar(max)");
                    b.HasKey("BlogId");
                    b.ToTable("Blogs");});




modelBuilder.Entity("Entities.Concrete.Post", b =>
                {       b.Property<int>("PostId")
                        .ValueGeneratedOnAdd()
                        .HasColumnType("int");   
             SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("PostId"), 1L, 1);
                    b.Property<int>("BlogId")
                        .HasColumnType("int");
                    b.Property<string>("Content")
                        .IsRequired()
                        .HasColumnType("nvarchar(max)");
                    b.Property<string>("Title")
                        .IsRequired()
                        .HasColumnType("nvarchar(max)");
                    b.HasKey("PostId");
                    b.HasIndex("BlogId");
                    b.ToTable("Posts"); });

I wonder why?

Kind Regards...

Your assumptions are a bit confused about what Include does.

var query = await _context.Posts
    .Include(c => c.Blog)
    .Select(c => new DenemeDto
    {
        DenemeName=c.Title
    }).ToListAsync();

Include means "eager load this relative". This applies when you want to return entities so that their related entities are loaded along with them. Typically this might be a case like where you are loading an individual Blog and want to access its Posts:

var blog = await _context.Blog
    .Include(b => b.Posts)
    .SingleAsync(b => b.BlogId == blogId);

Without the Include , EF would load the Blog, but if you try to access the Posts underneath it, this would trigger a Lazy Load (if enabled) or simply be #null or an empty collection. Lazy loading is a feature that has it's uses but can impose a significant cost. If you know you will need related data you eager load it with Include .

When you use Select you are doing something called Projection which does not require eager loading. EF will build a query suited to get the data you request. So your query can be simplified to just:

var query = await _context.Posts
    .Select(c => new DenemeDto
    {
        DenemeName=c.Title
    }).ToListAsync();

Which effectively translates to: "SELECT Title FROM Posts" which when that comes back, EF will populate DenemeDto models with.

So looking at your second example:

var query = await _context.Blogs
    .Include(c => c.Posts)
    .Select(c => new DenemeDto
    {
        DenemeName=c.Title
    }).ToListAsync();

Again, the Include does nothing here, so your query will translate to:

"SELECT Title FROM Blogs"

Which will either cause a compile time complaint if a Blog doesn't have a Title property, or give you the titles of your blogs, not your posts.

If your Post didn't have a reference back to a Blog and you only had Blog.Posts to work with and wanted the Post Titles:

var query = await _context.Blogs
    .SelectMany(c => Posts
        .Select( p => new DenemeDto
        {
            DenemeName=c.Title
        }).ToList())
   .ToListAsync(); 

A better example of how Projection can work with relationships would be if you wanted to select Blogs, but get a count of their Posts:

var query = await _context.Blogs
    .Select( b => new BlogDto
    {
        BlogName = b.Title
        PostCount = b.Posts.Count()
    }).ToListAsync(); 

If we loaded entities we would be loading every blog and every post into memory just to get this information. With the above query EF would build a query that just returned the Blog Name and the Count of posts for each blog. Much faster, and note we don't need to worry about eager or lazy loading costs.

Projection is definitely a useful tool when reading data because it helps EF build more efficient queries. Eager loading a lot of related data creates Cartesian products that result in large amounts of data coming back to the server from the DB that EF then has to reduce down to the entity models. This takes time, memory, and in many cases bandwidth. Select means EF can build more succinct queries and potentially leverage indexes on the database to save time, memory, and bandwidth.

In this example that you provided:

var query = await _context.Blogs
           .Include(c => c.Posts)
           .Select(c => new DenemeDto
           {
               DenemeName=c.Title
           }).ToListAsync();
        return Ok(query);

c is a Blog , so doesn't have a Title .

I'm not sure what the aim of your query is, but if you wanted to get all posts for a blog, you could do:

var query = await _context.Blogs
                          .SelectMany(x => x.Posts)
                          .Select(x => new DenemeDto
                          {
                              DenemeName = x.Title
                          })
                          .ToListAsync();

Also, because you're doing a Select to a non-entity, you don't need to use Include .

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