I was trying to select data using LINQ and I have a list called "products" and I want just these items that exist in products list
var Owner = db.Owners
.Where(m => m.ID == id)
.Include(m => m.Products.Where(item1 => products.Any(item2 => item2.ProductID == item1.ProductID)).ToList())
.FirstOrDefault();
but I'm getting this error :
System.ArgumentException: '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'
CodePudding user response:
Include is meant to fetch complete rows of a table, inclusive primary and foreign keys.
Usually it is not efficient to fetch the complete rows of a table. For example, suppose you have a database with Schools and Students. There is a one-to-many relation between Schools and Students: every School has zero or more Students, every Student attends exactly one School, namely the School that the foreign key refers to.
If you fetch School [10] with its 2000 Students, then every Student will have a foreign key SchoolId with a value 10. If you use Include and fetch complete Student rows you will transfer this value 10 over 2000 times. What a waste of processing power.
A DbContext has a ChangeTracker object. Whenever you fetch data without using Select, so if you fetch complete rows, then the fetched rows are stored in the ChangeTracker, together with a Clone of it. You get a reference to the Clone (or the original, doesn't matter). When you change properties of the fetched data, you change the value in the Clone. When you call SaveChanges
, the values of all properties of all originals in the ChangeTracker are comparer with the values in the Clones. The items that are changed are updated in the database.
So if you fetch School [10] with its Students, you are not only fetching way more data than you will ever use, but you will also store all these Students in the ChangeTracker together with a Cloned Student. If you call SaveChanges for something completely different (the telephone number of the School for instance), then all Students are value compared property by property with their Clones.
Whenever you fetch data using Entity Framework, always use
Select
, and Select only the properties that you actually plan to use. Only fetch complete rows, only use Include if you plan to update the fetched data.
Using Select will also solve your problem:
int ownerId = ...
IEnumerable<Product> products = ...
var Owner = db.Owners.Where(owner => owner.ID == ownerId)
.Select(owner => new
{
// Select only the Owner properties that you actually plan to use
Id = owner.Id,
Name = owner.Name,
// get the Products of this Owner that are in variable products
Products = owner.Products
.Where(product => products.Any(p => p.ProductId == product.ProductId)
.Select(product => new
{
// Select only the Product properties that you plan to use
Id = product.Id,
Price = product.Price,
...
// No need to fetch the foreign key, you already fetched the value
// OwnerId = product.OwnerId,
})
.ToList(),
...
})
.FirstOrDefault();
I used automatic types (new {...}
). If you really want to create Owner and Properties, use:
var Owner = db.Owners.Where(...)
.Select(owner => new Owner
{
Id = owner.Id,
...
Products = owner.Products.Where(...).Select(product => new Product
{
Id = product.Id,
...
})
.ToList(),
})
.FirstOrDefault();
CodePudding user response:
this way it is not working split your query into 2 queries.
var owner = db.Owners
.Include(m => m.Products)
.FirstOrDefault(m => m.ID == id);
if(owner != null)
{
var ownerProducts = owner.Products
.Where(item1 => products.Any(item2 => item2.ProductID == item1.ProductID)).ToList();
}
maybe this way it works.
CodePudding user response:
Try the following:
var productIds = products.Select(x => x.ProductID);
var Owner = db.Owners
.Where(m => m.ID == id)
.Include(m => m.Products.Where(product => productIds.Contains(product.ProductID))
.FirstOrDefault();