Error report of C # groupby using AsQueryable - c#

var commodity = _appDbContext.ArchivesCCommodity.Where(lambda)
.GroupJoin(_appDbContext.ArchivesCCommoditySpecification, a => a.Code, b => b.Commodity, (a, b) => new { a, b })
.SelectMany(a => a.b.DefaultIfEmpty(), (a, b) => new { a.a, b })
.GroupJoin(_appDbContext.ArchivesCSpecificationDetail, a => a.a.a.b.SpecificationDetail, d => d.Code, (a, d) => new { a, d })
.SelectMany(a => a.d.DefaultIfEmpty(), (a, d) => new
{
Commodity = a.a.a.Code,
CommodityName = a.a.a.Name,
SpecificationDetailName = d.Name,
OrderSN = d.OrderSN
}).AsQueryable().OrderBy(a => a.OrderSN).GroupBy(a => new { a.Commodity, a.CommodityName })
.Select(a => new
{
Commodity = a.Key.Commodity,
CommodityName = a.Key.CommodityName,
SpecificationDetailName = string.Join(" - ", a.Select(a => a.SpecificationDetailName)),
SpecificationDetailTotal = string.Join(" - ", a.Select(a => a.SpecificationDetailName)) == "" ? 0 : a.Count()
});
Where .AsQueryable() will cause an error
.AsQueryable()
.OrderBy(a => a.OrderSN)
.GroupBy(a => new { a.Commodity, a.CommodityName })
No error will be reported when changing to AsEnumerable()
.ASEnumerable()
.OrderBy(a => a.OrderSN)
.GroupBy(a => new { a.Commodity, a.CommodityName })
But I don't want to send this code to the database for the time being, because it will be sent after paging query. I don't know how to deal with it?
//////////////I pasted my complete code and talked about my actual needs
Query the code and query the database page by page. For example, only one page and 10 rows of records are checked. Here is OK.
var AA= _appDbContext.ArchivesCCommodity.Where(lambda)
.GroupJoin(_appDbContext.ArchivesCCommoditySpecification, a => a.Code, b => b.Commodity, (a, b) => new { a, b })
.SelectMany(a => a.b.DefaultIfEmpty(), (a, b) => new { a.a, b })
.GroupJoin(_appDbContext.ArchivesCSpecificationDetail, a => a.a.b.SpecificationDetail, d => d.Code, (a, d) => new { a, d })
.SelectMany(a => a.d.DefaultIfEmpty(), (a, d) => new
{
Commodity = a.a.a.a.a.Code,
CommodityName = a.a.a.a.a.Name,
SpecificationDetailName = d.Name,
OrderSN = d.OrderSN
});
PageHealper<object> page = new PageHealper<object>();
page.Start(pageNum, pageSize);
page = await page.RestPage(AA);
At this time, I grouped and sorted again, and now I found that:
It is not to operate the paging query results, but to query all the AA databases.
Based on the previous pagination query, the number of rows and page numbers are obtained. Here, the number of rows is changed by grouping and merging.
That's why I want to put grouping and sorting together, and finally pagination.
var BB = AA.AsEnumerable().OrderBy(a => a.OrderSN).GroupBy(a => new { a.Commodity, a.CommodityName, a.Specification, a.SpecificationName })
.Select(a => new
{
Commodity = a.Key.Commodity,
CommodityName = a.Key.CommodityName,
SpecificationDetailName = string.Join(" - ", a.Select(a => a.SpecificationDetailName)),
SpecificationDetailTotal = string.Join(" - ", a.Select(a => a.SpecificationDetailName)) == "" ? 0 : a.Count()
}); ;
page.Data = BB.ToList<object>();
return page;

Checkout this article https://weblogs.asp.net/zeeshanhirani/using-asqueryable-with-linq-to-objects-and-linq-to-sql about what AsQueryable does.
I think you dont really need AsQueryable there... LINQ to SQL does not like something about that query.
It does not like the String.Join(...) because it cannot translate it.
So one thing you can do is put .AsEnumerable() after the GroupBy() this will do everything up to in SQL and everything after in memory.
Ex:
var commodity = _appDbContext.ArchivesCCommodity.Where(lambda)
.GroupJoin(_appDbContext.ArchivesCCommoditySpecification, a => a.Code, b => b.Commodity, (a, b) => new { a, b })
.SelectMany(a => a.b.DefaultIfEmpty(), (a, b) => new { a.a, b })
.GroupJoin(_appDbContext.ArchivesCSpecificationDetail, a => a.a.a.b.SpecificationDetail, d => d.Code, (a, d) => new { a, d })
.SelectMany(a => a.d.DefaultIfEmpty(), (a, d) => new
{
Commodity = a.a.a.Code,
CommodityName = a.a.a.Name,
SpecificationDetailName = d.Name,
OrderSN = d.OrderSN
}).OrderBy(a => a.OrderSN).GroupBy(a => new { a.Commodity, a.CommodityName })
.AnEnumerable()
.Select(a => new
{
Commodity = a.Key.Commodity,
CommodityName = a.Key.CommodityName,
SpecificationDetailName = string.Join(" - ", a.Select(a => a.SpecificationDetailName)),
SpecificationDetailTotal = string.Join(" - ", a.Select(a => a.SpecificationDetailName)) == "" ? 0 : a.Count()
});

Related

GroupBy with SingleOrDefault in EF

I have 5 tables:
NazelShifts
Nazel
Tank
PersonnelNazelShifts
Shift
sql query is:
SELECT SUM(NazelShift.Eold) AS tEold, SUM(NazelShift.Er) AS tEr, SUM(NazelShift.Ecf) AS tEcf, SUM(NazelShift.Esf) AS tEsf, SUM(NazelShift.ESale) AS tESale, Tank.FuelId,
NazelShift.ShiftId, PersonnelNazelShift.PersonnelId
FROM NazelShift INNER JOIN
Nazel ON NazelShift.NazelId = Nazel.NazelId AND NazelShift.NazelId = Nazel.NazelId INNER JOIN
Tank ON Nazel.TankId = Tank.TankId INNER JOIN
PersonnelNazelShift ON Nazel.NazelId = PersonnelNazelShift.NazelId INNER JOIN
Shift ON NazelShift.ShiftId = Shift.ShiftId AND PersonnelNazelShift.ShiftId = Shift.ShiftId
WHERE (NazelShift.ShiftId = 1)
GROUP BY Tank.FuelId, NazelShift.ShiftId, PersonnelNazelShift.PersonnelId
NazelShift have pelation many to one with Nazel and Shift
also PersonnelNazelShift have relation many to one with Nazel and Shift.
diagram is http://jmp.sh/dlO3MTf
I need to run this query:
NazelShifts.Where(i => i.ShiftId == 1)
.GroupBy(i => new
{
i.ShiftId,
i.Nazel.Tank.FuelId,
i.Nazel.PersonnelNazelShifts.SingleOrDefault().PersonnelId
})
.Select(i => new
{
i.Key.ShiftId,
i.Key.PersonnelId,
i.Key.FuelId,
tEold = i.Sum(rr => rr.Eold),
tEr = i.Sum(rr => rr.Er),
tEcf = i.Sum(rr => rr.Ecf),
tEsf = i.Sum(rr => rr.Esf),
tESale = i.Sum(rr => rr.ESale)
})
This works fine in LinqPad4 but in vs2012 throws an exception:
"The methods 'Single' and 'SingleOrDefault' can only be used as a
final query operation. Consider using the method 'FirstOrDefault' in
this instance instead."
How can I solve this problem?
I find this solution.
NazelShifts.Where(i => i.ShiftId == 1)
.Join(Nazels,
ns => ns.NazelId,
n => n.NazelId,
(ns, n) => new { NS = ns, N = n })
.Join(Shifts,
nsn => nsn.NS.ShiftId,
s => s.ShiftId,
(nsn, s) => new { NSN = nsn, S = s })
.Join(PersonnelNazelShifts,
nsns =>new{ nsns.NSN.N.NazelId,nsns.S.ShiftId},
pns =>new { pns.NazelId,pns.ShiftId},
(nsns, pns) => new { NSNS = nsns, PNS = pns })
.Join(Tanks,
nsnspns => nsnspns.NSNS.NSN.N.TankId,
t => t.TankId,
(nsnspns, t) => new { NSNSpns = nsnspns, T = t })
.GroupBy(i => new { i.T.FuelId,i.NSNSpns.NSNS.NSN.NS.ShiftId,i.NSNSpns.PNS.PersonnelId })
.Select(i => new
{
i.Key.ShiftId,
i.Key.PersonnelId,
i.Key.FuelId,
tEold = i.Sum(rr => rr.NSNSpns.NSNS.NSN.NS.Eold),
tEr = i.Sum(rr => rr.NSNSpns.NSNS.NSN.NS.Er),
tEcf = i.Sum(rr => rr.NSNSpns.NSNS.NSN.NS.Ecf),
tEsf = i.Sum(rr => rr.NSNSpns.NSNS.NSN.NS.Esf),
tESale = i.Sum(rr => rr.NSNSpns.NSNS.NSN.NS.ESale)// not used
})

Groupby, sum with multiple joins in Linq lambda

I have the below Linq query, and I want to sum the Hours based on the WorkItem.Id:
EntryCategories
.Join(TimeReportEntries,
ec => ec.Id,
tr => tr.WorkItem.Id,
(ec, tr) => new { ec, tr })
.Join(Timesheets,
ecs => ecs.tr.Timesheet.Id,
t => t.Id,
(ecs, t) => new { ecs, t })
.Join(Users,
ts => ts.t.User.Id,
u => u.Id,
(ts, u) => new
{
Employee = ts.t.User.FirstName + " " + ts.t.User.LastName,
Hours = ts.ecs.tr.Hours
});
I have no idea as to how exactly you want it to work and cant compile it here, but I think you need a group by to get sum of hours by each work item, and that is what you're after.
EntryCategories
.Join(TimeReportEntries, ec => ec.Id, tr => tr.WorkItem.Id, (ec, tr) => new { ec, tr })
.Join(Timesheets, ecs => ecs.tr.Timesheet.Id, t => t.Id, (ecs, t) => new { ecs, t })
.Join(Users, ts => ts.t.User.Id, u => u.Id, (ts, u) => new
{
WorkItemId = ts.ecs.tr.WorkItem.Id
Employee = ts.t.User.FirstName + " " + ts.t.User.LastName,
Hours = ts.ecs.tr.Hours
})
.GroupBy(x => x.WorkItemId)
.Select(x => new
{
WorkItemId = x.Key,
Hours = x.Sum(y => y.Hours)
});

How to use predicate method in many to many relation entity framework linq lambda expression?

My problem is hard to solve. i need your help this problem. There is many to many relation in codefirst. But i can not resolve this. i would like to use Predicate func. But i can not resolve it? how to use "Method(Predicate func)"
public int Method<T>(Predicate<T> func)
{
var s1 = this.Uow.X.GetAll().Where(func)
.SelectMany(a => a.OrganizationalUnits.Where(q => Identity.Y.Contains(q.Z)))
.GroupBy(t => t, (k, g) => new
{
Tag = k,
Count = g.Count()
})
.OrderByDescending(g => g.Count);
var s2 = this.Uow.X.GetAll().Where(func)
.SelectMany(a => a.Classes.Where(q => Identity.Y.Contains(q.K)))
.GroupBy(t => t, (k, g) => new
{
Tag = k,
Count = g.Count()
})
.OrderByDescending(g => g.Count);
var s3 = this.Uow.X.GetAll().Where(func)
.SelectMany(a => a.Courses.Where(q => Identity.Y.Contains(q.L)))
.GroupBy(t => t, (k, g) => new
{
Tag = k,
Count = g.Count()
})
.OrderByDescending(g => g.Count);
return s1.ToString().Count() + s2.ToString().Count() + s3.ToString().Count();
}
Here is the way to do it. I also removed the ToString()s on the last line since you don't need an object to be a string to count it.
Call like this: int result = Method<TypeGoesHere>(p => p == aValue);
public int Method<T>(Expression<Func<T, Boolean>> Predicate)
{
var s1 = this.Uow.X.GetAll().Where(Predicate)
.SelectMany(a => a.OrganizationalUnits.Where(q => Identity.Y.Contains(q.Z)))
.GroupBy(t => t, (k, g) => new
{
Tag = k,
Count = g.Count()
})
.OrderByDescending(g => g.Count);
var s2 = this.Uow.X.GetAll().Where(Predicate)
.SelectMany(a => a.Classes.Where(q => Identity.Y.Contains(q.K)))
.GroupBy(t => t, (k, g) => new
{
Tag = k,
Count = g.Count()
})
.OrderByDescending(g => g.Count);
var s3 = this.Uow.X.GetAll().Where(Predicate)
.SelectMany(a => a.Courses.Where(q => Identity.Y.Contains(q.L)))
.GroupBy(t => t, (k, g) => new
{
Tag = k,
Count = g.Count()
})
.OrderByDescending(g => g.Count);
return s1.Count() + s2.Count() + s3.Count();
}
I think you probably want an extension method for whatever type this is. It would look like this (if thisType was type of this). This does not need to be a template since you (but not me) know the type of X.GetAll(). I'm guessing List<int> which would make T int. I've also changed the code to be briefer but have the same functionality. (It might be your original code was doing the wrong thing but this does the same thing.)
Call like this:
thisType something = new something();
someType aValue = X; // don't know the type here.
// do stuff with something
int result = something.Method(p => p == aValue);
Code:
public static int Method(this thisType me, Expression<Func<someType, Boolean>> Predicate)
{
var allOfEm = me.Uow.X.GetAll().Where(Predicate);
var s1 = allOfEm
.SelectMany(a => a.OrganizationalUnits.Where(q => Identity.Y.Contains(q.Z)))
.Distinct();
var s2 = allOfEm
.SelectMany(a => a.Classes.Where(q => Identity.Y.Contains(q.K)))
.Distinct();
var s3 = allOfEm
.SelectMany(a => a.Courses.Where(q => Identity.Y.Contains(q.L)))
.Distinct();
return s1.Count() + s2.Count() + s3.Count();
}

How to group dictionary by dateTime and do a sum on int

I have this code :
var tradeReqsBySegment = segGroups.Join(pPeriods, s => s.MarketSegmentId, p => p.EntityId, (s, p) => new
{
SegmentCode = s.SegmentCode, // string
Time = p.StartLocal, // datetime
TradeRequirement = p.Volume // double
})
.GroupBy(s => s.SegmentCode)
.ToDictionary(g => g.Key, g => g.ToDictionary(i => i.Time, i=>i.TradeRequirement));
I would like the g.ToDictionary(i => i.Time, i=>i.TradeRequirement)) to be grouped by time, and the TradeRequirement to be summed up or averaged. How do I approach this?
Also, is it possible to group the time by month by month basis, like get :
Time - TradeReq
01/2013 - 500
02/2013 - 234
...
g.GroupBy(gr => new DateTime(gr.Time.Year, gr.Time.Month, 1))
.ToDictionary(i => i.Key, i => i.Sum(s => s.TradeRequirement));
You can get both: Sum and Average at the same time, using anonymous type:
var tradeReqsBySegment = segGroups.Join(pPeriods, s => s.MarketSegmentId, p => p.EntityId, (s, p) => new
{
SegmentCode = s.SegmentCode, // string
Time = p.StartLocal, // datetime
TradeRequirement = p.Volume // double
})
.GroupBy(s => s.SegmentCode)
.ToDictionary(g => g.Key,
g => g.GroupBy(gr => new DateTime(gr.Time.Year, gr.Time.Month, 1))
.ToDictionary(gr => gr.Key.ToString("MM/yyyy"),
gr => new {
Sum = gr.Sum(s => s.TradeRequirement),
Avg = gr.Average(s => s.TradeRequirement)
}));

Better way to do what I need to do using LINQ?

I'm not sure if there is a more efficient way of doing what I'm doing using LINQ... I have two enumerations:
enumA(string): { "Andy", "Bill", "Charlie", "Doug" }
enumB(foo): { "Doug", "Edward", "George", "Bill" } (Note that enumB actually contains objects)
var dictionary = new Dictionary<string,
foreach (var a in enumA)
{
var b = enumB.SingleOrDefault(x => String.Equals(x.ToString(), a));
if (b != null)
dictionary[a] = b;
}
It just seems bad to me to enumerate over enumB over and over again and create a dictionary this way when I'm sure there is probably a more "correct" way to create a dictionary using LINQ.
You can do it efficiently using a
join
and an
ToDictionairy
call afterwards.
var listA = "abcd";
var listB = "cdef";
var tuples = from charFromA in listA
join charFromB in listB
on charFromA.ToString() equals charFromB.ToString() // instead of ToString(), do something complex
select new { A = charFromA, B = charFromB };
var dictionairy = tuples.ToDictionary(keySelector: t => t.A,elementSelector: t => t.B);
var query = from b in enumB.Where(x => x != null)
join a in enumA on b.ToString() equals a
select new { a, b };
var dictionary = query.ToDictionary(x => x.a, x => x.b);
Or with fluent API:
var dictionary = enumB.Where(b => b != null)
.Join(enumA,
b => b.ToString(),
a => a,
(b, a) => new { a, b })
.ToDictionary(x => x.a, x => x.b);
var dictionary = enumA
.Join(enumB, a => a, b => b.ToString(), (a, b) => new { A = a, B = b })
.ToDictionary(i => i.A, i => i.B);
This will throw an exception if there are any duplicate keys, so you can use:
var dictionary = enumA
.Join(enumB, a => a, b => b.ToString(), (a, b) => new { A = a, B = b })
.Aggregate(new Dictionary<string, Foo>(), (dict, i) => {
dict[i.A] = i.B;
return dict;
});
which will keep the last matching value.
var dict = enumA.Join(enumB, a => a, b => b, (a, b) => new {a, b})
.GroupBy(x => x.a)
.ToDictionary(x => x.Key, x => x.First());
GroupBy is to eliminate possible duplicates in enumB.
if enumB duplicates don't exist you can simplify it to
var dict = enumA.Join(enumB, a => a, b => b, (a, b) => new {a, b})
.ToDictionary(x => x.a, x => x.b);

Categories