Unable to write an Include query with FirstOrDefault() and condition - c#

I'm writing an entity framework query which needs Eager loading multiple levels based on condition.
var blogs1 = context.Blogs
.Include(x => x.Posts.FirstOrDefault(h => h.Author == "Me"))
.Include(x => x.Comment)
.FirstOrDefault();
public class Blog
{
public int BlogId { get; set; }
public virtual ICollection<Post> Posts { get; set; }
}
public class Post
{
public int PostId { get; set; }
public string Author { get; set; }
public int BlogId { get; set; }
public virtual ICollection<Comment> Comments { get; set; }
}
public class Comment
{
public int PostId
public int CommentId { get; set; }
public string CommentValue { get; set;}
}
var blogs2 = context.Blogs
.Include("Posts.Comments")
.ToList();
I expect result to have first or default Blog and first or default Post for that blog by author "Me" and a list of all comments.
When query for blogs1is executed I see following exception
blogs2 query works as expected
The Include path expression must refer to a navigation property defined on the type. Use dotted paths for reference navigation properties and the Select operator for collection navigation properties.
Parameter name: path

FirstOrDefault executes the query and you can not use it inside Include as its purpose is to include navigational properties. You will need to modify the query to one of the below two ways:
Method 1: Its two two step process:
var blogs1 = context.Blogs
.Include(x => x.Posts.Select(p => p.Comments))
**// .Include(x => x.Comment) // This include is incorrect.**
.FirstOrDefault(x => x.Posts.Any(h => h.Author == "Me"));
var myPosts = blogs1?.Posts.Where(p => p.Author == "Me");
Method 2:
var myPosts = context.Posts.Include(p => p.Blog).Include(p => p.Comments).Where(p => p.Author == "Me");

Related

Using LINQ to query four nested entities. - Include Where condition

I have four classes Offer, Section, Field, and Option:
Where the offer has sections and every section has some fields and each field has some options as shown:
public class Offer
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Section> Sections { get; set; }
}
public class Section
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Field> Fields { get; set; }
}
public class Field
{
public int Id { get; set; }
public string Type { get; set; } //[question, group]
public ICollection<Option> Options { get; set; }
}
public class Option
{
public int Id { get; set; }
public string Name { get; set; }
}
I tried to get the offer by id including the nested entities and this code works perfectly:
var offer = _context.Offers
.Include(o => o.Sections
.Select(s => s.Fields
.Select(f => f.Options)))
.FirstOrDefault(o => o.Id == offerId);
The problem is when I try to filter the Fields by 'type' like this:
var offer = _context.Offers
.Include(o => o.Sections
.Select(s => s.Fields.Where(f => f.Type == "question")
.Select(f => f.Options)))
.FirstOrDefault(o => o.Id == offerId);
and I get this error:
The Include path expression must refer to a navigation property defined on the type. Use dotted paths for reference navigation properties and the Select operator for collection navigation properties.
Parameter name: path
I've reviewed lots of questions and still cannot achieve that :(
Linq: query with three nested levels
EF LINQ include nested entities [duplicate]
Using LINQ to query three entitites. - Include path expression must refer to a navigation property defined on the type
Include() is used for Eager loading. It is the process whereby a query for one type of entity also loads related entities as part of the query, so that we don't need to execute a separate query for related entities. Where() is currently not supported inside Include.
If you want to just filter the result you can do something like this :
var offer = _context.Offers
.Include(o => o.Sections
.Select(s => s.Fields
.Select(f => f.Options)))
.FirstOrDefault(o => o.Id == offerId);
var filtered_offer =
new Offer
{
Sections = offer.Sections.Select(S => new Section
{
Id = S.Id,
Name = S.Name,
Fields = S.Fields.Where(f => f.Type == "question").ToList()
}).ToList(),
Id = offer.Id,
Name = offer.Name
};

Filtering "include" entities in EF Core

I have the following models
public class Person
{
public int PersonId { get; set; }
public string Name { get; set; }
public List<PersonRole> PersonRoles { get; set; }
}
public class RoleInDuty
{
public int roleInDutyId { get; set; }
public string Name { get; set; }
public int typeOfDutyId { get; set; }
public TypeOfDuty typeOfDuty { get; set; }
public List<PersonRole> PersonRoles { get; set; }
}
public class PersonRole
{
public int PersonId { get; set; }
public Person Person { get; set; }
public int RoleInDutyId { get; set; }
public RoleInDuty RoleInDuty { get; set; }
}
And now I can load all people with all their roles using the following code:
var people = _context.Persons
.Include(p => p.PersonRoles)
.ThenInclude(e => e.RoleInDuty).ToList();
But I wantn't load all data to List, I need load PersonRole according entered typeOfDutyId.
I am trying to solve this with the following code
people = _context.Persons
.Include(p => p.PersonRoles
.Where(t=>t.RoleInDuty.typeOfDutyId == Id)).ToList();
But VS throw error
InvalidOperationException: The Include property lambda expression 'p
=> {from PersonRole t in p.PersonRoles where ([t].RoleInDuty.typeOfDutyId == __typeOfDuty_typeOfDutyId_0) select
[t]}' is invalid. The expression should represent a property access:
't => t.MyProperty'. To target navigations declared on derived types,
specify an explicitly typed lambda parameter of the target type, E.g.
'(Derived d) => d.MyProperty'. For more information on including
related data, see http://go.microsoft.com/fwlink/?LinkID=746393.
As I understand I can't access property RoleInDuty.typeOfDutyId because i'm not include it yet.
I solved this problem with the following code
people = _context.Persons
.Include(p => p.PersonRoles)
.ThenInclude(e=>e.RoleInDuty).ToList();
foreach (Person p in people)
{
p.PersonRoles = p.PersonRoles
.Where(e => e.RoleInDuty.typeOfDutyId == Id)
.ToList();
}
Finally, filter in Include with ef core 5. Details in MSDN doc: https://learn.microsoft.com/en-us/ef/core/querying/related-data#filtered-include
Var people = _context.Persons
.Include(p => p.PersonRoles
.Where(t=>t.RoleInDuty.typeOfDutyId == Id))
.ToList();
var blogs = context.Blogs
.Include(e => e.Posts.OrderByDescending(post => post.Title).Take(5)))
.ToList();
devnull show the next How to filter "Include" entities in entity framework?, and there the same problem, I read it, and find the answer. Solve my problem can with the next:
var temp = _context.Persons.Select(s => new
{
Person = s,
PersonRoles= s.PersonRoles
.Where(p => p.RoleInDuty.typeOfDutyId == this.typeOfDuty.typeOfDutyId)
.ToList()
}).ToList();

LINQ Filter Nth Level Nested list

I have the following hierarchy in my project :
Activity
Task
Step
Responses
This means an activity has many tasks, which in turn has many steps , a step has many responses.
Here are my POCO classes:
public class Activity
{
public virtual ICollection<Task> Tasks { get; set; }
}
public class Task
{
public virtual ICollection<Step> Steps{ get; set; }
}
public class Step
{
public virtual int DisplayOrder { get; set; }
public virtual ICollection<Response> Responses{ get; set; }
}
public class Response
{
public virtual int UserId { get; set; }
public virtual int ResponseText{ get; set; }
}
Now, I need to return a List<Activity> which is sorted by ActivityId and has Steps ordered by DisplayOrder and also a Responses which only belong to a given UserId.
Here's what I have tried:
Activities.ToList()
.ForEach((activity) => activity.Tasks.ToList()
.ForEach((task) => task.Steps = task.Steps.OrderBy(s => s.DisplayOrder).ToList()
.ForEach((step) => step.Responses = step.Responses.Where(r => r.UserId == RequestorUserId)))
));
Which is giving me an error:
Cannot implicitly convert type 'void' to ICollection<Step>
The ForEach extension method doesn't return anything and you are trying to set the result to task.Steps. You can achieve the desired result by using the Select method to alter the objects in line
Activities.ToList()
.ForEach((activity) => activity.Tasks.ToList()
.ForEach((task) => task.Steps = task.Steps.OrderBy(s => s.DisplayOrder).ToList()
.Select((step) =>
{
step.Responses = step.Responses.Where(r => r.UserId == 1).ToList();
return step;
}).ToList()));
Also It might be worth changing your types if possible to IEnumerable rather than ICollection as you then won't need all of the ToList() calls.
If you need to assign the result then you'll need to replace the first ForEach as well - try something like:
var a = Activities.ToList()
.Select((activity) =>
{
activity.Tasks.ToList()
.ForEach((task) => task.Steps = task.Steps.OrderBy(s => s.DisplayOrder).ToList()
.Select((step) =>
{
step.Responses = step.Responses.Where(r => r.UserId == 1).ToList();
return step;
}).ToList());
return activity;
});

Linq to entities with join

I'm having some problem with my linqToEntities query. The Product is missing in the query result. Is there any way to return the ProductQuantity with the Product property correctly with a linqToEntities expression?
public class ProductQuantity
{
public string Id { get; set; }
public string SomeProperty { get; set; }
public Product Product { get; set; }
public Guid ProductId { get; set; }
}
public class Product
{
public Guid Id { get; set; }
public string SomeProperty { get; set; }
//...
}
// MyId is the ProductId I need
// The following will return all productQuantity detail but the Product property will be null
var result = myEntities.ProductQuantities.Include(x => x.Product).Where(x => x.ProductId == MyId)
// The following will work but I want to avoid refilling the object like this :
var result = myEntities.ProductQuantities.Include(x => x.Product).Where(x => x.ProductId == MyId)
.Select(y => new ProductQuantity{ SomeProperty = y.SomeProperty, Product = y.Product});
What is the proper way to do this with linq to entities? Why the product is not just simply returned with the query ?
Thanks
EDIT 1
Look like my problem is releated to .Include() when using more than one include.
Just add a Category to ProductQuantity in the preceding example :
//This will return the product but not the category
var result = myEntities.ProductQuantities.Include(x => x.Product).Include(x=> x.Category).Single(x => x.ProductId == MyId)
//This will return the category but not the product
var result = myEntities.ProductQuantities.Include(x => x.Category).Include(x=> x.Product).Single(x => x.ProductId == MyId)
Why only one include can be used and only the first one is working??????? (a saw tons of similar example on the net?)
Any help?
Seems like there is a problem when the same entity is used in any other include. (ex: Product.Unit and Product.AlternateUnit cannot be retreived at the same time if the same entity is used ie:unit) I dont really understand why but I use separate query to fetch the data that cannot be retrieved by the include.

query multi-level entity with filter at the lowest level

So I have 3 entity classes:
public partial class Event
{
public Event()
{
Recurrences = new HashSet<Recurrence>();
}
public int Id { get; set; }
public ICollection<Recurrence> Recurrences { get; set; }
}
public partial class Recurrence
{
public Recurrence()
{
AspNetUsers = new HashSet<AspNetUser>();
}
public int Id { get; set; }
public int EventId { get; set; }
public ICollection<AspNetUser> AspNetUsers { get; set; }
}
public partial class AspNetUser
{
public AspNetUser()
{
Recurrences = new HashSet<Recurrence>();
}
public string Id { get; set; }
public string UserName { get; set; }
public ICollection<Recurrence> Recurrences { get; set; }
}
I would like to get the event given the aspnetuser.id using line to entity. so far this is what I have but it's returning an error:
// GET: api/Events?userId={userId}
public IQueryable<Event> GetEvents(string userId)
{
return db.Events
.Include(e => e.Recurrences
.Select(u => u.AspNetUsers.Where(i => i.Id == userId)));
}
When I exclude the where clause it works fine. Please help.
Thanks in advance!
I don't think Include() means what you think it means. (https://msdn.microsoft.com/en-us/library/bb738708%28v=vs.110%29.aspx) What it does is tell the db set to be sure to bring in relationships for that object. By default (last I checked), the db context will auto pull in all relationships, so this isn't necessary. However, if you've turned off the lazy-loading (http://www.entityframeworktutorial.net/EntityFramework4.3/lazy-loading-with-dbcontext.aspx) then you'll need to .Include() all the relationships you want to have in the query.
This should solve your problem. I don't guarantee the SQL generated won't be silly, though.
If you have lazy-loading turned on:
db.Events.Include("Recurrences").Include("Recurrences.AspNetUsers")
.Where(e => e.Recurrences
.Any(r => r.AspNetUsers
.Any(u => u.Id ==userId)));
If you have lazy-loading turned off:
db.Events
.Where(e => e.Recurrences
.Any(r => r.AspNetUsers
.Any(u => u.Id ==userId)));
Also, if you have trouble seeing errors, you can .ToList() the query before returning so that it fails in your code and not deep inside the Web API stack. Personally, I like to do this so that I can try/catch the query and handle it properly.

Categories