Entity Framework Code First IQueryable - c#

I am using Entity Framework Code First and ran into a small road block. I have a class "Person" defined as such:
public class Person
{
public Guid Id { get; set; }
public virtual ICollection<History> History { get; set; }
}
and a class "History" defined as such:
public class History
{
public Guid Id { get; set; }
public virtual Person Owner { get; set; }
public DateTime OnDate { get; set; }
}
However, when I call:
IEnumerable<History> results = person.History
.OrderBy(h => h.OnDate)
.Take(50)
.ToArray();
It appears to pull all of the history for the person, then order it and such in memory. Any recommendations on what I'm missing?
Thanks in advance!

Because you are querying an IEnumerable (ie: LINQ to Objects) not IQueryable (ie: LINQ to Entities) given by EF.
Instead you should use
IEnumerable<History> results = context.History.Where(h => h.Person.Id = "sfssd").OrderBy(h => h.OnDate).Take(50)

This question and the accepted answer are both a bit old. Code like this would, as the original question points, load the entire history for the person from the database - not good!
var results = person
.History
.OrderBy(h => h.OnDate)
.Take(50)
.ToArray();
With EF 6 there is an easy solution. Without rearranging your query, you can have it work the IQueryable way by making use of the DbContext.Entry method, the DbEntryEntity.Collection method, and the DbCollectionEntry.Query method.
var results = dbContext
.Entry(person)
.Collection(p => p.History)
.Query()
.OrderBy(h => h.OnDate)
.Take(50)
.ToArray();

Related

Entity Framework - Include Properties, but exclude single columns

Let's say I have a list of items, which each can reference a list of documents (and other stuff).
In Entity Framework I can get the list like that:
var items = context.Items
.Include(i => i.Documents)
.Include(i => i.OtherProperty)
.ToList()
Now this includes all columns from documents. Is it possible to exclude one of these columns (eg. the "Data"-column which stores the complete document as binary)?
Something like this:
var items = context.Items
.Include(i => i.Documents)
.ButExclude(d => d.Data)
.Include(i => i.OtherProperty)
.ToList()
I know that I could use .Select() and create an object without the data property, but since I use a lot of foreign keys I would rather like to use includes.
I also know that it is better to separate the document's metadata from the actual content, but well... I haven't...
Make another model that doesnt include data that you dont need
public class CreateAndUpdatePropertiesModel
{
public string Documents { get; set; }
public string OtherProperties { get; set; }
}
And then use this model as a property in primary model:
public class ExampleModel
{
public string Documents { get; set; }
public string Data { get; set; }
public string OtherProperties { get; set; }
// And then add the CreateAndUpdateProperties model as a property
public CreateAndUpdateProperties { get; set; }
}
then only Include wanted data
.Include(s => s.CreateAndUpdateProperties)
This Question was previously asked many times:
Exclude columns getting data with Entity Framework
Added as answer due to low amount of rep(cant comment)

EF core + Automapper ID-only related entity collection, how?

I have a simple problem - I would like one of the RESTful endpoints serve a resource DTO (auto-mapped) with its related resources as their IDs only. However there does not seem to be any way to implement it without loading the whole(and heavy) related entities. Consider following (DB first) example model:
public partial class Blog
{
public int Id { get; set; }
public string Url { get; set; }
public virtual ICollection<Post> Posts { get; set; }
}
public partial class Post // some heavy entity
{
public int Id { get; set; }
public string Content { get; set; }
// other properties
}
and its corresponding DTO
// api/v1/blogs serves collection of following type
public class BlogSlimDto
{
public int Id { get; set; }
public string Url { get; set; }
public int[] PostIds { get; set; }
}
a straightforward soltion would be to fetch all the related Posts from database and discard all data except for the IDs, but that can be inefficient or even unfeasible depending on related Post entity size:
var result = ctx.Blogs.Include(blog => blog.Posts) //fecth everything and discard it on next line
.Select(blog => _mapper.Map<BlogSlimDto>(blog));
// simply use a profile that discards Posts but keeps their Ids, e.g.
// .forMember(dto => dto.PostIds, opt => opt.MapFrom(db.Posts.Select(p => p.Id)))
there is similar question which offers a solution using anonymous types, however this does not play well with Automapper at all:
var result = ctx.Blogs.Select(blog => new {
blog.Id,
blog.Url,
PostIds = blog.Posts.Select(b => b.Id),
}).Select(ablog => _mapper.Map<BlogSlimDto>(ablog)); //throws, no mapping and such mapping cannot be defined
The code above will throw during runtime because there no Automapper mapping defined. Even worse, it cannnot be defined because there is no support for anonymous types in Automapper. Moreover, solutions with one-by-one 'manual' property assignment tend to be difficult to maintain.
Is there an alternative solution that would allow EF query without fetching whole related entities while allowing the result to be auto-mapped to the BlogSlimDto?
You can use the queryable extensions:
https://docs.automapper.org/en/stable/Queryable-Extensions.html
var configuration = new MapperConfiguration(cfg =>
cfg.CreateMap<OrderLine, OrderLineDTO>()
.ForMember(dto => dto.Item, conf => conf.MapFrom(ol => ol.Item.Name)));
public List<OrderLineDTO> GetLinesForOrder(int orderId)
{
using (var context = new orderEntities())
{
return context.OrderLines.Where(ol => ol.OrderId == orderId)
.ProjectTo<OrderLineDTO>().ToList();
}
}
Replacing the Item and OrderLine with your Post and Blogs

How to select properties of simulated many-to-many in EFCore with single SQL generated query

EFCore does not support many-to-many relationships without creating a linking entity. I need to efficiently select a subset of properties from the 'other end' of the one-to-many-to-one relationship.
I'd swear this would have an answer already but haven't found it.
With these Models:
public class Book
{
public int BookId { get; set; }
public string Title { get; set; }
public Author Author { get; set; }
public ICollection<BookCategory> BookCategories { get; set; }
}
public class Category
{
public int CategoryId { get; set; }
public string CategoryName { get; set; }
public string ExtraProperties {get; set; }
public ICollection<BookCategory> BookCategories { get; set; }
}
public class BookCategory
{
public int BookId { get; set; }
public Book Book { get; set; }
public int CategoryId { get; set; }
public Category Category { get; set; }
}
This question is an extension of a similar, but different question titled, "Select specific properties from include ones in entity framework core"
I am looking for a query that returns a List<string> categoryNames of the Categories of the Book.
This nested select, using "projection" results in multiple SQL Queries:
var result= await _ctx.Books
.Where(x => x.BookId == id)
.Select(x => x.BookCategorys
.Select(y => y.Category.CategoryName )
.ToList())
.FirstOrDefaultAsync();
Any solution with .Include(x => x.BookCategory).ThenInclude(x => Category) will load all the data form the Server before applying the select.
Is there any query that meets the following criteria?:
Only generates 1 SQL query
Does not load the entire linking entity and or the entire navigation property 2 'hops' in.
Returns only List<string> of CategoryNames.
I infer from this Entity Framework Core generates two select queries for one-to-many relationship, it's not possible.
In general, you cannot control the generated SQL and how many SQL queries are executed by ORM. And EF Core at the time of writing (version 2.0.2) is known to produce N + 1 queries when the query contains collection projection. This is fixed in the next 2.1 release, but still will generate and execute at least 2 queries.
But every rule has exceptions. Since you want to return only a single related collection projection, you can simply use SelectMany instead of the original Select + FirstOrDefault construct. These are equivalent to this scenario, and EF Core is not smart enough to treat the later the same way as the former. Which is understandable counting the fact how many other cases need to be considered. The good thing is that rewriting the LINQ query this way produces the desired single SQL query translation:
var result = await _ctx.Books
.Where(x => x.BookId == id)
.SelectMany(x => x.BookCategorys
.Select(y => y.Category.CategoryName))
.ToListAsync();

Including only Id of related entity in entity framework core

I am currently developing an API with ASP.NET Core and Entity framework core with npgsql as database provider. I have two Entities and they have a one to many relation. The thing is that I only want to include the Id's of the child entity in the JSON result that the "Parent Controller" returns.
These are my entities:
public class Meal {
public int Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string UserId { get; set; }
public User User { get; set; }
public List<Picture> Pictures { get; set; }
public Meal () {
this.Pictures = new List<Pictures>();
}
}
public class Picture {
public int Id { get; set; }
public int MealId { get; set; }
public Meal Meal { get; set; }
public byte[] full { get; set; }
public byte[] small { get; set; }
}
I am however not sure on how to achieve this. Yesterday I came across another SO question which suggested something like this:
public IActionResult Meals () {
var meal = this.context.Meals
.Include(m => m.Pictures.Select(p => p.Id))
.First();
return new JsonResult(meal);
}
This however throws an InvalidOperationException.
My DbContext is very basic, no onModelConfiguring because this code is following convention for as far as I know and it just has two DbSets of the corresponding types. The foreign keys are also correct in the database and callling something like:
var pictures = dbContext.Pictures.Where(p => p.MealId == mealId).ToList();
Works as expected. I have only included the code which I thought was relevant. If more is needed I will include it, but I think this is completely my limited understanding of the queries.
Thank you for your time!
You don't need to change your DB structure, one option is like so:
var db = this.context;
var result = (from meal in db.Meals
where meal.<whatever> == "123"
select new
{
Id = meal.Id,
Title = meal.Title,
Description = meal.Description,
//other required meal properties here.
PictureIds = meal.Pictures.Select(x => x.Id)
}).ToList();
You can do the same thing via lambda as well by using the "Select" method, Linq in such things seem more intuitive to me, however, to each his own... that's your choice.
.Include(m => m.Pictures.Select(p => p.Id)) will not work you have to do
.Include(m => m.Pictures)
And this will get you an array of whole picture model for you (with all properties Id, MealId,..) and in your client side you can choose Id to work with..
There is probably a better answer out there, but this is how I fixed it. Including the entire Picture class was not an option since the binary data would also be included and I did not want to query the server for the data without using it since that is an expensive call to the database.
So what I did is put the binary data in another class called PictureFile (need to think of a better name, but just File was obviously not an option). The class PictureFile just has a reference to the corresponding picture and a byte array with the picture data. That way you can include the Pictures in a Meal without getting the actual files. The client can than later decide which pictures it needs and request them by PictureId.

Query base type definiton as derived type using Entity Framework

I have the following POCOs (using Code First and EF 6.0.0 alpha 3):
public class RevBase
{
[Key]
public int Id{ get; set; }
}
public class ItemBase
{
[Key]
public int Id { get; set; }
public List<RevBase> Revs { get; set; }
}
public class RevDev : RevBase
{
public string Content { get; set; }
}
public class ItemDev : ItemBase
{
public string Title { get; set; }
}
and the following context
public class MyContext : DbContext
{
DbSet<ItemDev> Items { get; set; }
}
Now I would like to query context.Items.Include(i => i.Revs) but tell the EF somehow it should load the Revs as RevDev not RevBase.
Is that possible or do I need to load them as RevBase and make another query to get the according RevDev instances?
One other way I tried was creating a second relation from RevDev to ItemDev, but then EF also creates a second foreign key column in the DB which is not really necessary...
load the Revs as RevDev not RevBase
If a given RevBase is not a RevDev you can't load it as RevDev. (Not every animal is a dog. You cannot make every animal a dog, some are cats.)
Actually, what you need, I believe, is a filter by the type, which is generally a problem when using Include because it doesn't support any filtering at all. I see two options you have:
Use explicit loading (which supports filtering):
var items = context.Items.ToList();
foreach (var item in items)
context.Entry(item).Collection(i => i.Revs).Query()
.Where(r => r is RevDev)
.Load();
These are 1 + N separate database queries.
Use a projection:
var items = context.Items
.Select(i => new
{
Item = i,
RevDevs = i.Revs.Where(r => r is RevDev)
})
.AsEnumerable()
.Select(a => a.Item)
.ToList();
This is only one database query. Automatic relationship fixup should populate the Item.Revs collection with the loaded RevDevs.
Just found out that Entity Framework does that by default. I had another error in my code why it did not work...

Categories

Resources