I have a set of related tables that I am trying to return a result set for and I just can't get the syntax quite right to get the results I want.
I am trying to return a list of countries (United States being the only one expected right now) with appropriate states. The "appropriate" requirement is that I only want to return the states under the countries that are represented by one of our clients....ie....if we have 3 clients, 2 in Texas and 1 in OK, I need the query to return "United States (with only Texas and OK...not the other 48 states where we don't have clients).
I can get the query to return only the United States, but it returns ALL states, not just the ones I am after. This is an example of the query that I "want" to run....NOTE: FirstAdminDivision table = states table.
select * from Country c
inner join FirstAdminDivision f on f.CountryId = c.CountryId
where f.FirstAdminDivisionId IN
(
select f2.FirstAdminDivisionId from Company C
inner join [Address] a on a.AddressId = c.AddressId
inner join City cty on cty.CityId = a.CityId
inner join FirstAdminDivision f2 on f2.FirstAdminDivisionId = cty.FirstAdminDivisionId
)
This is the code I currently have (which is as close as I have been able to get) that returns the US only with all states. The "ids" list contains only Texas and OK like I would expect, so I think they issue lies in the where in the main select.
IQueryable<int> innerQ = base.Context.Set<FirstAdminDivision>().Where(x => x.Cities.Any(y => y.Addresses.Any(z => z.Companies.Any()))).Select(x => x.FirstAdminDivisionId);
List<int> ids = innerQ.ToList();
IQueryable<ICountryModel> q2 = base.Context.Set<Country>()
.Include(x => x.FirstAdminDivisions)
.Where(x => x.FirstAdminDivisions.Where(y => innerQ.Contains(y.FirstAdminDivisionId)).Any())
.Select(x => new CountryModel
{
Abbreviation = x.Abbreviation,
CountryId = x.CountryId,
Name = x.Name,
UrlDisplay = x.UrlDisplay,
FirstAdminDivisions = x.FirstAdminDivisions.Select(y => new FirstAdminDivisionModel
{
Abbreviation = y.Abbreviation,
Name = y.Name,
UrlDisplay = y.UrlDisplay
}).ToList()
});
Any help pointing out what I am missing/doing wrong would be greatly appreciated.
Well basically i would use your first query as the base query instead of all the countries and instead of returning a list of ints i would want it to return a list of FirstAdminDivision objects.
So in this case you would have two objects in that list OK and Texas. And in this case you also should have the country available since you say that FirstAdminDivision has the country as a property
Then from that list i would include country object so you can group those two state objects by country. And from that build your model using the key, country, and then the list of states.
Something like this:
IQueryable<ICountryModel> countriesWithStates = base.Context.Set<FirstAdminDivision>()
.Where(x => x.Cities.Any(y => y.Addresses.Any(z => z.Companies.Any())))
.Include(x => x.Country)
.GroupBy(x => x.Country, y=>y, (countryKey, states) => new { Country = countryKey, States = states.ToList() })
.Select(x => new CountryModel
{
Abbreviation = x.Country.Abbreviation,
CountryId = x.Country.CountryId,
Name = x.Country.Name,
UrlDisplay = x.Country.UrlDisplay,
FirstAdminDivisions = x.States.Select(y => new FirstAdminDivisionModel
{
Abbreviation = y.Abbreviation,
Name = y.Name,
UrlDisplay = y.UrlDisplay
}).ToList()
});
Related
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.
Is it possible in Linq-to-NHibernate to do a Join with a SelectMany within the same query?
It might look weird, but the idea would be to generate the following query:
select * from State
join (
select CityId, StateId
from State
inner join City on State.StateId=City.StateId
) as City on City.StateId = State.StateId
The reason is that we are using a repository pattern and I do not want to use 2 different repository to do my join.
In code, I have tried the following block, but I get a not supported exception
var states = Session.Query<State>();
var query = states.Join(states.SelectMany(x => x.Cities), state => state.StateId,
city => city.State.StateId, (state, city) => new {state, city});
var result = query.ToArray(); // <- Throws a not supported exception
I have tried the same thing with two repositories and it works:
var states = Session.Query<State>();
var cities = Session.Query<City>();
var query = states.Join(cities , state => state.StateId,
city => city.State.StateId, (state, city) => new {state, city});
var result = query.ToArray(); // <- This works perfectly fine
Thanks in advance!
Ah! I found out that I wasn't thinking the right way. I can do the equivalent by starting with a SelectMany, then do a select of both.
This is supported and will do the same thing as a join but even more optimized:
var states = Session.Query<State>();
var query = states.SelectMany(x => x.Cities).Select(city => new {city.State, city});
var result = query.ToArray(); // It works!
And with that, I don't even need to get a repo of City.
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
I have an odd relationship.
3 classes: Manager, Group, Vehicle
Group has a many to many to both Manager and Vehicle, but neither Manager nor Vehicle know anything about Group (only a one way mapping).
I have a ManagerID. I want to get a distinct list items of type T that has a VehicleID that is in a group that has a manager with the specified ID.
var vehicles = Session.QueryOver<Group>(() => group)
.Right.JoinQueryOver<Manager>(x => x.Managers)
.Where(x => x.Id == managerID)
.Select(Projections.Distinct(Projections.Property<VehicleGroup>(g => g.Vehicles)))
.List<Vehicle>()
;
Now what?
Ok, some further information:
"Manager" is not mapped to groups.
"Vehicle" is not mapped to groups.
Group has many Managers
Group has many Vehicles
A Manager can be referenced by multiple groups.
A Vehicle can be managed by multiple groups.
I have a Manager ID. I want to get a list of distinct Vehicle from the groups that have that manager.
============================================
Ok. More:
SQL I wish to emulate:
select * from Summary
where [vehicleID] in
(
select [vehicleID] from [Managers]
inner join [Manager_Groups] on [Managers].[managerID] = [Manager_Groups].[managerID]
inner join [Groups] on [Manager_Groups].[groupID] = [Groups].[groupID]
inner join [Groups_Object] on [Groups].[groupID] = [Groups_Object].[groupID]
inner join [Vehicle] on [Groups_Object].[ID] = [Vehicle].[vehicleId]
where [Managers].[ManagerId] = 34 and [Groups].[type] = 1
)
There are 2 types of groups. Drivers (type = 0) and vehicles (type = 1)
So far, I have:
var sq = QueryOver.Of<Manager>(() => manager)
.Where(mf => mf.Id == managerId)
.Fetch(mf => mf.ManagedVehicleGroups).Eager
.TransformUsing(Transformers.DistinctRootEntity)
.JoinQueryOver<VehicleGroup>(mf => mf.ManagedVehicleGroups)
.SelectList(list => list.Select(mf => mf.ManagedVehicleGroups))
;
var vp = Session.QueryOver<VehiclePerformanceDay>(() => item)
.WithSubquery.WhereExists(sq)
.Take(10)
.List();
And this generates:
SELECT
TOP (10) this_. ~~~lots of fields removed~~~
FROM
dbo.Summary this_
WHERE
exists (
SELECT
this_0_.ManagerId as y0_
FROM
dbo.Managers this_0_
inner join
dbo.Manager_Groups managedveh3_
on this_0_.ManagerId=managedveh3_.managerID
inner join
dbo.Groups vehiclegro1_
on managedveh3_.groupID=vehiclegro1_.groupId
WHERE
this_0_.ManagerId = 34
);
So, I am getting closer.
I think what you are after is somthing like the following.
var list = session.QueryOver<Group>()
.Where(x => x.Manager.Id == managerID)
.Select(group=> group.Vehicle)
.TransformUsing(Transformers.DistinctRootEntity)
.List<Vehicle>();
Ok - got a solution, mostly. Minor inconvenience that I will ask another question on:
var sq = QueryOver.Of<VehicleGroup>(() => vehicleGroup)
.JoinQueryOver<Manager>(vg => vg.Managers)
.Where(man => man.Id == managerId)
.JoinQueryOver<VehicleBase>(() => vehicleGroup.Vehicles)
.Where(v => v.Id == item.VehicleId)
.Select(vg => vg.Id)
;
var vp = Session.QueryOver<Summary>(() => item)
.WithSubquery.WhereExists(sq)
.Take(10)
.List();
So, this works. However, I had to add the map to Summary.VehicleId which is not what I wanted to do, but it'll do for now.
BACKGROUND:
Given 3 tables
results contains 2 columns vId and pId
vTable contains 2 columns vId and data
pTable contains 2 columns pId and data
I want to accomplish this sort of SQL query using QueryOver
SELECT v.data, p.data
from results r
inner join vTable v on r.vId = v.vId
inner join pTable p on r.pId = p.pId
I've tried the following:
var res = GetResults(some parameters)
.Select(x => x.vId
.Select(x => x.pID);
var dataset = session.QueryOver<vTable>()
.WithSubquery.WhereProperty(v => v.vId).In(res)
.Select(v => v.vId)
.Select(v => v.data)
which works just fine to get data from vTable
however, when I add the 2nd table
var dataset = session.QueryOver<vTable>()
.WithSubquery.WhereProperty(v => v.vId).In(res)
.JoinQueryOver<pTable>(p => p.pId)
.WithSubquery.WhereProperty(p => p.pId).In(res)
.Select(v => v.vId)
.Select(v => v.data)
.Select(p => p.pId)
.Select(p => p.data)
I get the error
Delegate 'System.Func<System.Collections.Generic.IEnumerable<pTable>>' does not take 1 arguments
What am I doing wrong?
.JoinQueryOver<pTable>(p => p.pId)
has to point to a mapped entity or collection not it an id, if you can't map it in the hbm. And also the JoinQueryOver will return pTables not vTables, you might want to use JoinAlias instead if you wan to retain the return type to be a list of vTables, but if all you want is that projection make sure you add aliases the QueryOver and JoinQueryOver calls