Convert this SQL to lambda for EF 4 Code first - entity-framework

I have this Sql statement
SELECT * FROM Game
INNER JOIN Series ON Series.Id = Game.SeriesId
INNER JOIN SeriesTeams ON SeriesTeams.SeriesId = Series.Id
INNER JOIN Team ON Team.Id = SeriesTeams.TeamId
INNER JOIN TeamPlayers ON TeamPlayers.TeamId = Team.Id
INNER JOIN Player ON Player.Id = TeamPlayers.PlayerId
WHERE AND Game.StartTime >= GETDATE()
AND Player.Id = 1
That I want to be converted into a lambda expression.
This is how it works.
A game can only be joined to 1 series, but a serie can of course have many games. A serie can have many teams and a team can join many series.
A player can play in many teams and a team has many players.
SeriesTeams and TeamPlayers are only the many-to-many tables created by EF to hold the references between series/teams and Teams/Players
Thanks in advance...
Edit: I use the EF 4 CTP5 and would like to have the answer as lambda functions, or in linq if that is easier...

Ok, first of all, if you want to make sure that everything is eager-loaded when you run your query, you should add an explicit Include:
context.
Games.
Include(g => g.Series.Teams.Select(t => t.Players)).
Where(g =>
g.StartTime >= DateTime.Now &&
g.Series.Teams.Any(t => t.Players.Any(p => p.Id == 1))).
ToList();
However, as I mentioned in my comment, this won't produce the same results as your SQL query, since you don't filter out the players from the child collection.
EF 4.1 has some nifty Applying filters when explicitly loading related entities features, but I couldn't get it to work for sub-sub-collections, so I think the closest you can get to your original query would be by projecting the results onto an anonymous object (or you can create a class for that if you need to pass this object around later on):
var query = context.
Games.
Where(g =>
g.StartTime >= DateTime.Now &&
g.Series.Teams.Any(t => t.Players.Any(p => p.Id == 1))).
Select(g => new
{
Game = g,
Players = g.
Series.
Teams.
SelectMany(t => t.
Players.
Where(p => p.Id == user.Id))
});
Then you can enumerate and inspect the results:
var gamesAndPlayersList = query.ToList();

I did found the solution.
IList<Domain.Model.Games> commingGames = this.Games
.Where(a => a.StartTime >= DateTime.Now && a.Series.Teams.Any(t => t.Players.Any(p => p.Id == user.Id))).ToList();
If somebody has a better solution then I am all ears..

Related

How to get count from rows in Linq faster

I have a linq to get all row count group by Id.
Because of my table is very big it needs very long in Linq (because I have to get the data completly since EF Core 3.0).
var reportCountData = this.context.ReportData.AsEnumerable().GroupBy(x => x.ReportId).ToDictionary(x => x.Key, x => x.Count());
How to make this faster (e.g. without getting all data first)?
The following should be translated:
var reportCountData = from p in this.context.ReportData
group p by p.ReportId into g
select new
{
g.Key,
Count = g.Count()
};
https://learn.microsoft.com/de-de/ef/core/querying/complex-query-operators#groupby

.Net MVC Linq query

In my program i have a database with a table containing persons.
Every person has a collection of clothes, which has a collection of fabrics. Say i want to return the number of persons who has clothes that contain cotton.
I only want to count them once even if the person has more than one clothes that contain cotton.
I tried the following and several other solutions but it didn't quite work out for me:
if ((from p in context.Persons
from c in p.Clothes
from f in c.Fabrics
select f.Name == "Cotton").Count();
{
var count = database.People
.Where(p => p.Clothes.Any(c => c.Fabrics.Any(f => f.Name == "Cotton")))
.Count();
Select all people where any of the clothes' fabrics are Cotton.

How do you join two many to many tables in Entity Framework?

I have a few Tables I want to join together:
Users
UserRoles
WorkflowRoles
Role
Workflow
The equivalent sql I want to generate is something like
select * from Users u
inner join UserRoles ur on u.UserId = ur.UserId
inner join WorkflowRoles wr on wr.RoleId = ur.RoleId
inner join Workflow w on wr.WorkflowId = w.Id
where u.Id = x
I want to get all the workflows a user is part of based on their roles in one query. I've found that you can get the results like this:
user.Roles.SelectMany(r => r.Workflows)
but this generates a query for each role which is obviously less than ideal.
Is there a proper way to do this without having to resort to hacks like generating a view or writing straight sql?
You could try the following two queries:
This one is better readable, I think:
var workflows = context.Users
.Where(u => u.UserId == givenUserId)
.SelectMany(u => u.Roles.SelectMany(r => r.Workflows))
.Distinct()
.ToList();
(Distinct because a user could have two roles and these roles may contain the same workflow. Without Distinct duplicate workflows would be returned.)
But this one performs better, I believe:
var workflows = context.WorkFlows
.Where(w => w.Roles.Any(r => r.Users.Any(u => u.UserId == givenUserId)))
.ToList();
So it turns out the order which you select makes the difference:
user.Select(x => x.Roles.SelectMany(y => y.Workflows)).FirstOrDefault()
Have not had a chance to test this, but it should work:
Users.Include(user => user.UserRoles).Include(user => user.UserRole.WorkflowRoles.Workflow)
If the above is not correct then is it possible that you post your class structure?

LINQ to Entity Framework - multiple include paths result in lousy TSQL

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.

Dynamic Query with Entity Framework 4

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.