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...
CodePudding user response:
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.
CodePudding user response:
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
.