LINQ Lambda - Join, Distinct - c#

I am still learning to develop LINQ lambda expressions.
I have a parent table Requests and a child table Sponsor that will have 0 or 1 row associated with a request. I would like to show a list of past sponsors that a user might have defined in any of his/her previous requests.
1st: I can find all previous requests entered by a user (Request.UserId == 1111);
2nd: The tables are associated by RequestId (request.RequestId == Sponsor.RequestId);
3rd: I want to limit the rows returned based on distinct Sponsor.Email (return the max Sponsor.RequestId based on distinct Sponsor.Email);
4th: I want them ordered by the latest sponsor used (order by descending Sponsor.RequestId);
One last caveat, I only want to return sponsor records were the Sponsor.LastNm is not null (A previous upgrade issue).
So I am close, but I am not filtering out based on emails being the same:
db.Requests
.Where (req => req.UserID == 1111)
.Join(db.Sponsors,
req => req.RequestID,
spon => spon.RequestID,
(req, spon) => new { Requests = req, Sponsors = spon })
.Where(both => both.Sponsors.LastNm != null)
.OrderByDescending(both => both.Sponsors.RequestID);
At a minimum I need the Request.DateRequested and entire Sponsor row returned.
Request Table (only certain columns)
RequestId UserId DateRequested
12 1111 2013-10-12
34 1111 2013-10-23
56 2222 2013-10-25
87 1111 2013-11-02
99 1111 2013-11-15
Sponsor Table (only certain columns)
RequestId Email LastNm
12 abc.xyz.com
34 abc#xyz.com Doe
87 abc#xyz.com Doe
99 def#xyz.com Doe
So I would like to have the following rows returned
Request.DateRequested Sponsor
2013-11-15 99, def#xyz.com, Doe
2013-11-02 87, abc#xyz.com, DOe

I find it easier to write my LINQ queries in query syntax style. It really does improve readability for me.
var qry = from r in db.Requests
join s in db.Sponsors on r.RequestID equals s.RequestID
where r.UserID == 111 &&
s.LastNm != null
orderby s.RequestID descending
group new { Request = r, Sponsor = s } by s.EMail into g
select g.First();
Sticking with function notation, it would be:
var qry = db.requests
.Where(req => req.UserID == 111)
.Join(db.sponsors,
req => req.RequestID,
spon => spon.RequestID,
(req, spon) => new { Requests = req, Sponsor = spon })
.Where(both => both.Sponsor.LastNm != null)
.OrderByDescending(both => both.Sponsor.RequestID)
.GroupBy(both => both.Sponsor.EMail)
.Select(group => group.First());
This produces the result I think you are going for. With a local replica of your data in two separate arrays,and using the following loop:
foreach (var rec in qry)
Console.WriteLine("{0}\t{1}\t{2}\t{3}", rec.Request.DateRequested, rec.Request.RequestID, rec.Sponsor.EMail, rec.Sponsor.LastNm);
I get:
11/15/2013 12:00:00 AM 99 def#xyz.com Doe
11/2/2013 12:00:00 AM 87 abc#xyz.com Doe
Also, if you have referential integrity in your database and are using EntityFramework (or OpenAccess) you can replace the join with two froms.
from r in requests
from s in r.sponsors

Related

Join with last record of details table

Please consider these two tables in my database:
Header:
Id Name
-------------------------------
1 London
2 Berlin
3 Paris
and Details:
Id HeaderId Amount YearMonth
--------------------------------------------------------------------
1 1 1000 2010-01
2 1 2000 2010-05
3 2 3000 2015-04
4 2 2700 2017-12
5 2 4500 2016-10
6 2 7000 2011-09
7 1 3000 2009-05
I want Header records with related Last Details record. For example:
HeaderId HeaderName Amount
----------------------------------------------------
1 London 2000
2 Berlin 2700
3 Paris Null
I wrote this query for Inner Join version (But I want Outer Join version):
from h in Header
join d in Details
on h.Id equals d.HeaderId
select new
{
HeaderId = h.Id,
HeaderName = h.Name,
Amount = (Details.Where(k=>k.HeaderId == h.Id).OrderBy(m=>m.YearMonth).LastOrDefault() == null ? null : Details.Where(k=>k.HeaderId == h.Id).OrderBy(m=>m.YearMonth).LastOrDefault().Amount,
}
and I got this error:
System.NotSupportedException: LINQ to Entities does not recognize the method 'Details.LastOrDefault()Details' method, and this method cannot be translated into a store expression.
How can I get above result?
thanks
This query should return desired result:
from h in Header
from d in Details.Where(d => d.HeaderId == h.Id)
.OrderByDescending(d => d.YearMonth)
.Take(1)
.DefaultIfEmpty()
select new
{
HeaderId = h.Id,
HeaderName = h.Name,
Amount = d.Amount
}
You should change your code as :
Amount = Details.Where(k=>k.HeaderId == h.Id).OrderByDescending(m => m.YearMonth).FirstOrDefault(o=>o.Amount);

How to apply self join in Linq Query?

Books Table
Id VendorId ASIN Price
-- -------- ---- ------
1 gold123 123 10
2 sil123 123 11
3 gold456 456 15
4 gold678 678 12
5 sil456 456 12
6 gold980 980 12
I want to write a linq query which will return me rows for which corresponding to every gold if sil vendor id not exist. The last three digit of vendor Id is corresponding ASIN column in that row.
Ex- For gold123 corresponding sil123 exist so that row will not be returned but for gold678 and gold980 corresponding sil not exist. So those rows will be returned.
I tried following
var gold = _repository.Query<Books>().Where(x =>
x.VendorId.Contains("gold"))
.OrderBy(x => x.Id).Skip(0).Take(500).ToList();
var asinsForGold = gold.Select(x => x.ASIN).ToList();
var correspondingSilver = _repository.Query<Books>().Where(x =>
x.VendorId.Contains("sil")
&& asinsForGold.Contains(x.ASIN)).ToList();
var correspondingSilverAsins = correspondingSilver.Select(x => x.ASIN).ToList();
var goldWithoutCorrespondingSilver = gold.Where(x =>
!correspondingSilverAsins.Contains(x.ASIN));
Can We apply self join or better way to get result only in one query instead of two query and several other list statement.
It's just another predicate, "where a corresponding silver vendor doesn't exist":
var goldWoSilver = _repository.Query<Books>()
.Where(x => x.VendorId.Contains("gold"))
.Where(x => !_repository.Query<Books>()
.Any(s => s.ASIN == x.ASIN
&& s.VendorId.Contains("sil"))
.OrderBy(x => x.Id).Skip(0).Take(500).ToList();
In many cases this is a successful recipe: start the query with the entity you want to return and only add predicates. In general, joins shouldn't be used for filtering, only to collect related data, although in that case navigation properties should be used which implicitly translate to SQL joins.
See if it helps -
var goldWithoutCorrespondingSilver = from b1 in books
join b2 in books on b1.ASIN equals b2.ASIN
where b1.VendorId.Contains("gold")
group b2 by b1.VendorId into g
where !g.Any(x => x.VendorId.Contains("sil"))
select g.FirstOrDefault();
What I have done is -
Selected records with matching ASIN
Grouped them by VendorID
Selected ones which do not have sil

Selecting Distinct Count and Sum of columns received as sub-query in Entity Framework

I want to get summarized data for a report that shows total amount & suppliers Count per decision in entity Framework Syntax. My Result needed to include a SUM of Amount and COUNT of total suppliers per decision.
I have a table of suppliers with the following columns:
SupplierNo | Decision | DecisionIssuedOn | Amount | SupplierGroup | SubSupplier
Raw SQL query to get above data for a specific time period is:
SELECT S.Decision, SUM(S.Amount) AS TotalAmount, COUNT(DISTINCT S.SupplierNo) AS SupplierCount
FROM (SELECT * FROM Indentors WHERE Indentors.DecisionIssuedOn BETWEEN '2018-01-01' AND '2018-12-31') S
GROUP BY S.Decision
Which gives data as:
SupplierCount | Amount
-----------------------
Approved 20 | 5000
Rejected 11 | 3000
In-Process 5 | 1500
Now from front end, the condition parameters can be anything from the given pool of options (dropdowns) which when selected add where clause in the exixting query like
WHERE Decision = 'Approved' AND SupplierGroup ='ABC' AND SubSupplier ='zxc'
The problem is I am having a hard time getting the desired result using Entity Framework lambda expressions instead of raw SQL.
What I did so far:
I checked for the availability of Options from fornt-end to build where clause as:
IQueryable<Supplier> suppliers = this.db.suppliers.OrderByDescending(i => i.Id);
if (string.IsNullOrEmpty(selectedSupplierGroup) == false)
{
suppliers = suppliers.Where(i => i.SupplierGroup == selectedSupplierGroup);
}
if (string.IsNullOrEmpty(selectedSubSupplier) == false)
{
suppliers = suppliers.Where(i => i.SubSupplier == selectedSubSupplier);
}
if (string.IsNullOrEmpty(selectedDecision) == false)
{
suppliers = suppliers.Where(i => i.Decision == selectedDecision);
}
if (selectedDecisionIssuedOn.HasValue)
{
suppliers = suppliers.Where(i => i.DecisionIssuedOn >= selectedDecisionIssuedOn);
}
var result = suppliers
.GroupBy(i => i.Decision)
.Select(i => i.SupplierNo).Distinct().Count(); // Gives me error
The error is:
IGrouping does not contain a definition for SupplierNo, and no extension method blah blah blah...
But after that I am unable to get data as the raw query (described above) would get me. Thanks
This should give you a similar result to your SQL query. Give it a try and see how you get on:
var results = suppliers
.Where(i => i.DecisionIssuedOn >= selectedDecisionIssuedOn)
.GroupBy(i => i.Decision)
.Select(group => new
{
Decision = group.Key,
TotalAmount = group.Sum(g => g.Amount),
SupplierCount = group.Select(i => i.SupplierNo).Distinct().Count()
});

Efficient way of finding multiple dates per ID

I'm trying to query my MsSQL Express database to find all CompanyID's which have multiple dates associated - when I say multiple dates, I must point out they need to be over different days.
EG
ID UkDate CompanyId
1 01/01/2015 16
2 01/01/2015 16
3 03/01/2015 18
4 05/01/2015 19
5 06/01/2015 20
6 08/01/2015 20
In the example above, only the rows with ComapnyID 20 would be returned because it occurred multiple times and those times were over dates (note that although companyId 16 has multiple entries, but both entries are the same date).
I'm not sure how to write the query for this using Linq. My object is already IQueryable<T> but, I'm not sure how to perform the query without executing the code, and then 'finishing off' the query.
I'm not near Visual Studio but the code would be (please forgive typing errors, this is from memory)
//First, grab unique CompanyIds as this removes those who didn't visit multiple times
var uniqueIds = (from d in this._database.MyTable
select companyId).Distinct();
//This is the problem because on each iteration I'm re-querying the database!
foreach(var id in uniqueIds)
{
var result = (from d in this._database.MyTable.OrderBy(a=>a.UkDate)
where d.CompanyId==id
select d);
//check for nulls
if (result.First(a=>a.UkDate.Day) != result.Last(a => a.UkDate.Day)
{
this.AllResultsList.AddRange(results);
}
}
Whilst it works without error I don't feel the code is correct - it feels like a hack and unefficient but this was my best effort. Is there a way I could reduce the number of database requests I make and achieve the same result
It would be something along the lines of
var results = myTable.GroupBy(x => x.CompanyID)
.Where(g => g.GroupBy(g2 => g2.UkDate).Count()>1)
.Select(g => g.Key);
Live example (albeit with LinqToObjects, but the query should work against a database just fine): http://rextester.com/FPHI53553
var results = (from o in this._database.MyTable
group o by o.CompanyId into grouped
where (grouped.Max(s => s.UKDate) - grouped.Min(s => s.UKDate)).TotalDays > 0
select grouped.Key);
Edit (by OP)
Final result:
var results = (from o in this._database.MyTable
group o by o.CompanyId into grouped
where (Convert.ToDateTime(grouped.Max(s => s.UKDate)) - Convert.ToDateTime(grouped.Min(s => s.UKDate))).TotalDays > 0
from l in myTable
where l.CompanyID == grouped.Key
select l).ToList();
A little different version:
var result = (from o in this._database.MyTable
group o by o.CompanyId into grouped
select new {
grouped.Key,
Count = grouped.Select(c => c.UkDate).Distinct().Count()
} into filter
where filter.Count > 1
join a in this._database.MyTable on filter.Key equals a.CompanyID
select new { a.CompanyID, a.UkDate}
).ToList();
You can also try this if you want the company id and a count of the different dates:
from c in dataTable
group c by c.CompanyId into grouped
let count = grouped.Select(x => x.UkDate).Distinct().Count()
where count > 1
select new { CompanyId = grouped.Key, Count = count }

Joining and grouping multiple tables with LINQ when using a linking table

I'm looking for some advice on how best to get the first record when using a join with multiple tables, as demonstrated below.
I have three tables:
Leads <-- this should be unique in the results
LeadAddresses (joining
table)
Addresses
Normally I'd join them like this:
from t2
in db.Leads
.Where(o => t1.LeadId == o.Lead_ID)
from t4
in db.LeadAddresses
.Where(o => t2.Lead_ID == o.Lead_ID)
.DefaultIfEmpty()
from t5
in db.Addresses
.Where(o => t4.Address_ID == o.Address_ID)
.DefaultIfEmpty()
(if this is bad practice, let me know ;)
I'm looking to get a property from the Addresses table (the one with, say, the maximum ID) for each Lead record and project to a model:
select new LeadGridModel
{
...
});
Example:
Lead Company | City | ZIP
==============================
Company 1 | Boston | 00000
Company 2 | Houston | 00001
from l in db.Leads
from a in l.LeadAddresses.Select(la => la.Address).OrderByDescending(a => a.ID).Take(1).DefaultIfEmpty()
select new { l, a }
This might look tricky, but you understand it part by part:
Using OrderByDescending in combination with Take(1) we take the address with the maximum ID
Using DefaultIfEmpty we create a left-join.
Be aware that this pattern forces a loop-join due to limitation of SQL Server. For small result sets this is usually not a problem.

Categories

Resources