How can I fix linq query to select count of ids with group by? - entity-framework

I want to create this SQL query to linq:
SELECT
COUNT(m.FromUserId) AS Messages,
m.FromUserId AS UserId
FROM
dbo.ChatMessages m
INNER JOIN
dbo.ChatMessagesRead mr ON mr.ChatMessageId = m.ChatMessageId
WHERE
m.ToUserId = #toUserId
GROUP BY
m.FromUserId
I have tried create following linq query:
var messages = from m in _dbContext.ChatMessages
join mread in _dbContext.ChatMessagesRead on m.ChatMessageId equals mread.ChatMessageId
where m.ToUserId == userId
group m by m.FromUserId into g
select new
{
UserId = g.Key,
Messages = g.Count()
};
var messagesList = messages.ToList();
But this doesn't work.
How can I fix this linq query?
I get this exception:
Expression of type 'System.Func2[Microsoft.Data.Entity.Query.EntityQueryModelVisitor+TransparentIdentifier2[Project.BL.ChatMessages.ChatMessages,Project.BL.ChatMessages.ChatMessagesRead],System.Int32]' cannot be used for parameter of type 'System.Func2[<>f__AnonymousType12[Project.BL.ChatMessages.ChatMessages,Project.BL.ChatMessages.ChatMessagesRead],System.Int32]' of method 'System.Collections.Generic.IEnumerable1[System.Linq.IGrouping2[System.Int32,Project.BL.ChatMessages.ChatMessages]] _GroupBy[<>f__AnonymousType12,Int32,ChatMessages](System.Collections.Generic.IEnumerable1[<>f__AnonymousType12[Project.BL.ChatMessages.ChatMessages,Project.BL.ChatMessages.ChatMessagesRead]], System.Func2[<>f__AnonymousType12[Project.BL.ChatMessages.ChatMessages,Project.BL.ChatMessages.ChatMessagesRead],System.Int32], System.Func2[<>f__AnonymousType1`2[Project.BL.ChatMessages.ChatMessages,Project.BL.ChatMessages.ChatMessagesRead],Project.BL.ChatMessages.ChatMessages])'"

I'm facing the same issue and I've found that there is an opened issue on the Entity Framework Core bugtracker
The only workaround for now seems to split the request in two.
var filtered = (from m in _dbContext.ChatMessages
join mread in _dbContext.ChatMessagesRead on m.ChatMessageId equals mread.ChatMessageId
where m.ToUserId == userId
select m).ToList();
var messages = from m in filtered
group m by m.FromUserId into g
select new
{
UserId = g.Key,
Messages = g.Count()
};

you can try this
var res = ctx.MyTable // Start with your table
.GroupBy(r => r.id) / Group by the key of your choice
.Select( g => new {Id = g.Key, Count = g.Count()}) // Create an anonymous type w/results
.ToList(); // Convert the results to List

Your code should work. However I created another version of your query using extension methods.
var messages =
_dbContext
.ChatMessages
.Where(message => message.ToUserId == userId)
.Join(
_dbContext.ChatMessageRead,
message => message.ChatMessageId,
readMessage => readMessage.ChatMessageId,
(m, mr) => m.FromUserId
)
.GroupBy(id => id)
.Select(group =>
new
{
UserId = group.Key,
Messages = group.Count()
}
);
Could you please try it if it also throws the same exception or not?

Related

Select FROM Subquery without starting with another context object

I am trying to model the following MSSQL query that I am trying to replicate in netCore 2.2 - EF Core:
SELECT
wonum,
MIN(requestdate) AS startdate,
MAX(requestdate) AS enddate,
MIN(laborcode)
FROM
(
SELECT
wo.wonum,
sw.requestdate,
wo.wolablnk AS 'laborcode'
FROM
DB1.dbo.web_users wu INNER JOIN
DB2.dbo.workorder wo on
wu.laborcode = wo.wolablnk INNER JOIN
DB2.dbo.sw_specialrequest sw on
wo.wonum = sw.wonum
WHERE
wo.status in ('LAPPR', 'APPR', 'REC') AND
sw.requestdate > GETDATE()
) a
GROUP BY
wonum
ORDER by
I have the subquery portion built and working but that leaves me at an impasse:
var workOrders = await _db1Context.Workorder
.Where(r => r.Status == "LAPPR" || r.Status == "APPR" || r.Status == "REC")
.ToListAsync();
var specialRequests = await _db2Context.SwSpecialRequest
.Where(r => r.Requestdate > DateTime.Now)
.ToListAsync();
var subQuery = (from webUser in webUsers
join workOrder in workOrders on webUser.Laborcode equals workOrder.Wolablnk
join specialRequest in specialRequests on workOrder.Wonum equals specialRequest.Wonum
orderby webUser.Laborcode, specialRequest.Requestdate, specialRequest.Wonum
select new { workOrder.Wonum, Laborcode = workOrder.Wolablnk, specialRequest.Requestdate, workOrder.Workorderid })
.ToList();
I am not sure how to initiate the query I need with the subquery i've built and i'm not sure if I am on the right track even. I've looked at a couple of other examples but i'm not getting it.
Would anyone be able to shed some light on the subject and help?
Thank you!
Write LINQ query identical to the SQL and do not mix with ToListAsync(). After ToListAsync() query is sent to the server. Also you should use only one DbContext for such query.
var webUsers = _db1Context.Webuser;
var workOrders = _db1Context.Workorder
.Where(r => r.Status == "LAPPR" || r.Status == "APPR" || r.Status == "REC");
var specialRequests = _db1Context.SwSpecialRequest
.Where(r => r.Requestdate > DateTime.Now);
var subQuery =
from webUser in webUsers
join workOrder in workOrders on webUser.Laborcode equals workOrder.Wolablnk
join specialRequest in specialRequests on workOrder.Wonum equals specialRequest.Wonum
select new
{
workOrder.Wonum,
Laborcode = workOrder.Wolablnk,
specialRequest.Requestdate
};
var resultQuery =
from a in subQuery
group a by a.Wonum into g
select new
{
Wonum = g.Key,
StartDate = g.Min(x => x.Requestdate),
EndDate = g.Max(x => x.Requestdate),
Laborcode = g.Min(x => x. Laborcode)
};
// final materialization
var result = await resultQuery.ToListAsync();

Linq query select property list with join

I'm trying to get a list in a linq with joins and get all events for specific user.
This is the query I have so far:
var runnerObject = from r in _context.Runners
join re in _context.RunnerEvents
on r.RunnerId equals re.RunnerId
join e in _context.Events
on re.EventId equals e.EventId
where r.RunnerId == runnerId
select new RunnerVM
{
RunnerId = r.RunnerId,
FirstName = r.FirstName,
LastName = r.LastName,
UserId = r.UserId,
Events = //get all events in Events table for the runnerId
};
Events should be all entries from Events table for that runner, based on their id which is joined in the RunnerEvents table. How can I get that?
Something like this?
var runnerObject = from r in _context.Runners
join re in _context.RunnerEvents
on r.RunnerId equals re.RunnerId
join e in _context.Events
on re.EventId equals e.EventId
where r.RunnerId == runnerId
select new RunnerVM
{
RunnerId = r.RunnerId,
FirstName = r.FirstName,
LastName = r.LastName,
UserId = r.UserId,
Events = r.Events.Select(e => new Event { }).ToList()
};

Can't add/sum string values in IEnumerable

The ultimate goal is to display a GrandTotal column using Highcharts. The GrandTotal should be the sum of TotalAmount for a given Offer id. TotalAmount is a string and the values are like $10.00 or 10.00. GrandTotal is an int, but could easily be changed. Here is what I have done so far.
Step 1) Convert the two IEnumerable lists into their ViewModel counterparts. I set GrandTotal to 0 here because I don't know the amount.
var offersConvert = offers
.Select(o => new OfferSummaryViewModel
{
Id = o.Id,
Name = o.Name,
Created = o.Created,
Shares = o.Shares,
Redemptions = o.Redemptions,
GrandTotal = 0
})
.ToList();
var sharedOffersConvert = sharedOffers
.Select(s => new SharedOfferViewModel
{
OfferId = s.OfferId,
//TotalAmount = s.TotalAmount.Replace("$", string.Empty).Replace(",", string.Empty).Trim()
TotalAmount = s.TotalAmount
})
//.Where(i => i.TotalAmount != null)
.ToList();
Step 2) Join the two lists on the Id of the Offer.
var data = offersConvert
.Join(sharedOffersConvert,
o => o.Id,
s => s.OfferId,
(o, s) => new { offersConvert = o, sharedOffersConvert = s })
.Select(o => new
{
Id = o.offersConvert.Id,
Created = o.offersConvert.Created,
Shares = o.offersConvert.Shares,
Redemptions = o.offersConvert.Redemptions,
Name = o.offersConvert.Name,
OfferId = o.sharedOffersConvert.OfferId,
TotalAmount = o.sharedOffersConvert.TotalAmount,
//GrandTotal = Convert.ToInt32(o.sharedOffersConvert.TotalAmount.Replace("$", string.Empty).Replace(",", string.Empty).Trim())
//GrandTotal = Convert.ToInt32(Convert.ToDouble(o.sharedOffersConvert.TotalAmount, CultureInfo.InvariantCulture))
})
//.Where(o => o.Id == o.OfferId)
.OrderBy(o => o.Created.Add(offset))
.ToList();
As you can tell, I've tried to remove any dollar signs and commas. I've even tried to trim white space in order to get clean data. I am then trying to convert the strings to int values, so I can sum them. Nothing seems to work. I've even tried .GroupBy and other methods (see below). At least with .GroupBy I can get to the .Sum operator. With the other method I run into issues when I can't convert int into ToList(), so I have to try and convert ToString().
.Where(o => o.Id == o.OfferId)
.GroupBy(g => g.Id)
.Select(x => new { GrandTotal = x.Sum(o => o.TotalAmount) })
ERROR in above: can't convert TotalAmount to decimal
.Where(i => i.Id == i.OfferId)
.Sum(i => Convert.ToInt32(Convert.ToDouble(i.TotalAmount, CultureInfo.InvariantCulture)
)).ToString()
Does anyone know how I can add/sum the string values in TotalAmount to get a GrandTotal per Offer id?
Any help us much appreciated. Thanks!
UPDATE: This works, but I really don't understand why and I don't think it is very clean. I really couldn't find many examples where people were joining two lists together and summing one of the columns. This seems pretty common to me, but perhaps it is not.
var data = (from o in offersConvert
join s in sharedOffersConvert on o.Id equals s.OfferId
orderby o.Created.Add(offset)
let k = new
{
Id = o.Id,
Name = o.Name,
Created = o.Created,
Shares = o.Shares,
Redemptions = o.Redemptions
}
group s by k into totals
select new
{
OfferId = totals.Key.Id,
Name = totals.Key.Name,
Created = totals.Key.Created,
Shares = totals.Key.Shares,
Id = totals.Key.Id,
Redemptions = totals.Key.Redemptions,
GrandTotal = totals.Sum((s => s.TotalAmount == null ? Decimal.Zero : Decimal.Parse(s.TotalAmount, NumberStyles.Currency)))
})
.ToList();
You can use
Decimal.TryParse("$10.00", NumberStyles.Currency, CultureInfo.CurrentCulture, out var res);
Or in the context of your LINQ,
GrandTotal = Decimal.Parse(o.sharedOffersConvert.TotalAmount, NumberStyles.Currency)
if o.sahredOffersConvert.TotalAmount may be null,
GrandTotal = (o.sharedOffersConvert.TotalAmount ==null) ? Decimal.Zero : Decimal.Parse(o.sharedOffersConvert.TotalAmount, NumberStyles.Currency)
I'm still puzzled why everyone lately has this odd fascination with the Lambda syntax. And that seems to have been only one of several means you've used to make this more complicated than necessary:
var data = (from o in offers
join s in sharedOffers on o.Id equals s.OfferId
orderby o.Created.Add(offset)
select new
{
Id = o.Id,
Created = o.Created,
Shares = o.Shares,
Redemptions = o.Redemptions,
Name = o.Name,
OfferId = o.OfferId,
TotalAmount = Decimal.Parse(o.TotalAmount, NumberStyles.Currency)
})
.ToList();
Further, as offset seems to be a constant (for the life of this query), adding it to Created isn't going to affect the ordering, and that bit can be removed.
And, since it appears your final output is just the grand totals, it can be reduced further to:
var data = (from o in offers
join s in sharedOffers on o.Id equals s.OfferId
orderby o.Created
group Decimal.Parse(o.TotalAmount, NumberStyles.Currency) by o.id into totals
select new
{
Id = totals.Key,
GrandAmount = totals.Sum()
})
.ToList();
UPDATE: Putting back what I took out... This should work (I don't have your tables, so I can't test it)
var data = (from o in offers
join s in sharedOffers on o.Id equals s.OfferId
orderby o.Created.Add
group o by o.Id into totals
let item = totals.First()
select new
{
Id = item.Id,
Created = item.Created,
Shares = item.Shares,
Redemptions = o.Redemptions,
Name = item.Name,
OfferId = item.OfferId,
GrandTotal = totals.Sum(t=>Decimal.Parse(t.TotalAmount, NumberStyles.Currency))
})
.ToList();

Get Counts using Linq to SQL

I have the following SQL which works fine
SELECT
f.ForumId,
f.Name,
COUNT(ft.TopicId) AS TotalTopics,
COUNT(fm.MessageId) AS TotalMessages
FROM
tblForumMessages fm INNER JOIN
tblForumTopics ft ON fm.TopicId = ft.TopicId RIGHT OUTER JOIN
tblForums f ON ft.ForumId = f.ForumId
GROUP BY f.ForumId, f.Name
That I'm trying to convert to Linq.
Here's what I have
var forums = (from f in Forums
join ft in ForumTopics on f.ForumId equals ft.ForumId into topics
from y in topics.DefaultIfEmpty()
join fm in ForumMessages on y.TopicId equals fm.TopicId into messages
from x in messages.DefaultIfEmpty()
select new { f.ForumId, f.Name, y.TopicId, x.MessageId } into x
group x by new { x.ForumId, x.Name } into g
select new
{
ForumId = g.Key.ForumId,
ForumName = g.Key.Name,
TopicCount = g.Count(i => i.TopicId),
MessageCount = g.Count(i => i.MessageId)
}
).ToList();
I'm getting an error on TopicCount = g.Count(i => i.TopicId) saying Cannot convert expression type 'System.Guid' to return type 'bool'.
What am I missing to make this work?
Thanks
* EDIT *
Thanks to Rob I got it to work but the counts were always returning 1 for Topic Count and Message Count even though there were no records. It should have been returning 0 for both.
I've modified the query by changing
select new { f.ForumId, f.Name, y.TopicId, x.MessageId } into x
to
select new
{
f.ForumId, f.Name,
TopicId = y != null ? y.TopicId : (Guid?)null,
MessageId = z != null ? z.MessageId : (Guid?)null
} into x
And for the actual counts, I changed the query to
select new
{
g.Key.ForumId,
g.Key.Name,
TopicCount = g.Count(t => t.TopicId != null),
MessageCount = g.Count(t => t.MessageId != null)
}
The offending article is TopicCount = g.Count(i => i.TopicId). The Count method takes a Func<T, bool> (it gives the number of items in the collection that satisfy the predicate).
It looks like you want the number of distinct TopicIds in your group. Try replacing TopicCount = g.Count(i => i.TopicId) with TopicCount = g.GroupBy(i => i.TopicId).Count().
You can also try TopicCount = g.Select(i => i.TopicId).Distinct().Count()

Converting T-SQL to Linq

I am using Entitry Framework 4.1 and I am struggling to understand how the convert the below query which uses joins and aggregate methods to a Linq to Entities call in the DomainService.
SELECT tblTime.Period As Timeline, COUNT(tblEngineeringDashboard_ItemList.ID) AS Items
FROM tblEngineeringDashboard_ItemList INNER JOIN
tblTime ON tblEngineeringDashboard_ItemList.TimeID = tblTime.ID
GROUP BY tblTime.Period
ORDER BY tblTime.Period
Can anyone provide help.
Possible Solution
Dim var = From i In ObjectContext.tblEngineeringDashboard_ItemList
Join t In ObjectContext.tblTimes On i.TimeID Equals t.ID
Group By i.TimeID Into Group
Select DateStart = (From n In ObjectContext.tblTimes Where n.ID = TimeID Select n.Period), PartCount = Group.Count
Phil
The first thing which comes to mind is:
var q = from t in Context.Time
group t by t.Period into g
orderby g.Key
select new
{
Timeline = g.Key,
Items = (from ti in g
from il in ti.ItemList // or whatever the property for the navigation to tblEngineeringDashboard_ItemList is called
select il).Count()
};
However, the original SQL had an INNER JOIN, which would reject tblTime records without any matching records in tblEngineeringDashboard_ItemList. So you may want:
var q = from t in Context.Time
where t.ItemList.Any()
group t by t.Period into g
orderby g.Key
select new
{
Timeline = g.Key,
Items = (from ti in g
from il in ti.ItemList // or whatever the property for the navigation to tblEngineeringDashboard_ItemList is called
select il).Count()
};
You can also flip the query around:
var q = from i in Context.EngineeringDashboardItemList
where i.Time != null
group i by i.Time.Period into g
orderby g.Key
select new
{
Timeline = g.Key,
Items = g.Count()
};
Does this work?
tblEngineeringDashboard_ItemList
.Join(tblTime,ed => ed.TimeID ,t => t.ID, (ed,t) => new{ed,t})
.GoupBy(g => g.t.Period)
.Select(s => new
{
Timeline = s.Key,
Items = s.Count()
}
)
.OrderBy(o => o.Timeline)
While converting from sql to linq isn't an ideal approach (you should think directly in linq, translating your need to a linq query), the query you posted is rather simple.
var grouped = tblTime.OrderBy(c => c.Period).GroupBy(c => c.Period).Select(c =>
new {
timeline = c.Key,
count = c.SelectMany(x => x.tblEngineeringDashboard).Count()
});
*Edit: There, fixed. Everything on the L2E engine.
This provided that there are correct foreign keys between the tables (thus you don't have to declare the join manually).