how to write distinct count query in Linq - entity-framework

suppose i have a following query in Sql
select UserID,count(distinct ip) as NumberOfIPUsed from UserLogs
group by UserID
i want this to write with Linq

Well, first you're going to want to group by UserID:
UserLogs.GroupBy(ul => ul.UserID)
Then you want to get the UserID and the count of distinct ip from that:
UserLogs.GroupBy(ul => ul.UserID).Select(g => new {UserID = g.Key, Count = g.Select(ul => ul.ip).Distinct().Count()})

You need to use the Distinct function which isn't available directly in comprehension expressions, so some functional style is required:
var res = await (from ul in context.UserLogs
group ul by ul.UserId into grouped
select new {
UserId = grouped.Key,
Count = group.Select(x => x.ip).Distinct().Count()
}).ToListAsync();

Related

Take each first element of the group by

How can I take each first element in the group with EF 5 ?
var result = await context.SomeDbSet
.Where(...)
.GroupBy(x => new { x.SomeField, ... })
.Select(x => x.First())
.ToListAsync();
I am getting not supported exception.
How to correctly rewrite query? Thanks.
You cannot do that with grouping. SQL has a limitation - with GROUP BY you can select only grouping keys and aggregation result. This limitation for sure extended to LINQ to Entities - after GroupBy you can select only grouping keys and aggregation result.
Such result can be achieved by SQL and Window functions:
SELECT
r.*,
FROM
(
SELECT
s.*,
ROW_NUMBER() OVER(PARTITION BY s.SomeField1, s.SomeField2 ORDER BY s.SomeDate) AS RN
FROM SomeDbSet s
WHERE ...
) r
WHERE r.RN = 1
For those who want to stay with LINQ, I propose extension (disclaimer: I'm extension creator) linq2db.EntityFrameworkCore
And you can write query above via LINQ
var rnQuery =
from s in context.SomeDbSet
where ...
select new
{
Data = s,
RN = Sql.Ext.RowNumber().Over()
.PartitionBy(s.SomeField1, s.SomeField2)
.OrderBy(s.SomeDate)
.ToValue()
}
var resultQuery = await rnQuery
.Where(r => r.RN == 1)
.Select(r => r.Data)
.ToLinqToDB();
var result = resultQuery.ToList();
// async variant may need ToListAsyncLinqToDB() call
// because of collision in async extension methods between EF Core and linq2db
var result = await resultQuery.ToListAsyncLinqToDB();

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

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?

.Include in following query does not include really

var diaryEntries = (from entry in repository.GetQuery<OnlineDiary.Internal.Model.DiaryEntry>()
.Include("DiaryEntryGradeChangeLog")
.Include("DiaryEntryAction")
join diary in repository.GetQuery<OnlineDiary.Internal.Model.OnlineDiary>()
on entry.DiaryId equals diary.Id
group entry
by diary
into diaryEntriesGroup
select new { Diary = diaryEntriesGroup.Key,
DiaryEntry = diaryEntriesGroup.OrderByDescending(diaryEntry => diaryEntry.DateModified).FirstOrDefault(),
});
This query does not include "DiaryEntryGradeChangeLog" and "DiaryEntryAction" navigation properties, what is wrong in this query?
I have removed join from the query and corrected as per below, and still it populates nothing
var diaryEntries = from entry in repository.GetQuery<OnlineDiary.Internal.Model.DiaryEntry>()
.Include("DiaryEntryGradeChangeLog").Include("DiaryEntryAction")
.Where(e => 1 == 1)
group entry
by entry.OnlineDiary
into diaryEntryGroups
select
new { DiaryEntry = diaryEntryGroups.OrderByDescending(diaryEntry => diaryEntry.DateModified).FirstOrDefault() };
It will not. Include works only if the shape of the query does not change (by design). If you use this query it will work because the shape of the query is still same (OnlineDiary.Internal.Model.DiaryEntry):
var diaryEntries = (from entry in repository.GetQuery<OnlineDiary.Internal.Model.DiaryEntry>()
.Include("DiaryEntryGradeChangeLog")
.Include("DiaryEntryAction");
But once you use manual join, grouping or projection (select new { }) you have changed the shape of the query and all Include calls are skipped.
Edit:
You must use something like this (untested) to get related data:
var diaryEntries = from entry in repository.GetQuery<OnlineDiary.Internal.Model.DiaryEntry>()
group entry by entry.OnlineDiary into diaryEntryGroups
let data = diaryEntryGroups.OrderByDescending(diaryEntry => diaryEntry.DateModified).FirstOrDefault()
select new {
DiaryEntry = data,
GradeChangeLog = data.DiaryEntryGradeChangeLog,
Action = data.DiaryEntryAction
};
or any similar query where you manually populate property for relation in projection to anonymous or unmapped type.

EntityFramework - how to get rows that match any of the IDS from another table where the userId matches?

I'm not sure how to get the rows I need in EF. In SQL it would look something like this:
SELECT * FROM [Recipes]
JOIN [UserFavorites] ON [UserFavorites].[RecipeId] = [Recipes].[Id]
WHERE [UserFavorites].[UserId] = #UserId
I know how to get the userfavorites that match the user id like this:
db.UserFavorites.Where(x => x.UserId == userId
But then how do I get all of the recipes that match the recipeIds inside of those userfavorites?
You can either use LINQ and construct a query:
var recipes = from r in db.Recipes
join f in db.UserFavorites on r.Id equals f.RecipeId
where f.UserId = userId
select r
or you can use the lambda syntax with navigation properties, assuming you have them set up for the relationships in question
var recipes = db.Recipes.Where(r => r.UserFavorites.Any(f => f.UserId == userId));
You can, of course, construct the actual query that's described in the first section using the equivalent lambda syntax (since the query syntax is just a language feature that compiles down to the equivalent calls to the extension methods using anonymous delegates), but that tends to be a little more difficult to read.
How about:
var recipes = context.Recipes
.SelectMany(r => r.Users, (r, u) => new { Recipe = r, User = u }
.Where(o => o.User.UserId = userId)
.Select(o => o.Recipe);
This implies UserFavorites is the intersection table between Recipes and Users
Recipes >- UserFavorites -< Users

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).