I want to select only those parents whose all children have status = success
I also want to include these children for filtered parents
So the corresponding SQL statement that returns expected result is
select * from Parent p
join Child c on c.ParentId = p.ParentId
where c.Status = 'success'
I want to write the same using EF
var result = await _dbcontext.Parents
.Where(x => x.Children.All(y => y.Status == "success"))
.ToListAsync();
this is not working
Update 1
Sorry the SQL statement above is also not working as expected.
it should only return those parents whose all children have status success. So if parent has 5 children and 4 children have success and 1 does not have success then it should not return that parent
Update 2
I have already disabled the lazy loading
dbContext.Configuration.LazyLoadingEnabled = false;// turn-off loading on-demand
dbContext.Configuration.ProxyCreationEnabled = false;// turn-off dynamic proxy class generation
For the time being lets not include children in the select and only filter the parents. So the following EF query
var result = await _dbcontext.Parents
.Where(x => x.Children.All(y => y.Status == "success"))
.ToListAsync();
generates the following SQL
SELECT
[Extent1].[ParentId] AS [ParentId],
[Extent1].[DocumentName] AS [DocumentName],
[Extent1].[CreatedDateTime] AS [CreatedDateTime],
[Extent1].[ModifiedDateTime] AS [ModifiedDateTime],
FROM [dbo].[Parent] AS [Extent1]
WHERE NOT EXISTS (SELECT
1 AS [C1]
FROM [dbo].[Child] AS [Extent2]
WHERE ([Extent1].[ParentId] = [Extent2].[ParentId]) AND (('success' <> [Extent2].[Status]) OR (CASE WHEN ('success' = [Extent2].[Status]) THEN cast(1 as bit) WHEN ('success' <> [Extent2].[Status]) THEN cast(0 as bit) END IS NULL))
)
Your SQL statement is not correct, you will get all Parents that has at least one childres with the "success" status. But this is not the main point.
As you don't share your model this could it be an eager\lazy load problem. Looks like your EF expression is logically ok. So could you try in this way:
var result = await _dbcontext.Parents.Include(x => x.Children).
.Where(x => x.Children.All(y => y.Status == "success"))
.ToListAsync();
I think i found it. I also have to check if parent has any children. My code was returning parents that does not have any children
var result = await _dbcontext.Parents
.Include(x=>x.Children)
.Where(x => x.Children.Any() && x.Children.All(y => y.Status == "success"))
.ToListAsync();
Why not select all successful Children, and get their Distinct Parents:
var result = dbContext.Children // get all children
.Where(child => child.Status == "success") // keep only the successful ones
.Select(child => child.Parent); // select their parents
.Distinct(); // remove duplicate parents
For this you need to have setup a proper one-to-many relationship in entity framework.
class Parent
{
...
// a parent has many children:
public virtual ICollection<Child> Children {get; set;}
}
class Child
{
...
// A Child belongs to one parent via foreign key
public int ParentId {get; set;}
public virtual Parent Parent {get; set;}
}
Related
I updated my .net core 2.2 to 5
I have a error about ef that
System.InvalidOperationException: 'The LINQ expression 'x' could not
be translated. Either rewrite the query in a form that can be
translated, or switch to client evaluation explicitly by inserting a
call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or
'ToListAsync'. See https://go.microsoft.com/fwlink/?linkid=2101038 for
more information.'
public List<CustomerPageModel> GetCustomers(int AccountID)
{
return (from p in context.Customers
join f in context.Patients on p.ID equals f.CustomerID into ps
from t in ps.DefaultIfEmpty()
where p.AccountID == AccountID
select new CustomerPageModel
{
ID = p.ID,
Name = p.Name,
IsActive = p.IsActive,
TC = p.TC,
Surname = p.Surname,
Email = p.Email,
Address = p.Address,
Phone = p.Phone,
Note = p.Note,
AccountID = p.AccountID,
Pats = string.Join(",", ps.Select(x => x.Name)),
PatCount = ps.Count()
})
.GroupBy(p => p.ID)
.Select(g => g.First())
.ToList();
}
How can I convert that code?
Your problem line is:
Pats = string.Join(",", ps.Select(x => x.Name)),
Specifically, the string.Join method doesn't translate to SQL, so in previous versions of EF, it had to retrieve the data from the database then in-memory preform the string.Join function. Now EF explicitly tells you it can't run that on the database server - this is a breaking change but a design decision that tells you (the developer) that it may not have been running as efficiently as you thought it was...
To "fix" this, and based on your particular code example I'd recommend the following:
Add a pet names array property to your CustomerPageModel:
public string[] PetNames {get;set;}
And turn the Pets property into a readonly calculated string:
public string Pets { get => string.Join(",", PetNames); }
And change the problem line in your LINQ expression to:
PetNames = ps.Select(x => x.Name).ToArray()
I changed (lamb to subquery)
string.Join(",", ps.Select(x => x.Name))
to
string.Join(",", (from y in PatientList where y.CustomerID == p.ID select y.Name).ToArray()),
I made the group later (after tolist)
var test = mylist.GroupBy(p => p.ID)
.Select(g => g.First())
.ToList();
problem solved
I have an entity which contains several reference navigation properties under it.
The Repository implementation for the entity looks some thing like this:
return await _dbContext.MyEntity
.Include(s => s.Address) //Reference Navigation
.Include(s => s.BuildingDetails) //Reference Navigation
.ThenInclude(s => s.ChildOfBuildingDetails)
.Include(s => s.ContactPersons)
.Include(s => s.Technicians)
.Include(s => s.DeactivationDetails) //Reference Navigation
.FirstOrDefaultAsync(s => s.Id == id, cancellationToken);
When I check the actual DB queries being executed, all the queries contain the reference navigation properties included in them as joins to the parent entity.
SELECT [m92].[Id], .......
FROM [MyDB].[ContactPersons] AS [m92]
INNER JOIN (
SELECT DISTINCT [m93].[Id], [t76].[Id] AS [Id0]
FROM [MyDB].[MyEntity] AS [m93]
LEFT JOIN (
SELECT [m94].*
FROM [MyDB].[DeactivationDetails] AS [m94]
WHERE [m94].[Deleted] = 0
) AS [t75] ON [m93].[Id] = [t75].[MyEntityId]
LEFT JOIN (
SELECT [m95].*
FROM [MyDB].[BuildingDetails] AS [m95]
WHERE [m95].[Deleted] = 0
) AS [t76] ON [m93].[Id] = [t76].[MyEntityId]
LEFT JOIN (
SELECT [m96].*
FROM [MyDB].[Address] AS [m96]
WHERE [m96].[Deleted] = 0
) AS [t77] ON [m93].[Id] = [t77].[MyEntityId]
WHERE [m93].[Deleted] = 0
) AS [t78] ON [m92].[MyEntityId] = [t78].[Id]
WHERE [m92].[Deleted] = 0
ORDER BY [t78].[Id], [t78].[Id0]
Basically, the whole portion inside the INNER JOIN is present in all the queries that are being executed. Ideally we only need to join the child entities with parent entity in the queries.
1) Why does EF core translate to queries such that it includes the reference navigation property in all the split queries?
2) Is there a way to avoid this behavior, to be specific, replace the INNER JOIN block with just the parent entity
1) Why does EF core translate to queries such that it includes the reference navigation property in all the split queries?
It's an implementation defect/missing optimization.
2) Is there a way to avoid this behavior, to be specific, replace the INNER JOIN block with just the parent entity
The only way I found is to materialize the query with collection navigation property includes (which generate the additional queries) removed, then manually execute queries to load the related collections (requires tracking queries and relies on navigation property fix-up).
For instance (assuming navigation properties not marked as reference are collections):
// Query with filters only
var query = _dbContext.MyEntity
.Where(s => s.Id == id);
// Execute and materialize query with only filters and reference includes
var result = await query
.Include(s => s.Address) //Reference Navigation
.Include(s => s.BuildingDetails) //Reference Navigation
//.ThenInclude(s => s.ChildOfBuildingDetails)
//.Include(s => s.ContactPersons)
//.Include(s => s.Technicians)
.Include(s => s.DeactivationDetails) //Reference Navigation
.FirstOrDefaultAsync(cancellationToken);
// Load the related collections
await query.SelectMany(s => s.BuildingDetails.ChildOfBuildingDetails)
.LoadAsync(cancellationToken);
await query.SelectMany(s => s.ContactPersons)
.LoadAsync(cancellationToken);
await query.SelectMany(s => s.Technicians)
.LoadAsync(cancellationToken);
I have categories, and categories have a collection of products (Category.Products). I need to retrieve a category from the db by its id, but instead of including all its products, I want it to only include products with a given condition (example, order=0)
How can I do this with linq?
I tried with:
var e = db.Categories
.Include(a => a.products)
.Where(a => a.products.Any(r => r.order == 0))
.FirstOrDefault(p => p.id == id_category);
I don't think you can do that. In any case, the call to .Include() should be after any where clause, or it won't work.
In order to filter child collection you can try to select that to YouCustomModelor anonymous projection.
Note that it is not currently possible to filter which related entities are loaded. Include will always bring in all related entities Msdn reference.
var e = db.Categories
.Where(c => c.id == id_category)
.Select(p=> new
{
category = p,
products = p.Products.Where(k=>k.order==0)
}.FirstOrDefault();
var e = db.Categories.Where(a => a.order == 0);
This question already has answers here:
EF: Include with where clause [duplicate]
(5 answers)
Closed 1 year ago.
It seems that I can't get a parent and a subset of its children from the db.
For example...
db.Parents
.Include(p => p.Children)
.Where(p => p.Children.Any(c => c.Age >= 5))
This will return all Parents that have a child aged 5+, but if I iterate through the Parents.Children collection, all children will be present (not just those over 5 years old).
Now the query does make sense to me (I've asked to include children and I've got them!), but can imagine that I would like to have the where clause applied to the child collection in some scenarios.
How could I get an IEnumerable, in which each of the parents has a filtered collection of Children (Age>=5)?
The only way to get a collection of parents with a filtered children collection in a single database roundtrip is using a projection. It is not possible to use eager loading (Include) because it doesn't support filtering, Include always loads the whole collection. The explicite loading way shown by #Daz requires one roundtrip per parent entity.
Example:
var result = db.Parents
.Select(p => new
{
Parent = p,
Children = p.Children.Where(c => c.Age >= 5)
})
.ToList();
You can directly work with this collection of anonymous type objects. (You can also project into your own named type instead of an anonymous projection (but not into an entity like Parent).)
EF's context will also populate the Children collection of the Parent automatically if you don't disable change tracking (using AsNoTracking() for example). In this case you can then project the parent out of the anonymous result type (happens in memory, no DB query):
var parents = result.Select(a => a.Parent).ToList();
parents[i].Children will contain your filtered children for each Parent.
Edit to your last Edit in the question:
I am after a) A list of parents who have a child older than 5 (and
include only those children).
The code above would return all parents and include only the children with Age >= 5, so potentially also parents with an empty children collection if there are only children with Age < 5. You can filter these out using an additional Where clause for the parents to get only the parents which have at least one (Any) child with Age >= 5:
var result = db.Parents
.Where(p => p.Children.Any(c => c.Age >= 5))
.Select(p => new
{
Parent = p,
Children = p.Children.Where(c => c.Age >= 5)
})
.ToList();
In EF Core 5.0, the Include method now supports filtering of the entities included.
https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-5.0/whatsnew#filtered-include
var data = db.Parents
.Include(p => p.Children.Where(c => c.Age >= 5))
.ToList();
Taking your example the following should do what you need. Take a look here for more info.
db.Entry(Parents)
.Collection("Children")
.Query().Cast<Child>()
.Where(c => c.Age >= 5))
.Load();
I think parents and child are not really well suited as separate entities. A child can always also be a parent and usually a child has two parents (a father and a mother), so it's not the simplest context. But I assume you just have a simple 1:n relationship as in the following master-slave model that I used.
What you need to do is make a left outer join (that answer has led me on the right path). Such a join is a bit tricky to do, but here's the code
var query = from m in ctx.Masters
join s in ctx.Slaves
on m.MasterId equals s.MasterId into masterSlaves
from ms in masterSlaves.Where(x => x.Age > 5).DefaultIfEmpty()
select new {
Master = m,
Slave = ms
};
foreach (var item in query) {
if (item.Slave == null) Console.WriteLine("{0} owns nobody.", item.Master.Name);
else Console.WriteLine("{0} owns {1} at age {2}.", item.Master.Name, item.Slave.Name, item.Slave.Age);
}
This will translate to the following SQL statement with EF 4.1
SELECT
[Extent1].[MasterId] AS [MasterId],
[Extent1].[Name] AS [Name],
[Extent2].[SlaveId] AS [SlaveId],
[Extent2].[MasterId] AS [MasterId1],
[Extent2].[Name] AS [Name1],
[Extent2].[Age] AS [Age]
FROM [dbo].[Master] AS [Extent1]
LEFT OUTER JOIN [dbo].[Slave] AS [Extent2]
ON ([Extent1].[MasterId] = [Extent2].[MasterId]) AND ([Extent2].[Age] > 5)
Note that it is important to perform the additional where clause on the age on the joined collection and not between the from and the select.
EDIT:
IF you want a hierarchical result you can convert the flat list by performing a grouping:
var hierarchical = from line in query
group line by line.Master into grouped
select new { Master = grouped.Key, Slaves = grouped.Select(x => x.Slave).Where(x => x != null) };
foreach (var elem in hierarchical) {
Master master = elem.Master;
Console.WriteLine("{0}:", master.Name);
foreach (var s in elem.Slaves) // note that it says elem.Slaves not master.Slaves here!
Console.WriteLine("{0} at {1}", s.Name, s.Age);
}
Note that I used an anonymous type to store the hierarchical result. You can of course create also a specific type like this
class FilteredResult {
public Master Master { get; set; }
public IEnumerable<Slave> Slaves { get; set; }
}
and then project the group into instances of this class. That makes it easier if you need to pass these results to other methods.
I have an ASP.NET MVC application that displays data in a table format. I want to give my users the ability to search the table, so I take a text string and pass it into my service layer to construct a query using Linq to Entities.
I want to search a number of columns using the string. Some of the columns are integers (order ids), but the user doesn't care about integers and strings. They want to type '1200' and get any order with '1200' in the order number, or '1200' in the address.
The problem is that I can't find a way to construct a Linq-to-Entities query that results in SQL that looks like this:
select orderid, address from orders where orderid like '%1200%' or address like '%1200%'
Database context:
public DbSet<Person> Persons { get; set; }
public DbSet<Worker> Workers { get; set; }
public DbSet<WorkerSignin> WorkerSignins { get; set; }
The Persons and Workers tables are in a 1 to 0..1 relationship. If a worker record exists, a person record must also exist. They share the same ID. A worker record doesn't have to exist, however.
The Workers and WorkerSignins tables are related, but it's not enforced because of a client requirement. The Worker has an id-card with a barcode number on it (dwccardnum), but there may be discrepancies between cards issued and records in the DB, so I record all cards scanned in WorkerSignins, regardless of whether there is a matching record in the Workers table.
Here is the code I am working with:
allWSI = signinRepo.GetAllQ()
.Where(jj => jj.dateforsignin == date)
.Select(a => a);
if (!string.IsNullOrEmpty(search))
{
allWSI = allWSI
.Join(workerRepo.GetAllQ(), s => s.dwccardnum, w => w.dwccardnum, (s, w) => new { s, w })
.DefaultIfEmpty()
.Join(personRepo.GetAllQ(), oj => oj.w.ID, p => p.ID, (oj, p) => new { oj, p }).DefaultIfEmpty()
.DefaultIfEmpty()
.Where(jj => Convert.ToString(jj.oj.w.dwccardnum).Contains(search) ||
jj.p.firstname1.Contains(search) ||
jj.p.firstname2.Contains(search) ||
jj.p.lastname1.Contains(search) ||
jj.p.lastname2.Contains(search))
.Select(a => a.oj.s);
}
The GetAllQ() methods return an IQueryable() object.
The problem is on this line:
.Where(jj => Convert.ToString(jj.oj.w.dwccardnum).Contains(search) ||
I get this error:
LINQ to Entities does not recognize the method 'System.String ToString(Int32)' method, and this method cannot be translated into a store expression."
If I take out the convert, and try this:
.Where(jj => jj.oj.w.dwccardnum.Contains(search) ||
I get this error:
'int' does not contain a definition for 'Contains' and the best extension method overload 'System.Linq.ParallelEnumerable.Contains(System.Linq.ParallelQuery, TSource)' has some invalid arguments
So the question is...
How do I construct a Where clause to generate a like '%string%' and execute it against a integer column using Linq to Entities? (e.g. without using LINQ to SQL)
One option is to replace ...
jj => Convert.ToString(jj.oj.w.dwccardnum).Contains(search)
... by:
jj => SqlFunctions.StringConvert((decimal)jj.oj.w.dwccardnum).Contains(search)
SqlFunctions is a static class in namespace System.Data.Objects.SqlClient and I believe it only works with SQL Server. The weird cast to decimal is necessary because StringConvert doesn't have an overload for an int and without the cast the compiler complains that it cannot select the right overload unambiguously. (It has one for decimal? and one for double?.) But I just tested that the code above works indeed (with SQL Server and assuming dwccardnum is an int).
Try this
if (!string.IsNullOrEmpty(search))
{
int cardnum;
bool searchIsInt = int.TryParse(search, out cardnum);
allWSI = allWSI
.Join(workerRepo.GetAllQ(), s => s.dwccardnum, w => w.dwccardnum, (s, w) => new { s, w })
.DefaultIfEmpty()
.Join(personRepo.GetAllQ(), oj => oj.w.ID, p => p.ID, (oj, p) => new { oj, p }).DefaultIfEmpty()
.DefaultIfEmpty()
.Where(jj => (searchIsInt ? jj.oj.w.dwccardnum == cardnum : true) ||
jj.p.firstname1.Contains(search) ||
jj.p.firstname2.Contains(search) ||
jj.p.lastname1.Contains(search) ||
jj.p.lastname2.Contains(search))
.Select(a => a.oj.s);
}
Basically, you're first checking to see if the search is an int and then use it in your linq if it is.