Combine two queries in linq - sql

I have two linq queries as follows:
GroupNamesWithCorrespondingEffects
= new ObservableCollection<GroupNameWithCorrespondingEffect>(
from g in db.Groups
select new GroupNameWithCorrespondingEffect
{
GroupID = g.GroupID,
GroupName = g.GroupName,
CorrespondingEffect = g.Master_Effects.Effect
}
);
GroupNamesWithCorrespondingEffects
= new ObservableCollection<GroupNameWithCorrespondingEffect>
(GroupNamesWithCorrespondingEffects.
Where(u => !GetAllChildren(25).
Select(x => x.GroupID).
Contains(u.GroupID)).ToList());
Now how can I combine these two queries?

You can pass directly this to the constructor of the ObservableCollection:
from g in groups
let g = select new GroupNameWithCorrespondingEffect
{
GroupID = g.GroupID,
GroupName = g.GroupName,
CorrespondingEffect = g.Master_Effects.Effect
}
where !GetAllChildren(25)
.Select(x => x.GroupID)
.Contains(g.GroupID)
select g
I'm not sure if EF is able to compose the first and the second part (I can't remember from the top of my head if Contains is resolved in an IN clause, my EF is a bit rusty), but you were not doing that anyway, so the effect is the same as yours. If it is able to compose, then this way you are getting a more efficient execution.

If you don't mind mixing SQL-style and extension method syntax, you can do this:
GroupNamesWithCorrespondingEffects
= new ObservableCollection<GroupNameWithCorrespondingEffect>(
(from g in groups
select new GroupNameWithCorrespondingEffect
{ GroupID = g.GroupID,
GroupName = g.GroupName,
CorrespondingEffect = g.Master_Effects.Effect
})
.Where(u => !GetAllChildren(25)
.Select(x => x.GroupID)
.Contains(u.GroupID))
.ToList());

Related

LinqToSql OrderBy has no Effect

I am using a LinqToSql-DataSource for a GridView in this way:
wsv.wsv2DataContext db = new wsv.wsv2DataContext();
e.KeyExpression = "id";
e.QueryableSource = (from mitgliedschaft in db.mitgliedschaft
join person in db.person on mitgliedschaft.person_id equals person.id
join institution in db.institution on mitgliedschaft.verein_id equals institution.id
select new
{
vorname = person.vorname,
nachname = person.nachname,
nameVerein = institution.name,
vereinid = mitgliedschaft.verein_id,
id = mitgliedschaft.id,
verbandsMitgliedsNummer = person.verbandsMitgliedsNummer,
strasse = person.strasse,
plz = person.plz,
ort = person.ort,
geburtsdatum = person.geburtsdatum,
geschlechtid = person.geschlechtid,
statusid = mitgliedschaft.statusid,
bezirk_id = mitgliedschaft.bezirk_id,
kreis_id = mitgliedschaft.kreis_id,
person_id = mitgliedschaft.person_id.Value,
deletedFlag = mitgliedschaft.deletedFlag,
stammverein = mitgliedschaft.stammVerein,
eintrittsdatum = mitgliedschaft.eintritt
}).GroupBy(p => p.person_id).Select(p => p.First());
}
Now i want to order the Selection. At first the "stammVerein"-Column of Table "mitgliedschaft" descending AND the Column "eintritt" of Table "mitgliedschaft". I have tried several ways:
wsv.wsv2DataContext db = new wsv.wsv2DataContext();
e.KeyExpression = "id";
e.QueryableSource = (from mitgliedschaft in db.mitgliedschaft
join person in db.person on mitgliedschaft.person_id equals person.id
join institution in db.institution on mitgliedschaft.verein_id equals institution.id
orderby mitgliedschaft.stammVerein descending, mitgliedschaft.eintritt
select new
{
...
}).GroupBy(p => p.person_id).Select(p => p.First());
}
AND:
wsv.wsv2DataContext db = new wsv.wsv2DataContext();
e.KeyExpression = "id";
e.QueryableSource = (from mitgliedschaft in db.mitgliedschaft
join person in db.person on mitgliedschaft.person_id equals person.id
join institution in db.institution on mitgliedschaft.verein_id equals institution.id
select new
{
...
}).GroupBy(p => p.person_id).Select(p => p.First()).OrderByDescending(stamm => stamm.stammverein).ThenBy(eintritt => eintritt.eintrittsdatum);
}
AND:
wsv.wsv2DataContext db = new wsv.wsv2DataContext();
e.KeyExpression = "id";
e.QueryableSource = (from mitgliedschaft in db.mitgliedschaft
join person in db.person on mitgliedschaft.person_id equals person.id
join institution in db.institution on mitgliedschaft.verein_id equals institution.id
select new
{
....
}).OrderByDescending(stamm => stamm.stammverein).ThenBy(eintritt => eintritt.eintrittsdatum).GroupBy(p => p.person_id).Select(p => p.First());
But nothing of this has any Effects ! I am very new in this kind of DataSource and Linq.
Can anyone help me achieving this order ?
Items within a grouped result will not retain their order. Depending on how you want to factor in the ordering, you will need to do it after the group by, and before, and/or after your First...
To accomplish this, it will be easiest if you map the relationships in EF with navigation properties rather than substituting SQL with Linq QL (joins and such)
Using the following base query:
var query = db.mitgliedschaft
.GroupBy(m => m.Person); // Group by related entity, not ID
For instance, after the group by, you will have sets of records grouped by Person. If you want the first Person with an earliest related record:
var result = query.OrderByDescending(g => g.Key.mitgliedschafts.Max(stamm => stamm.stammverein)
.ThenBy(stamm => stamm.eintritt.eintrittsdatum)
.First();
This is taking a wild guess at your schema & entity relationships, but hopefully it will help you work out something that fits. I can only guess at what eintritt is and how it relates to your entity model.
The initial query takes just your base entities that you want to group, and groups them by the related entity. The result of that grouping will be a set of Grouped mitgliedschafts with a key being the Person. To Order those groups by the person with the most recent mitgliedschafts we use an orderby on the Key's associated mitgliedschafts using the Max value for the collection given a descending order request.
The First then gives us the first grouped collection of mitgliedschafts.
Then if you want to sort the resulting list of mitgliedschafts after getting the person with the most recent one:
var result = query.OrderByDescending(g => g.Key.mitgliedschafts.Max(stamm => stamm.stammverein)
.ThenBy(stamm => stamm.eintritt.eintrittsdatum)
.First().OrderByDescending(stamm => stamm.stammverein)
.ThenBy(stamm => stamm.eintritt.eintrittsdatum)
.ToList();
The 2nd set of OrderBy clauses apply to the selected group, or the mitgliedschafts.
To compose the desired view model, Insert a Select() to build the view model from the mitgliedschafts before the ToList().
With the navigation properties this can probably be done without resorting to a group by. On a hunch, something like this should return something similar:
var query = db.Person
.OrderByDescending(p => p.mitgliedschafts.Max(stamm => stamm.stammverien))
.ThenBy(stamm => stamm.eintritt.eintrittsdatum)
.SelectMany(p => p.mitgliedschafts)
.OrderByDescending(stamm => stamm.stammverien)
.ThenBy(stamm => stamm.eintritt.eintrittsdatum)
.Select(stamm => new { ... })
.ToList();
Anyhow, hopefully that gives you some ideas on things to try if you have the navigation properties mapped or can set those up.

Selecting an object from the GroupBy key

I'm accustomed to GroupBy() being more of an art than a science, but maybe someone can help me with a very specific problem:
Given the following code
var results = session.Query<MyClass>()
.GroupBy(c => c.OtherPersistentObject)
.Select(group => new
{
key = group.Key,
count = group.Count()
})
.ToList();
The generated query comes out like this:
/* [expression] */select
otherclass_.ID as col_0_0_,
cast(count(*) as INT) as col_1_0_,
otherclass_.ID as id1_1_,
otherclass_.START_DATE as start2_1_,
otherclass_.END_DATE as end3_1_,
otherclass_.Zone as zone9_1_
from
mytable mytable0_
left outer join
otherclass otherclass_
on mytable0_.otherID=otherclass_.ID
group by
mytable0_.otherID
which gives me the SQL error "Column 'otherclass .ID' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause"
Is there a way to get the Select to do what I want?
TIA
It's a known NHibernate issue NH-3027.
As a workaround you can use last approach described in this answer (rewrite GroupBy part as sub-query). So your query can be rewritten to something like:
var results = session.Query<MyClass>()
.Where(c => c == session.Query<MyClass>().First(cs => cs.OtherPersistentObject == c.OtherPersistentObject))
.Select(x => new
{
key = x.OtherPersistentObject,
count = session.Query<MyClass>().Count(cs => cs.OtherPersistentObject == x.OtherPersistentObject)
}).ToList();
Try this:
var results = session
.Query<MyClass>()
.GroupBy(c => c.OtherPersistentObject)
.Select(group => new
{
key = group.Key.Id,
count = group.Count()
})
.ToList();
Here you can find the reason for the error.

Multiple Subqueries With QueryOver

I need help converting this sql query into QueryOver Nhibernate criteria.
select distinct * from event e where e.name like '%req%'
or e.Id in (select r.eventId from requirement r where r.name like '%req%')
or e.Id in (select r.eventId from requirement r where r.id
in (select s.requirementId from solution s where s.name like '%sol%'))
var queryOver = session.QueryOver<Event>()
.Where(x => x.Name.IsInsensitiveLike("%"+searchTerms[1]+"%"))
.OrderBy(x => x.CreatedOn).Asc;
So far I have the main query but couldn't find enough reference material on how to add the subqueries. Haven't been successful using joinQueryOver.
Event has one-to-many rel with requirement and requirement has one-to-many rel with solution.
Requirement reqAlias = null;
Solution solAlias = null;
var subQuery = QueryOver.Of<Event>()
.JoinAlias(x => x.Requirements, () => reqAlias)
.Where(x => x.Name.IsInsensitiveLike(searchTerms[2]))
.JoinAlias(() => reqAlias.Solutions, () => solAlias)
.Where(x => x.Name.IsInsensitiveLike(searchTerms[3]))
.Select(Projections.Group<Event>(x => x.Id));
var events = session.QueryOver<Event>()
.Where(x => x.Name.IsInsensitiveLike(searchTerms[1]))
.WithSubquery.WhereProperty(x => x.Id).In(subQuery)
.List().ToList();
still not working.
When you use IsInsensitiveLike NHibernate appends the % after parsing, and uses lower to do a lower case comparison. In your code, you are appending the % yourself, which results in,
select distinct * from event e where e.name like %lower('%req%')%
which in turn, doesn't work.
Also, you have 3 subqueries, no a big one, so you need to restructure your code to account for that:
select r.eventId from requirement r where r.name like '%req%'
to
var firstQuery = QueryOver.Of<Requirement>()
.Where(r => r.Name.IsInsensitiveLike(searchTerms[2]))
.Select(r => r.EventId);
then,
select s.requirementId from solution s where s.name like '%sol%'
to
var solutionQuery = QueryOver.Of<Solution>()
.Where(s => s.Name.IsInsensitiveLike(searchTerms[3]));
then,
select r.eventId from requirement r where r.id
in (select s.requirementId from solution s where s.name like '%sol%')
to
var requirementQuery = QueryOver.Of<Requirement>()
.WithSubquery
.WhereProperty(r => r.Id).In(solutionQuery)
.Select(r => r.EventId);
Then you need to construct the main query using Restrictions.Or to include the 3 queries.

SQL to Entity Framework Count Group-By

I need to translate this SQL statement to a Linq-Entity query...
SELECT name, count(name) FROM people
GROUP by name
Query syntax
var query = from p in context.People
group p by p.name into g
select new
{
name = g.Key,
count = g.Count()
};
Method syntax
var query = context.People
.GroupBy(p => p.name)
.Select(g => new { name = g.Key, count = g.Count() });
Edit: EF Core 2.1 finally supports GroupBy
But always look out in the console / log for messages. If you see a notification that your query could not be converted to SQL and will be evaluated locally then you may need to rewrite it.
Entity Framework 7 (now renamed to Entity Framework Core 1.0 / 2.0) does not yet support GroupBy() for translation to GROUP BY in generated SQL (even in the final 1.0 release it won't). Any grouping logic will run on the client side, which could cause a lot of data to be loaded.
Eventually code written like this will automagically start using GROUP BY, but for now you need to be very cautious if loading your whole un-grouped dataset into memory will cause performance issues.
For scenarios where this is a deal-breaker you will have to write the SQL by hand and execute it through EF.
If in doubt fire up Sql Profiler and see what is generated - which you should probably be doing anyway.
https://blogs.msdn.microsoft.com/dotnet/2016/05/16/announcing-entity-framework-core-rc2
A useful extension is to collect the results in a Dictionary for fast lookup (e.g. in a loop):
var resultDict = _dbContext.Projects
.Where(p => p.Status == ProjectStatus.Active)
.GroupBy(f => f.Country)
.Select(g => new { country = g.Key, count = g.Count() })
.ToDictionary(k => k.country, i => i.count);
Originally found here:
http://www.snippetsource.net/Snippet/140/groupby-and-count-with-ef-in-c
Here are simple examples of group-by in .NET Core 2.1:
var query = this.DbContext.Notifications
.Where(n => n.Sent == false)
.GroupBy(n => new { n.AppUserId })
.Select(g => new { AppUserId = g.Key, Count = g.Count() });
var query2 = from n in this.DbContext.Notifications
where n.Sent == false
group n by n.AppUserId into g
select new { id = g.Key, Count = g.Count()};
Both of these translate to:
SELECT [n].[AppUserId], COUNT(*) AS [Count]
FROM [Notifications] AS [n]
WHERE [n].[Sent] = 0
GROUP BY [n].[AppUserId]
with EF 6.2 it worked for me
var query = context.People
.GroupBy(p => new {p.name})
.Select(g => new { name = g.Key.name, count = g.Count() });

Duplicated and unnecessary joins when using Linq in NHibernate

Basically I crossed the same problem of Linq provider in this linq-to-nhibernate-produces-unnecessary-joins
List<Competitions> dtoCompetitions;
dtoCompetitions = (from compset in session.Query<FWBCompetitionSet>()
where compset.HeadLine == true
&& compset.A.B.CurrentSeason == true
select (new Competitions
{
CompetitionSetID = compset.CompetitionSetID,
Name = compset.Name,
Description = compset.Description,
Area = compset.Area,
Type = compset.Type,
CurrentSeason = compset.A.B.CurrentSeason,
StartDate = compset.StartDate
}
)).ToList();
Which leads to duplicated join in its generated SQL
SELECT fwbcompeti0_.competitionsetid AS col_0_0_,
fwbcompeti0_.name AS col_1_0_,
fwbcompeti0_.DESCRIPTION AS col_2_0_,
fwbcompeti0_.area AS col_3_0_,
fwbcompeti0_.TYPE AS col_4_0_,
fwbseason3_.currentseason AS col_5_0_,
fwbcompeti0_.startdate AS col_6_0_
FROM fwbcompetitionset fwbcompeti0_
INNER JOIN A fwbcompeti1_
ON fwbcompeti0_.competitionseasonid = fwbcompeti1_.competitionseasonid
INNER JOIN A fwbcompeti2_
ON fwbcompeti0_.competitionseasonid = fwbcompeti2_.competitionseasonid
INNER JOIN B fwbseason3_
ON fwbcompeti2_.seasonid = fwbseason3_.seasonid
WHERE fwbcompeti0_.headline = #p0
AND fwbseason3_.currentseason = #p1
Notice these joins, which are totally duplicated and also affect my SQL Server's performence.
INNER JOIN A fwbcompeti1_
ON fwbcompeti0_.competitionseasonid = fwbcompeti1_.competitionseasonid
INNER JOIN A fwbcompeti2_
ON fwbcompeti0_.competitionseasonid = fwbcompeti2_.competitionseasonid
Update1
In the NHibernate 3.2, this LiNQ bug is still valid, and I could not find a simple and reasonable Linq solution.
So I used QueryOver + JoinAlias + TransformUsing finishing the job, workds perfect to me.
FWBCompetitionSet compset = null;
FWBCompetitionSeason compseason = null;
FWBSeason season = null;
IList<Competitions> dtoCompetitions;
dtoCompetitions = session.QueryOver<FWBCompetitionSet>(() => compset)
.JoinAlias(() => compset.FWBCompetitionSeason, () => compseason)
.JoinAlias(() => compseason.FWBSeason, () => season)
.Where(() => compset.HeadLine == true)
.And(() => season.CurrentSeason == true)
.SelectList(
list => list
.Select(c => c.CompetitionSetID).WithAlias(() => compset.CompetitionSetID)
.Select(c => c.Name).WithAlias(() => compset.Name)
.Select(c => c.Description).WithAlias(() => compset.Description)
.Select(c => c.Area).WithAlias(() => compset.Area)
.Select(c => c.Type).WithAlias(() => compset.Type)
.Select(c => season.CurrentSeason).WithAlias(() => season.CurrentSeason)
.Select(c => c.StartDate).WithAlias(() => compset.StartDate)
)
.TransformUsing(Transformers.AliasToBean<Competitions>())
.List<Competitions>();
Yet Another Edit:
I think I finally found out what's going on. It seems that the LINQ to NHibernate provider has trouble navigating associations from the target to the source table and generates a separate join each time it encounters such an association.
Since you don't provide your mapping, I used the mapping from linq-to-nhibernate-produces-unnecessary-joins. This model has a Document with one Job and many TranslationUnits. Each TranslationUnit has many Translation entities.
When you try to find a Translation based on a Job, you are traversing the associations in the reverse order and the LINQ provider generates multiple joins: one for Translation -> TranslationUnit and one for TranslationUnit to Document.
This query will generate redundant joins:
session.Query<TmTranslation>()
.Where(x => x.TranslationUnit.Document.Job == job)
.OrderBy(x => x.Id)
.ToList();
If you reverse the navigation order to Document -> TranslationUnit -> Translation, you get a query that doesn't produce any redundant joins:
var items=(from doc in session.Query<Document>()
from tu in doc.TranslationUnits
from translation in tu.Translations
where doc.Job ==job
orderby translation.Id
select translation).ToList();
Given this quirkiness, QueryOver seems like a better option.
Previous Edit:
I suspect the culprit is compset.A.B.CurrentSeason. The first joined table (fwbcompeti1_) returns A.B while the next two (fwbcompeti2_ and fwbseason3_) are used to return A.B. The LINQ to NHibernate provider doesn't seem to guess that A is not used anywhere else and fails to remove it from the generated statement.
Try to help the optimizer a little by replacing CurrentSeason = compset.A.B.CurrentSeason with CurrentSeason = true from the select, since your where statement returns only items with CurrentSeason == true.
EDIT: What I mean is to change the query like this:
List<Competitions> dtoCompetitions;
dtoCompetitions = (from compset in session.Query<FWBCompetitionSet>()
where compset.HeadLine == true
&& compset.A.B.CurrentSeason == true
select (new Competitions
{
CompetitionSetID = compset.CompetitionSetID,
Name = compset.Name,
Description = compset.Description,
Area = compset.Area,
Type = compset.Type,
CurrentSeason = true,
StartDate = compset.StartDate
}
)).ToList();
I simply replace the value compset.A.B.CurrentSeason with true