Okay I'm new to EF and I'm having issues grasping on filtering results...
I'd like to emulate the ef code to do something like:
select *
from order o
inner join orderdetail d on (o.orderid = d.orderid)
where d.amount > 20.00
just not sure how this would be done in EF (linq to entities syntax)
Your SQL gives multiple results per order if there's multiple details > 20.00. That seems wrong to me. I think you want:
var q = from o in Context.Orders
where o.OrderDetails.Any(d => d.Amount > 20.00)
select o;
I would do it like that:
context.OrderDetails.Where(od => od.Amount > 20).Include("Order").ToList().Select(od => od.Order).Distinct();
We are taking details first, include orders, and take distinct orders.
Related
This worked in EF 6.4:
from a in Addresses
group a by new {a.StreetName, a.StreetNumber} into agrp
where agrp.Count() > 3
from aitem in agrp
select aitem
If EF Core 5 I get:
InvalidOperationException: The LINQ expression 'agrp => agrp' 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.
Why? Is there a different way to write this?
If you're OK with the data being loaded into memory, a simple solution could be to add .ToList() or .AsEnumerable() after Addresses:
from a in Addresses.ToList() // or .AsEnumerable()
group a by new {a.StreetName, a.StreetNumber} into agrp
where agrp.Count() > 3
from aitem in agrp
select aitem
Note that this (in SqlServer) translates into:
SELECT [a].[Id], [a].[StreetName], [a].[StreetNumber]
FROM [Addresses] AS [a]
In EF Core, GroupBy is (in many cases) not translated to SQL, but is run in memory.
(To avoid accidentally loading a lot of data into memory, EF will throw an exception unless .ToList() or .AsEnumerable() is called to indicate that this is intentional.)
(...) Since no database structure can represent an IGrouping, GroupBy operators have no translation in most cases. When an aggregate operator is applied to each group, which returns a scalar, it can be translated to SQL GROUP BY in relational databases. (...)
- Complex query operators, GroupBy
The article also has an example of a query which translates into group by with a filter on Count (included below).
The example doesn't fully cover the example in the question, unfortunately. It would not return the relevant Address-objects, only the group-by Key and Count.
var query = from p in context.Set<Post>()
group p by p.AuthorId into g
where g.Count() > 0
orderby g.Key
select new
{
g.Key,
Count = g.Count()
};
SELECT [p].[AuthorId] AS [Key], COUNT(*) AS [Count]
FROM [Posts] AS [p]
GROUP BY [p].[AuthorId]
HAVING COUNT(*) > 0
ORDER BY [p].[AuthorId]
This seems to work:
var addressGroupQuery = from a in Addresses
group a by new {a.StreetName, a.StreetType} into agrp
where agrp.Count() > 3
select agrp.Max(a => a.AddressID);
var addresses = from a in Addresses
where addressGroupQuery.Contains(a.AddressID)
select a;
Note the addressGroupQuery remains a deferred query (no ToList). Generates some clean looking SQL too:
SELECT [a].[AddressID], [a].[City], [a].[Country], [a].[StreetName], [a].[StreetNumber], [a].[StreetType], [a].[UnitNumber]
FROM [Address] AS [a]
WHERE EXISTS (
SELECT 1
FROM [Address] AS [a0]
GROUP BY [a0].[StreetName], [a0].[StreetType]
HAVING (COUNT(*) > 3) AND (MAX([a0].[AddressID]) = [a].[AddressID]))
Basically i want this as query:
SELECT DISTINCT c.description FROM students s
join courses c on s.courseId = c.Id
WHERE c.Id = 100
How do i do this in EF Core?
When i do :
db.Students
.Include(s => s.courseId)
.Select( -- how can i select for course description? --)
.Distinct()
Bear with me. I am new to Entity Framework.
First of all, your Include expression handles the joining for you, so you don't have to specify how your going to join the table. So your include will look like this:
_db.Students.Include(s => s.Course)
Then you'd accomplish your select through the use of a lamba expression like this:
_db.Students .Include(s => s.Course).Select(s => s.Course.Description).Distinct()
I have this table structure:
Classic many to many relationship. I want to get all the orders for products belonging to the category for a small number of products I provide. It may be easier to show the SQL that does exactly what I want:
select o.*
from [Order] o join Product p2 on o.FKCatalogNumber=p2.CatalogNumber
where p2.FKCategoryId IN
(select c.Id
from Category c join Product p1 on p1.FKCategoryId=c.Id
where p1.CatalogNumber in ('0001', '0002')
This example gives me all the orders belonging to the categories that catalog #'s 0001 and 0002 are in.
But I am unable to wrap my head around the equivalent EF syntax for this query. I'm embarrassed to say I spent half the day on this. I bet it's easy for someone out there.
I came up with this but it's not working (and probably not even close):
string[] catNumbers = {"0001", "0002"};
var orders = ctx.Categories
.SelectMany(c => c.Products, (c, p) => new {c, p})
.Where(#t => catNumbers.Contains(#t.p.CatalogNumber))
.Select(#t => #t.p.Orders)
.ToList();
You can use query syntax (which looks very similar to SQL) in LINQ, so if you're more comfortable with SQL then you may prefer to write your query like this:
string[] catNumbers = {"0001", "0002"};
var orders = from o in ctx.Orders
join p2 in ctx.Products on o.FKCatalogNumber equals p2.CatalogNumber
where
(
from c in ctx.Categories
join p1 in ctx.Products on c.ID equals p1.FKCategoryId
where catNumbers.Contains(p1.CatalogNumber)
select c.ID
).Contains(p2.FKCategoryId)
select o;
As you can see, it's actually just your SQL query rearranged slightly, but it compiles as C#.
Note that:
the [Order] o syntax for referencing tables is replaced by o in ctx.Orders
LINQ enforces which way round you do the join condition so I had to flip your on o.FKCatalogNumber=p2.CatalogNumber to be on o.FKCatalogNumber equals p2.CatalogNumber
instead of your where p2.FKCategoryId IN (...), the equivalent c# is (...).Contains(p2.FKCategoryId)
the select comes last, not first
but those are the only major changes. Otherwise, it's written just like SQL.
I'd also draw your attention to a distinction regarding this comment:
the equivalent EF syntax for this query
The syntax here isn't specific to EF, but is just LINQ - Language Integrated Querying. It has two flavours: query syntax (sometimes called declarative) and method syntax (sometimes called fluent). LINQ works on just about any collection that implements IEnumerable or IQueryable, including EF's DbSet.
For more info on the different ways of querying, this MSDN page is a decent place to start. There's also this handy reference table showing the equivalent query syntax for each method-syntax operator, where applicable.
You can still nest queries in EF. The following looks like it works for me:
string[] catNumbers = {"0001", "0002"};
var orders = ctx.Orders
.Where(o => ctx.Products
.Where(p => catNumbers.Contains(p.CatalogNumber))
.Select(p => p.CategoryId)
.Contains(o.Product.CategoryId)
);
This produces the following SQL:
SELECT
[Extent1].[Id] AS [Id],
[Extent1].[CatalogNumber] AS [CatalogNumber]
FROM [dbo].[Orders] AS [Extent1]
WHERE EXISTS (SELECT
1 AS [C1]
FROM [dbo].[Products] AS [Extent2]
INNER JOIN [dbo].[Products] AS [Extent3] ON [Extent2].[CategoryId] = [Extent3].[CategoryId]
WHERE ([Extent1].[CatalogNumber] = [Extent3].[CatalogNumber]) AND ([Extent2].[CatalogNumber] IN (N'0001', N'0002'))
)
Given the following LINQ to Entities (EF4) query...
var documents =
from doc in context.Documents
.Include(d => d.Batch.FinancialPeriod)
.Include(d => d.Batch.Contractor)
.Include(d => d.GroupTypeHistory.Select(gth => gth.GroupType))
.Include(d => d.Items.Select(i => i.Versions))
.Include(d => d.Items.Select(i => i.Versions.Select(v => v.ProductPackPeriodic.ProductPack.Product.HomeDelivery)))
.Include(d => d.Items.Select(i => i.Versions.Select(v => v.ProductPackPeriodic.ProductPack.Product.Manufacturer)))
.Include(d => d.Items.Select(i => i.Versions.Select(v => v.ProductPackPeriodic.ProductPeriodic)))
.Include(d => d.Items.Select(i => i.Versions.Select(v => v.ProductPackPeriodic.SpecialContainerIndicator)))
.Include(d => d.Items.Select(i => i.Versions.Select(v => v.Endorsements.Select(e => e.Periodic))))
where doc.ID == this.documentID
select doc;
Document document = documents.FirstOrDefault();
Where ...
a Document will always have a Batch which in turn will always have a
financial period and a contractor
a Document will always have one or
more GroupTypeHistory, each of which will always have a GroupType
a Document will have zero or more Item's, which in turn will have one
or more Version's
a Version will have zero or one ProductPackPeriodic
a ProductPackPeriodic will always have one ProductPack and one
ProductPeriodic, along with zero or one SpecialContainerIndicator
a ProductPack will always have one Product
a Product will have zero or one Manufacturer, and zero or one HomeDelivery
a Version will have zero or more Endorsements, each of which will have one Periodic
The above LINQ query generates some of the worst TSQL that I have ever seen, with some of the related tables included multiple times (probably because they are referenced within the query multiple times) and takes significantly longer than I would like to run (the tables concerned can contain millions of rows, but this is not the cause).
I know that there has to be a better way to write it (taking into account all of the different reference types that I describe above) which will result in better TSQL, but every version that I try fails to return the data correctly.
Can anybody assist in pointing me to a better solution?
If it makes it any easier to understand, were I writing TSQL directly I would be looking at something like the following...
select *
from Document d
inner join Batch b
inner join FinancialPeriod fp on b.FinancialPeriodID = fp.FinancialPeriodID
inner join Contractor c on b.ContractorID = c.ContractorID
on d.BatchID = b.BatchID
inner join DocumentGroupType dgt on d.DocumentID = dgt.DocumentID
left join Item i
left join ItemVersion iv
left join ProductPackPeriodic ppp
inner join ProductPack pack
inner join Product p
left join Manufacturer m on p.ManufacturerID = m.ManufacturerID
left join HomeDeliveryProduct hdp on p.ProductID = hdp.ProductID
on pack.ProductID = p.ProductID
on ppp.ProductPackID = pack.ProductPackID
inner join ProductPeriodic pp on ppp.ProductPeriodicID = pp.ProductPeriodicID
left join SpecialContainerIndicator sci on ppp.SpecialContainerIndicatorCode = sci.SpecialContainerIndicatorCode
on iv.ProductPackPeriodicID = ppp.ProductPackPeriodicID
left join ItemVersionEndorsement ive
inner join EndorsementPeriodic ep on ive.EndorsementPeriodicID = ep.EndorsementPeriodicID
on iv.ItemVersionID = ive.ItemVersionID
on i.ItemID = iv.ItemID
on d.DocumentID = i.DocumentID
where d.DocumentID = 33 -- example value
This may also make the relationship requirements clearer.
Thanks in advance.
For scenarios like this i write specific stored procedures and then use EFExtensions with a custom materializer to not only get excellent performance but also correctly materialized entities.
I don't have any good answer for EF, but it might suit you to use a micro ORM for certain complex queries. Micro ORMs are essentially low-level wrappers over SQL, that allow you to get strongly typed objects, along with other convenience features. You can take a look at Dapper, for example, which is used by this very site for some of the bottleneck queries. It should perform very close to native SQL performance.
Consider two tables Bill and Product with a many to many relationship. How do you get all the bills for a particular product using Entity Sql?
Something like this
SELECT B FROM [Container].Products as P
OUTER APPLY P.Bills AS B
WHERE P.ProductID == 1
will produce a row for each Bill
Another option is something like this:
SELECT P, (SELECT B FROM P.Bills)
FROM [Container].Products AS P
WHERE P.ProductID == 1
Which will produce a row for each matching Product (in this case just one)
and the second column in the row will include a nested result set containing the bills for that product.
Hope this helps
Alex
You need to use some linq like this;
...
using (YourEntities ye = new YourEntities())
{
Product myProduct = ye.Product.First(p => p.ProductId = idParameter);
var bills = myProduct.Bill.Load();
}
...
This assumes that you have used the entitiy framework to build a model for you data.
The bills variable will hold a collection of Bill objects that are related to your product object.
Hope it helps.