Complex nHibernate QueryOver expression - c#

I have the following objects in a hierarchy A > B > C > D. Each object is mapped to a table. I'm trying to write the following SQL using QueryOver:
SELECT B
FROM A, B, C, D
WHERE A.ID = B.ID
AND B.ID = C.ID
AND C.ID = D.ID
WHERE A.NUMBER = 'VALUE'
AND D.NAME IN ('VALUE1', 'VALUE2')
I have the C# code so far:
string[] entityNames = entityAttributes.Select(e => e.Name).ToArray();
string customerNumber = 2;
return session.QueryOver<B>()
.JoinQueryOver(b => b.C)
.JoinQueryOver(c => c.D)
.WhereRestrictionOn(d => d.Name).IsIn(entityNames)
.List<B>();
What's missing here is the A > B link. I cannot figure out how to add the join to A restricting it on the NUMBER field. I tried the following but the .JoinQueryOver(b => b.C) is looking for type A instead of finding type B.
return session.QueryOver<B>()
.JoinQueryOver(b => b.A)
.Where(a => a.Number == customerNumber)
.JoinQueryOver(b => b.C) **//Looks for type A instead of B**
.JoinQueryOver(c => c.D)
.WhereRestrictionOn(d => d.Name).IsIn(entityNames)
.List<B>();
How can I add type A to this query while still returning type B?

you can use Aliases to join your table like
B tB = null;
A tA = null;
C tC = null;
D tD = null;
var qOver = HibSession.QueryOver<B>(() => tB)
.JoinAlias(() => tB.A, () => tA, JoinType.LeftOuterJoin)
.Where(() => tA.Number == customerNumber)
.JoinAlias(() => tB.C, () => tC, JoinType.LeftOuterJoin)
.JoinAlias(() => tC.D, () => tD, JoinType.LeftOuterJoin)
.Where(Restrictions.On(() => tD.Name).IsIn(entityNames))
.List<B>();
I hope it's helpful.

Related

How GroupBy can be used in LINQ?

I have a following code:
var sql = db.Accounts.AsNoTracking()
.Join(db.Customers.AsNoTracking(),
d => d.AccountNr, c => c.CustNr,
(d, c) => new {Accounts = d, Customers = c })
.GroupBy(g => g.Accounts.AccountNr)
.Where(w => w.Accounts.Date == null)
.Select(s => new
{
Company = s.Customers.CompName,
TWQ = s.Customers.TWQ,
AccountNr = s.Accounts.AccountNr,
DocDate = s.Accounts.DocumentDate,
Income = s.Customers.Income
})
.OrderBy(o => o.DocDate);
The issue is that c# underlines the whole WHERE part with an alert saying that: Element IGrouping <string,> has no definition of Accounts and extension method of Accounts can not be found
I don't know where the problem lies. I also tried to use GroupBy in model (instead of using it in the code above) but got the some problem:
var model = (from ss in sql // here I refer to sql outcome I got from the code above
.GroupBy(g => g.AccountNr)
.Skip(page * 15 - 15)
.Take(15)
.AsEnumerable()
select new DocumentsModel
{
Company = s.Customers.CompName,
TWQ = s.Customers.TWQ,
AccountNr = s.Accounts.AccountNr,
DocDate = s.Accounts.DocumentDate,
Income = s.Customers.Income
}).ToList();
At first I would like to say that my English isn't that good.
If you execute an GroupBy, you'll get collection of elements where each element represents a projection over a group and its key.
That's why I execute SelectMany afterwards to work with the model in a normal way.
db.Accounts
.AsNoTracking()
.Join
(
inner: db.Customers.AsNoTracking(),
outerKeySelector: x => x.AccountNr,
innerKeySelector: x => x.CustNr,
resultSelector: (Accounts, Customers) => new
{
Accounts, Customers
}
)
.Where
(
predicate: x => x.Accounts.Date == null
)
.GroupBy
(
keySelector: x => x.Accounts.AccountNr
)
.SelectMany
(
selector: x => x
)
.Select
(
selector: x => new
{
Company = x.Customers.CompName,
TWQ = x.Customers.TWQ,
AccountNr = x.Accounts.AccountNr,
DocDate = x.Accounts.DocumentDate,
Income = x.Customers.Income
}
)

joining two from with an expression<func<>>

Is it possible to join two from based on a local expression variable?
ex;
var query = from t in context.table1
from a in context.anothertable1.Where(x => t.id == a.id)
select new {a,t};
on line 2, the Where clause .Where(x => t.id == a.id) how would you move it into an expression?
I know i can do this;
Expression<Func<anothertable1, bool>> test = x => x.field1 == 1;
and It would work here;
var query = from t in context.table1
from a in context.anothertable1
.Where(x => t.id == a.id)
.Where(test)
select new {a,t};
and everything work and the sql query generated is as expected.
I can't figure out how to do the same with the other where.
EDIT
a more complex example, i anonymized it so it might not compile
var listOfMinMaxtable1 = (from n in context.table1.Where(table1Filter)
group n by n.table1_Number into grp
select new MinMaxtable1()
{
table1_Id_Max = grp.Max(x => x.table1_Id),
table1_Id_Min = grp.Min(x => x.table1_Id),
table1_Number = grp.Key
});
var listtable2 = (from t in context.table2
group t by t.table2_Id into grp
select new table2()
{
table2 = grp,
table2_Id = grp.Key
});
var query = from MinMax in listOfMinMaxtable1
//inner join **reference 1**
from table3 in context.table3
.Where(x => x.table_Number == MinMax.table_Number)
.Where(noticeMasterFilter) //a working expression<func<>>
//inner join **reference 2**
from Lasttable1 in context.table1
.Where(x => x.table_Id == MinMax.table_Id_Max)
//left join **reference 3**
from Firsttable1 in context.table1
.Where(x => x.table_Id == MinMax.table_Id_Min)
.Where(firstNoticeFilter) //a working expression<func<>>
.DefaultIfEmpty()
//left join **reference 4**
from Lasttable2 in listtable2
.Where(x => x.table_Id == MinMax.table_Id_Max)
.SelectMany(x => x.table2)
.Where(x => x.table2_Id == 123)
.OrderByDescending(x => x.table_Id)
.Take(1)
.DefaultIfEmpty()
if you find //left join reference 3 in the code above
that where clause; .Where(x => x.table_Id == MinMax.table_Id_Min)
might be sometime; .Where(x => x.table_Id == MinMax.table_Id_Max)
I could just copy/paste the whole from and change the where clause while adding noop pattern (an expression that return false and this make entity framework remove the whole thing so it doesn't affect the generated sql/result) with an expression on both from
for reference(this is noise to the question), the noop expression that i'm talking about is;
Expression<Func<table1, bool>> includeFrom= x => false;
and would be used like
//left join **reference 3**
from Firsttable1 in context.table1
.Where(x => x.table_Id == MinMax.table_Id_Min)
.Where(firstNoticeFilter) //a working expression<func<>>
.Where(includeFrom) //<--- this line make it a noop if the expression stay false
.DefaultIfEmpty()
but I don't want to do this if it's possible to make a custom expression that would go into the .Where()
Instead of creating an expression based on one type, you can create a combined type and use that for your where expression.
Two Table Combined Type
public class TwoTableDto
{
public Table1 t { get; set; }
public Table2 a { get; set; }
}
Query without expression
var query = (from t in context.table1
from a in context.anothertable1
select new TwoTableDto { t = t, a = a })
.Where(x => x.t.id == x.a.id);
Expression
Expression<Func<TwoTableDto, bool>> expr = x => x.t.id == x.a.id;
Query with expression
var query = (from t in context.table1
from a in context.anothertable1
select new TwoTableDto { t = t, a = a })
.Where(expr);

Do can I convert this query syntax linq expression to method syntax?

I've written this code to join two tables together from sql server, now I want to write this as in method syntax. How can I rewrite this code?
LinqToLoginDataContext lnqdore = new LinqToLoginDataContext();
var f = (from k in lnqdore.Table_Years
join h in lnqdore.Table_Dores on k.Id equals h.FK_Year
where h.Id == (int)dataviewDore.CurrentRow.Cells["Id"].Value
select k).Single();
var f = lnqdore.Table_Years
.Join(lnqdore.Table_Dores, k => k.ID, h => h.FK_Year, (k, h) => new { k, h })
.Where(res => res.h.ID == (int)dataviewDore.CurrentRow.Cells["Id"].Value)
.Select(res => res.k)
.Single();

Translate from LINQ query syntax to method syntax

I have the following join:
var simpleJoin = from b in books
join p in publishers on b.PublisherName equals p.Name
where p.Id == 1
select b;
What is the equivalent using the method syntax? I'm getting tripped up by the filter:
simpleJoin = books.Join(publishers, p => p.PublisherName, b => b.Name, (b, p) => b).Where(*can't access publishers here*)
Can I not use books as my source collection? I'm wondering how we could manage filtering if we have multiple joins.
You'll need to include both b and p in the resultSelector. For example, using an anonymous-typed object:
simpleJoin = books.Join(publishers, p => p.PublisherName, b => b.Name,
(b, p) => new { b = b, p = p })
.Where(result => result.p.Id == 1)
.Select(result => result.b);
You can filter the publishers list prior to joining it to books:
var simpleJoin = books.Join(publishers.Where(p => p.Id == 1),
b => b.PublisherName, p => p.Name, (b, p) => b);
Just as example - you can also make it without join clause:
books.Where(b => publishers.Exists(p => p.Name == b.PublisherName && p.Id == 1));

Linq SelectMany

Hi I am coding my way through the MS 101 linq examples.
The "JoinOperators" are giving me a hard time since I am trying to refactor the query expressions to lambda syntax and vice versa.
Anyway, on example 105 I see this query expression:
var supplierCusts =
from sup in suppliers
join cust in customers on sup.Country equals cust.Country into cs
from c in cs.DefaultIfEmpty() // DefaultIfEmpty preserves left-hand elements that have no matches on the right side
orderby sup.SupplierName
select new
{
Country = sup.Country,
CompanyName = c == null ? "(No customers)" : c.CompanyName,
SupplierName = sup.SupplierName
};
And I tried implementing it as a lambda this way:
// something is not right here because the result keeps a lot of "Join By" stuff in the output below
var supplierCusts =
suppliers.GroupJoin(customers, s => s.Country, c => c.Country, (s, c) => new { Customers = customers, Suppliers = suppliers })
.OrderBy(i => i.Suppliers) // can't reference the "name" field here?
.SelectMany(x => x.Customers.DefaultIfEmpty(), (x, p) => // does the DefaultIfEmpty go here?
new
{
Country = p.Country,
CompanyName = x == null ? "(No customers)" : p.CompanyName,
SupplierName = p // not right: JoinOperators.Program+Customer ... how do I get to supplier level?
});
For some reason I can't access the supplier-level information this way. When I switch out the customers with suppliers I can't access the customer-level information.
Is there some overload of SelectMany() that lets me pull from the field-level of both objects?
Also, I don't understand why the GroupJoin() appears to return an object with 2 collections (suppliers and customers). Isn't it supposed to join them somehow?
I guess I don't understand how GroupJoin() works.
You have wrong result selector in group join, that's where problems started. Here is fixed query:
var supplierCusts =
suppliers
.GroupJoin(customers,
sup => sup.Country,
cust => cust.Country,
(sup, cs) => new { sup, cs })
.OrderBy(x => x.sup.Name)
.SelectMany(x => x.cs.DefaultIfEmpty(), (x, c) =>
new
{
Country = x.sup.Country,
CompanyName = c == null ? "(No customers)" : c.CompanyName,
SupplierName = x.sup.Name
});
If you want to learn translating the query expressions into lambda's, I suggest you check out LinqPad which can do that by default. For example, your query is translated as follows:
Suppliers
.GroupJoin (
Customers,
sup => sup.Country,
cust => cust.Country,
(sup, cs) =>
new
{
sup = sup,
cs = cs
}
)
.SelectMany (
temp0 => temp0.cs.DefaultIfEmpty (),
(temp0, c) =>
new
{
temp0 = temp0,
c = c
}
)
.OrderBy (temp1 => temp1.temp0.sup.CompanyName)
.Select (
temp1 =>
new
{
Country = temp1.temp0.sup.Country,
CompanyName = (temp1.c == null) ? "(No customers)" : temp1.c.CompanyName,
SupplierName = temp1.temp0.sup.CompanyName
}
)
That being said, I typically find SelectMany to be easier to code and maintain using the query syntax instead of the lambda syntax.
The GroupJoin in this example is used to accomplish the left join (via the .DefaultIfEmpty clause).
Try this:
var supplierCusts =
suppliers.GroupJoin(customers, s => s.Country, c => c.Country, (s, c) => new { Supplier = s, Customers = c })
.OrderBy(i => i.Supplier.SupplierName)
.SelectMany(r => r.Customers.DefaultIfEmpty(), (r, c) => new
{
Country = r.Supplier.Country,
CompanyName = c == null ? "(No customers)" : c.CompanyName,
SupplierName = r.Supplier.SupplierName
});

Categories