LINQ - Join 2 tables, Group by DateTime.Month , multiple Counts - c#

I'm pretty new to C# and LINQ and I'm trying get a list of emails that holds the sum of emails, attachments and user's (the one's that sent the email).
So my current Problem is the Output of my Query is false. The number of email's is equal to the number of attachment's which obvious is wrong.
My Query:
var monthQuery = from em in dbEdoka.email
join ema in dbEdoka.email_attachment on em.id equals ema.email_id into e
from e2 in e.DefaultIfEmpty()
group e2 by em.erstellt_am.Month into grouped
select new Entities.Month
{
NameOfMonth = grouped.FirstOrDefault().erstellt_am.ToString(),
NumberOfMails = grouped.Distinct().Count(m => m.email_id != null).ToString(),
NumberOfAttachments = grouped.Count(a => a.id != null).ToString(),
NumberOfUsers = grouped.Select(u => u.erstellt_von).Distinct().Count().ToString()
};
months = monthQuery.ToList();
Months = CollectionViewSource.GetDefaultView(months);
As you can see I had to take m.email_id from dbEdoka.email_attachment instead of m.id from dbEdoka.email because it wasn't avaliable (don't know why...).
Yet I have to count "NumberOfMails", "NumberOfAttachments" and "NumberOfUsers".
Thank you!

Related

Grouping with Linq only when all elements in a group have value in a column

I try to create groupings of lastReadings, where I can create client’s and competitor’s prices. I try below code, but this approach does not eliminate the readings that are only Client’s. With below table I need to eliminate the readingsId 5 and 6, for products C and D as there is no match and pass further only the comparable ones.
readingId ProductId Distributor Price
1 A Competitor 8.0
2 A Client 8.1
3 B Competitor 8.3
4 B Client 8.4
5 C Client 8.8
6 D Client 8.9
Below is what I get so far:
private IEnumerable<PriceComparison> getPriceComparisons(string competitor)
{
IEnumerable<IGrouping<string, LatestReading>> groupingsByProductId =
from latestReading in LatestReadings
group latestReading by latestReading.ProductId;
IEnumerable<PriceComparison> priceComparisons
= from grouping in groupingsByProductId
select new PriceComparison
{
ProductId = grouping.Key,
MyPrice = (from latestReading in grouping
where latestReading.Distributor == Client
select latestReading.Price).FirstOrDefault(),
CompetitorPrice = (from latestRading in grouping
where latestRading.Distributor == competitor
select latestRading.Price).FirstOrDefault()
};
return priceComparisons;
}
Actually as I write this post, I concluded that for the additional created groupings with "no competitor", competitor price is there as 0, so later I can easily eliminate such groupings and the code works. But somehow this approach of creating "empty" groupings does not feel right, is there a better way to only focus on the groupings excluding products C and D?
I think the Join is more suitable for what you are trying to achieve, like this
var priceComparison =
from a in LatestReadings.Where(r => r.Distributor == Client)
join b in LatestReadings.Where(r => r.Distributor == Competitor)
on a.ProductId equals b.ProductId
select new PriceComparison
{
ProductId = a.ProductId,
MyPrice = a.Price,
CompetitorPrice = b.Price
};
You can do something like this:
//...
IEnumerable<PriceComparison> priceComparisons =
from grouping in groupingsByProductId
where grouping.Any(p => p.Distributor == Client)
&& grouping.Any(p => p.Distributor == competitor)
select new PriceComparison
{
//..
};
This makes sure that you get only the groups that have a price for both Client and Competitor.

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 }

C# SQL/Linq/Entity Framework calculating column totals for multiple columns from large data source

Sorry to bother you, however I'm having issues converting my SQL Query into C# Entity Framework.
My SQL query is as follows:
SELECT CAST(ROUND(sum(size/rate), 0) AS INT) s,
CAST(ROUND(sum(PL/rate), 0) AS INT) PL
FROM [bs].[b] b
join [bs].[s] s on b.id = s.b_id
join [bs].[o] o on s.o_id = o.id
join [bs].[a] a on o.a_id = a.id
join [fs].[f] f on b.f_id = f.id
where f.r_date
between '2013-05-01 00:00:00.000'
and '2013-05-31 00:00:00.000'
and s.deleted_at is NULL
and b.group_id = '0'
and (o.a_id = 50 or o.a_id = 52)
I have in turn managed to get all the joins done and where statement in place (a.k.a. 'The Easy Bit') however I just cannot find a way to get those sums for the column totals to work.
This is what I have in place so far:
var GroupSk = (from Bs in sb.b
join S in sb.s on Bs.id equals S.b_id
join O in sb.o on S.o_id equals O.id
join A in sb.a on O.a_id equals A.id
join Fs in sb.vw_f on Bs.f_id equals Fs.f_id
where Fs.r_date >= t_FromDate && Fs.r_date <= t_ToDate
where S.deleted_at == null
where Bs.group_id == 0
where O.a_id == 50 || O.a_id == 52
select new {
As you can see, it's everything up until the SUM part of the query.
This query can return anywhere from 1-150000 rows, and I need a way to ensure that the column totals I get back are returned in a timely manner.
I had originally planned on using a ForEach loop but had trouble implementing it (along with the fact that it'll probably take a LONG time if a larger number of rows are returned).
I'm aware there are a few 'sum column total' questions out there, however they don't deal with multiple tables and multiple column outputs. They also appear to be limited to 2 or 3 columns total, whereas my tables far exceed that.
Any & all help would be greatly appreciated.
It's a bit of a hack, but it works. The trick is to make one group containing all items and then do the sums over the group:
var GroupSk = (from Bs in sb.b
join S in sb.s on Bs.id equals S.b_id
join O in sb.o on S.o_id equals O.id
join A in sb.a on O.a_id equals A.id
join Fs in sb.vw_f on Bs.f_id equals Fs.f_id
where Fs.r_date >= t_FromDate && Fs.r_date <= t_ToDate
where S.deleted_at == null
where Bs.group_id == 0
where O.a_id == 50 || O.a_id == 52
select new { r1 = ??.size / ??.rate, r2 = ??.PL / ??.rate })
.GroupBy(x => 0)
.Select(g => new {
R1 = g.Sum(x => x.r1),
R2 = g.Sum(x => x.r2)
});
I put ?? marks where I didn't know the origin of the properties, so you'll have to substitute the right variable names there. (Bs, S, O, A, Fs).
This will translate into one SQL query, so all the processing is done by the database engine and only the small result object is transferred over the wire.

Show 3 result from each element in group by

I am working on a system for handling meter reading.
I want to produce a output where the system displays all the meters belonging to the customer and for each meter, the three last readings.
So far, I have to followering code:
var lastMeterReading = from meeters in metermodel.Meeters
join reading in metermodel.Readings on meeters.MeterNumber equals reading.MeterNumber
where (maalers.CustNo == 6085574)
orderby reading.Date descending
group meeters by new { meeters.MeterNumber, reading.Consumption, reading.Date } into result
select new
{
Consumption = result.Key.Consumption, No = result.Key.MeterNumber, Date = result.Key.Date
};
Now, it shows all the meters belonging to the customer. If I put a .take(3), it only shows the first 3 results.
Thx!
Daniel
I think what you need is to put the .Take(3) in the right place.
In you case you probably did result.Take(3) but this means take the first three groups (with all their elements).
Below is an attempt to show what I mean, however, I suppose you will need to fix it in the last part, as I don't have data to test it on, and as such I'm not sure if what I'm trying to access is accessible at that point. But I hope you get what I mean.
var lastMeterReading = (from meeters in metermodel.Meeters
join reading in metermodel.Readings on meeters.MeterNumber equals reading.MeterNumber
where (maalers.CustNo == 6085574)
orderby reading.Date descending
group meeters by new { meeters.MeterNumber, reading.Consumption, reading.Date } into result
from m in result
select new {Key = m.Key, Info = result.OrderByDescending(r => r.Date).Take(3)})
.Select(r => new
{ Consumption = r.Consumption, No = r.MeterNumber, Date = r.Date });
Try this:
var lastMeterReading = from meeters in metermodel.Meeters
join reading in metermodel.Readings on meeters.MeterNumber equals reading.MeterNumber
where (maalers.CustNo == 6085574)
orderby reading.Date descending
group meeters by new { meeters.MeterNumber, reading.Consumption, reading.Date } into result
from m in result.Take(3)
select new
{
Consumption = m.Consumption, No = m.MeterNumber, Date = m.Date
};
You only want to group by MeterNumber. The way you're doing the grouping right now, you'll get a new group for every unique MeterNumber-Consumption-Date combination.
You can also simplify your query using LINQ's GroupJoin operator. In query syntax you use the "join..on..into" pattern:
from meter in meterModel.Meters
where (meter.CustNo == 6085574)
join reading in meterModel.Readings
on meter.MeterNumber equals reading.MeterNumber
into meterGroup
select meterGroup.OrderByDescending(r => r.Date).Take(3);
Or using dot notation:
meterModel.Meters
.Where(x => x.CustNo == 6085574)
.GroupJoin(
meterModel.Readings,
meter => meter.MeterNumber,
reading => reading.MeterNumber,
(meter,readings) => readings.OrderByDescending(r => r.Date).Take(3))
;

LINQ group by month question

I'm new to LINQ to SQL and I would like to know how to achieve something like this in LINQ:
Month Hires Terminations
Jan 5 7
Feb 8 8
Marc 8 5
I've got this so far, and I think there is something wrong with it but I'm not sure:
from term1 in HRSystemDB.Terminations
group term1 by new { term1.TerminationDate.Month, term1.TerminationDate.Year } into grpTerm
select new HiresVsTerminationsQuery
{
Date = Criteria.Period,
TerminationsCount = grpTerm.Count(term => term.TerminationDate.Month == Criteria.Period.Value.Month),
HiresCount = (from emp in HRSystemDB.Persons.OfType<Employee>()
group emp by new { emp.HireDate.Month, emp.HireDate.Year } into grpEmp
select grpEmp).Count(e => e.Key.Month == Criteria.Period.Value.Month)
});
Thanks in advance.
I'm not quite sure where does the Criteria.Period value come from in your sample query.
However I think you're trying to read both hires and terminations for all available months (and then you can easily filter it). Your query could go wrong if the first table (Termination) didn't include any records for some specified month (say May). Then the select clause wouldn't be called with "May" as the parameter at all and even if you had some data in the second table (representing Hires), then you wouldn't be able to find it.
This can be elegantly solved using the Concat method (see MSDN samples). You could select all termniations and all hires (into a data structure of some type) and then group all the data by month:
var terms = from t in HRSystemDB.Terminations
select new { Month = t.TerminationDate.Month,
Year = term1.TerminationDate.Year,
IsHire = false };
var hires = from emp in HRSystemDB.Persons.OfType<Employee>()
select new { Month = emp.HireDate.Month,
Year = emp.HireDate.Year
IsHire = true };
// Now we can merge the two inputs into one
var summary = terms.Concat(hires);
// And group the data using month or year
var res = from s in summary
group s by new { s.Year, s.Month } into g
select new { Period = g.Key,
Hires = g.Count(info => info.IsHire),
Terminations = g.Count(info => !info.IsHire) }
When looking at the code now, I'm pretty sure there is some shorter way to write this. On the other hand, this code should be quite readable, which is a benefit. Also note that it doesn't matter that we split the code into a couple of sub-queries. Thanks to lazy evalutation of LINQ to SQL, this should be executed as a single query.
I don't know if it shorter but you can also try this version to see if it works better with your server. I don't know exactly how these two answers turn into SQL statements. One might be better based on your indexs and such.
var terms =
from t in Terminations
group t by new {t.Month, t.Year} into g
select new {g.Key, Count = g.Count()};
var hires =
from p in Persons
group p by new {p.Month, p.Year} into g
select new {g.Key, Count = g.Count()};
var summary =
from t in terms
join h in hires on t.Key equals h.Key
select new {t.Key.Month, t.Key.Year,
Hires = h.Count, Terms = t.Count};

Categories

Resources