I have this domain:
public class User
{
public long Id { get; set; }
public string Login { get; set; }
public string Password { get; set; }
}
public class Application
{
public long Id { get; set; }
public string Name { get; set; }
}
public class UserApplications
{
[ForeignKey("User")]
public long UserId { get; set; }
public User User { get; set; }
[ForeignKey("Application")]
public long ApplicationId { get; set; }
public Application Application { get; set; }
public DateTime LastConnection { get; set; }
}
I want to make a select that returns something like that:
List of select new
{
User = user,
Applications = applications // List of all user's applications
}
I try:
from u in Users
join ua in UserApplications on u.Id equals ua.UserId into userApplications
from ua in userApplications.DefaultIfEmpty()
join a in Applications on ua.ApplicationId equals a.Id into applications
select new
{
User = u,
Applications = applications
}
But this repeats the user to each application.
I know that I can do that in two select statements, but I dont want that.
How can I do that?
I do not remember if Entity Frameworks could do groupby based on the entity object itself (and extract it's Id behind the scene and replace things as it fits and the like); but this code works for this case:
var q = from uapp in cntxt.UserApplications
group uapp by uapp.UserId
into g
select new { UserId = g.Key, Applications = g.Select(x => x.Application) };
And if you are willing to have User already extracted:
var q2 = from uapp in cntxt.UserApplications
group uapp by uapp.UserId
into g
let u = Users.First(x => x.Id == g.Key)
select new { User = u, Applications = g.Select(x => x.Application) };
Assuming you are writing a query against an Entity Framework Context - and not just trying to do a Linq to Objects query.
Actually, you just have to group UserApplications entity set by user:
context
.UserApplications
.GroupBy(_ => _.User, _ => _.Application)
.ToList();
because, in fact, IGrouping<User, Application> is what you need (Key is the user, and group items are his applications).
Any other improvements are matter of taste, like projection to anonymous type:
context
.UserApplications
.GroupBy(_ => _.User, _ => _.Application)
.Select(_ => new
{
User = _.Key,
// since IGrouping<User, Application> is IEnumerable<Application>,
// we colud return a grouping directly
Applications = _
})
.ToList();
(another projection option throws away group key in Applications):
context
.UserApplications
.GroupBy(_ => _.User, _ => _.Application)
.Select(_ => new
{
User = _.Key,
Applications = _.Select(app => app)
})
.ToList();
Try this:
var tmp =
from u in Users
join ua in UserApplications on u.Id equals ua.UserId
join a in Applications on ua.ApplicationId equals a.Id
select new
{
User = u,
App = a
};
var res = tmp
.ToArray() // edited
.GroupBy(_ => _.User)
.Select(_ => new
{
User = _.Key,
Applications = _.Select(_ => _.App).ToArray()
});
Related
I have this method for my API where I'm trying to filter out a user (userId) and group by EventId. Then I want to select three columns from the table: name, eventId and UserId.
This is my code but it's not working:
[HttpGet("[action]/{userId}")]
public IActionResult EventsMatchFiltered(int userId)
{
var events = _dbContext.Events.Where(event1 => event1.UserId != userId)
.GroupBy(g => new {g.EventId })
.SelectMany(g => g.Select(h => new
{
Name = h.Name,
h.EventId,
h.UserId
}));
return Ok(events);
}
Firstly, events would be an IQueryable rather than a materialized collection. Second, it would be an anonymous type rather than a proper DTO/ViewModel serializable class. Thirdly, you are grouping by an EventId, but then not "selecting" anything based on that grouping. EventId sounds like the Primary Key of the Event you are supposedly grouping.. Are you sure you don't actually mean to Group by User??
Normally for something like a Grouped result you would have a view model something like:
[Serializable]
public class UserEventSummaryViewModel
{
public int UserId { get; set; }
public ICollection<EventSummaryViewModel> Events { get; set; } = new List<EventSummaryViewModel>();
}
[Serializable]
public class EventSummaryViewModel
{
public int EventId { get; set; }
public string Name { get; set; }
}
then to query and fill:
var otherUserEvents = _dbContext.Events
.Where(e => e.UserId != userId)
.GroupBy(g => g.UserId )
.Select(g => new UserEventSummaryViewModel
{
UserId == g.Key,
Events = g.Select(e => new EventSummaryViewModel
{
EventId = e.EventId,
Name = e.Name
}).ToList()
}).ToList();
This would provide a structure of events grouped by each of the other Users. If you want UserId and UserName then add the UserName property to the UserEventSummary view model and in the GroupBy clause:
.GroupBy(g => new { g.UserId, g.Name } )
and populate these in the viewmodel with:
UserId == g.Key.UserId,
UserName = g.Key.Name,
I have SQL query like this
SELECT T.*
FROM
(
SELECT ServiceRecords.DistrictId, Districts.Name as DistrictName, COUNT(Distinct(NsepServiceRecords.ClientRegNo)) AS ClientsServedCount
FROM ServiceRecords
INNER JOIN Districts ON ServiceRecords.DistrictId = Districts.ID
INNER JOIN NsepServiceRecords ON NsepServiceRecords.ServiceRecordId = ServiceRecords.Id
WHERE ServiceRecords.CreatedAtUtc >= #StartDate
AND ServiceRecords.CreatedAtUtc <= #EndDate
AND ServiceRecords.DistrictId = #DistrictId
GROUP BY ServiceRecords.DistrictId, Districts.Name
) AS T
ORDER BY T.DistrictName ASC, T.DistrictId
Query results:
DistrictId DistrictName ClientsServedCount
8d059005-1e6b-44ad-bc2c-0b3264fb4567 Bahawalpur 117
27ab6e24-50a6-4722-8115-dc31cd3127fa Gujrat 492
14b648f3-4912-450e-81f9-bf630a3dfc72 Jhelum 214
8c602b99-3308-45b5-808b-3375d61fdca0 Lodhran 23
059ffbea-7787-43e8-bd97-cab7cb77f6f6 Muzafarghar 22
580ee42b-3516-4546-841c-0bd8cef04df9 Peshawar 211
I'm struggling converting this to LINQ to entities query. I want to get same results (except District Id column) using LINQ.
I have tried like this, but not working as expected. Can somebody tell me what I'm doing wrong?
_dbContext.ServiceRecords
.Include(x => x.District)
.Include(x=>x.NsepServiceRecords)
.GroupBy(x => x.DistrictId)
.Select(x => new DistrictClientsLookUpModel
{
DistrictName = x.Select(record => record.District.Name).FirstOrDefault(),
ClientsServedCount = x.Sum(t=> t.NsepServiceRecords.Count)
});
Model classes are like this
public class BaseEntity
{
public Guid Id { get; set; }
}
public class NsepServiceRecord : BaseEntity
{
public DateTime CreatedAtUtc { get; set; }
public Guid ServiceRecordId { get; set; }
public string ClientRegNo { get; set; }
// other prop .......
public virtual ServiceRecord ServiceRecord { get; set; }
}
public class ServiceRecord : BaseEntity
{
public DateTime CreatedAtUtc { get; set; }
public string DistrictId { get; set; }
public virtual District District { get; set; }
public virtual ICollection<NsepServiceRecord> NsepServiceRecords { get; set; }
}
public class DistrictClientsLookUpModel
{
public string DistrictName { get; set; }
public int ClientsServedCount { get; set; }
}
I'm using Microsoft.EntityFrameworkCore, Version 2.2.4
EDIT
I have also tried like this
var startUniversalTime = DateTime.SpecifyKind(request.StartDate, DateTimeKind.Utc);
var endUniversalTime = DateTime.SpecifyKind(request.EndDate, DateTimeKind.Utc);
return _dbContext.NsepServiceRecords
.Join(_dbContext.ServiceRecords, s => s.ServiceRecordId,
r => r.Id, (s, r) => r)
.Include(i => i.District)
.Where(x => x.DistrictId == request.DistrictId
&& x.CreatedAtUtc.Date >= startUniversalTime
&& x.CreatedAtUtc.Date <= endUniversalTime)
.OrderBy(x => x.DistrictId)
.GroupBy(result => result.DistrictId)
.Select(r => new DistrictClientsLookUpModel
{
DistrictName = r.Select(x=>x.District.Name).FirstOrDefault(),
ClientsServedCount = r.Sum(x=>x.NsepServiceRecords.Count())
});
Another try,
from s in _dbContext.ServiceRecords
join record in _dbContext.NsepServiceRecords on s.Id equals record.ServiceRecordId
join district in _dbContext.Districts on s.DistrictId equals district.Id
group s by new
{
s.DistrictId,
s.District.Name
}
into grp
select new DistrictClientsLookUpModel
{
DistrictName = grp.Key.Name,
ClientsServedCount = grp.Sum(x => x.NsepServiceRecords.Count)
};
It takes too long, I waited for two minutes before I killed the request.
UPDATE
EF core have issues translating GroupBy queries to server side
Assuming the District has a collection navigation property to ServiceRecord as it should, e.g. something like
public virtual ICollection<ServiceRecord> ServiceRecords { get; set; }
you can avoid the GroupBy by simply starting the query from District and use simple projection Select following the navigations:
var query = _dbContext.Districts
.Select(d => new DistrictClientsLookUpModel
{
DistrictName = d.Name,
ClientsServedCount = d.ServiceRecords
.Where(s => s.CreatedAtUtc >= startUniversalTime && s.CreatedAtUtc <= endUniversalTime)
.SelectMany(s => s.NsepServiceRecords)
.Select(r => r.ClientRegNo).Distinct().Count()
});
You don't appear to be doing a join properly.
Have a look at this:
Join/Where with LINQ and Lambda
Here is a start on the linq query, I'm not sure if this will give you quite what you want, but its a good start.
Basically within the .Join method you need to first supply the entity that will be joined. Then you need to decide on what they will be joined on, in this case district=> district.Id, serviceRecord=> serviceRecord.Id.
_dbContext.ServiceRecords
.Join( _dbContext.District,district=> district.Id, serviceRecord=> serviceRecord.Id)
.Join(_dbContext.NsepServiceRecords, Nsep=> Nsep.ServiceRecord.Id,district=>district.Id)
.GroupBy(x => x.DistrictId)
.Select(x => new DistrictClientsLookUpModel
{
DistrictName = x.Select(record => record.District.Name).FirstOrDefault(),
ClientsServedCount = x.Sum(t=> t.NsepServiceRecords.Count)
});
I have 3 classes and trying to use LINQ methods to perform an INNER JOIN and a LEFT JOIN. I'm able to perform each separately, but no luck together since I can't even figure out the syntax.
Ultimately, the SQL I'd write would be:
SELECT *
FROM [Group] AS [g]
INNER JOIN [Section] AS [s] ON [s].[GroupId] = [g].[Id]
LEFT OUTER JOIN [Course] AS [c] ON [c].[SectionId] = [s].[Id]
Classes
public class Group {
public int Id { get; set; }
public int Name { get; set; }
public bool IsActive { get; set; }
public ICollection<Section> Sections { get; set; }
}
public class Section {
public int Id { get; set; }
public int Name { get; set; }
public int GroupId { get; set; }
public Group Group { get; set; }
public bool IsActive { get; set; }
public ICollection<Course> Courses { get; set; }
}
public class Course {
public int Id { get; set; }
public int UserId { get; set; }
public int Name { get; set; }
public int SectionId { get; set; }
public bool IsActive { get; set; }
}
Samples
I want the result to be of type Group. I successfully performed the LEFT JOIN between Section and Course, but then I have an object of type IQueryable<a>, which is not what I want, sinceGroup`.
var result = db.Section
.GroupJoin(db.Course,
s => s.Id,
c => c.SectionId,
(s, c) => new { s, c = c.DefaultIfEmpty() })
.SelectMany(s => s.c.Select(c => new { s = s.s, c }));
I also tried this, but returns NULL because this performs an INNER JOIN on all tables, and the user has not entered any Courses.
var result = db.Groups
.Where(g => g.IsActive)
.Include(g => g.Sections)
.Include(g => g.Sections.Select(s => s.Courses))
.Where(g => g.Sections.Any(s => s.IsActive && s.Courses.Any(c => c.UserId == _userId && c.IsActive)))
.ToList();
Question
How can I perform an INNER and a LEFT JOIN with the least number of calls to the database and get a result of type Group?
Desired Result
I would like to have 1 object of type Group, but only as long as a Group has a Section. I also want to return the Courses the user has for the specific Section or return NULL.
I think what you ask for is impossible without returning a new (anonymous) object instead of Group (as demonstrated in this answer). EF will not allow you to get a filtered Course collection inside a Section because of the way relations and entity caching works, which means you can't use navigational properties for this task.
First of all, you want to have control over which related entities are loaded, so I suggest to enable lazy loading by marking the Sections and Courses collection properties as virtual in your entities (unless you've enabled lazy loading for all entities in your application) as we don't want EF to load related Sections and Courses as it would load all courses for each user anyway.
public class Group {
public int Id { get; set; }
public int Name { get; set; }
public bool IsActive { get; set; }
public virtual ICollection<Section> Sections { get; set; }
}
public class Section {
public int Id { get; set; }
public int Name { get; set; }
public int GroupId { get; set; }
public Group Group { get; set; }
public bool IsActive { get; set; }
public virtual ICollection<Course> Courses { get; set; }
}
In method syntax, the query would probably look something like this:
var results = db.Group
.Where(g => g.IsActive)
.GroupJoin(
db.Section.Where(s => s.IsActive),
g => g.Id,
s => s.GroupId,
(g, s) => new
{
Group = g,
UserSections = s
.GroupJoin(
db.Course.Where(c => c.IsActive && c.UserId == _userId).DefaultIfEmpty(),
ss => ss.Id,
cc => cc.SectionId,
(ss, cc) => new
{
Section = ss,
UserCourses = cc
}
)
})
.ToList();
And you would consume the result as:
foreach (var result in results)
{
var group = result.Group;
foreach (var userSection in result.UserSections)
{
var section = userSection.Section;
var userCourses = userSection.UserCourses;
}
}
Now, if you don't need additional filtering of the group results on database level, you can as well go for the INNER JOIN and LEFT OUTER JOIN approach by using this LINQ query and do the grouping in-memory:
var results = db.Group
.Where(g => g.IsActive)
.Join(
db.Section.Where(s => s.IsActive),
g => g.Id,
s => s.GroupId,
(g, s) => new
{
Group = g,
UserSection = new
{
Section = s,
UserCourses = db.Course.Where(c => c.IsActive && c.UserId == _userId && c.SectionId == s.Id).DefaultIfEmpty()
}
})
.ToList() // Data gets fetched from database at this point
.GroupBy(x => x.Group) // In-memory grouping
.Select(x => new
{
Group = x.Key,
UserSections = x.Select(us => new
{
Section = us.UserSection,
UserCourses = us.UserSection.UserCourses
})
});
Remember, whenever you're trying to access group.Sections or section.Courses, you will trigger the lazy loading which will fetch all child section or courses, regardless of _userId.
Use DefaultIfEmpty to perform an outer left join
from g in db.group
join s in db.section on g.Id equals s.GroupId
join c in db.course on c.SectionId equals s.Id into courseGroup
from cg in courseGroup.DefaultIfEmpty()
select new { g, s, c };
Your SQL's type is not [Group] (Type group would be: select [Group].* from ...), anyway if you want it like that, then in its simple form it would be:
var result = db.Groups.Where( g => g.Sections.Any() );
However, if you really wanted to convert your SQL, then:
var result = from g in db.Groups
from s in g.Sections
from c in s.Courses.DefaultIfEmpty()
select new {...};
Even this would do:
var result = from g in db.Groups
select new {...};
Hint: In a well designed database with relations, you very rarely need to use join keyword. Instead use navigational properties.
i have a question, i want to create a linq query that returns a list of object.
This is the model
public class Test
{
[Key]
public int ID { get; set; }
[Required]
[StringLength(5)]
public string Code { get; set; }
[Required]
[StringLength(50)]
public string Name { get; set; }
[NotMapped]
public string Reference { get; set; }
}
The query that i want to do is simple: context.Test.ToList();
this returns the database mapping Reference is null since is not part of the table.
Now if i create a linq query i know that i can do select new { all fields here }
i want to avoid this:
select new Test
{
Reference = r,
ID = t.ID,
Code = t.Code,
Name = t.Name
}).ToList();
is it possible to do something like this
(from t in context.Test
join r in context.Reference on f.ID equals r.ID
select new
{
t.Reference = r.Reference,
t
}).ToList();
i want to set the Reference value inside the same query, is that possible?
What are you asking is not directly supported in LINQ to Entities - neither projection to entity type, nor expression block which is the only way to assign properties of an existing object.
As usual, the typical workaround is to split the query on two parts - one being LINQ to Entities query selecting the necessary data (usually into intermediate anonymous type), then switch to LINQ to Objects with AsEnumerable() and do the rest - in this case using block inside Select:
var result =
(from t in context.Test
join r in context.Reference on f.ID equals r.ID
select new { t, r.Reference }
).AsEnumerable()
.Select(x =>
{
x.t.Reference = x.Reference;
return x.t;
}).ToList();
Don't select an anonymous object, just create a new T from the one you have.
(from t in context.Test
join r in context.Reference on t.ID equals r.ID
select new Test
{
Reference = r,
ID = t.ID,
Code = t.Code,
Name = t.Name
}).ToList();
EDIT:
To avoid having to manually copy over all the properties
public class Test
{
public int ID { get; set; }
public string Code { get; set; }
public string Name { get; set; }
public string Reference { get; set; }
public Test CopyWithReference(string reference)
{
var copy = (Test)this.MemberwiseClone();
copy.Reference = reference;
return copy;
}
}
Then...
(from t in context.Test
join r in context.Reference on t.ID equals r.ID
select t.CopyWithReference(r)).ToList();
Try following :
(from t in context.Test
join r in context.Reference on f.ID equals r.ID
select new Test()
{
ID = t.ID,
Code = t.Code,
Name = t.Name,
Reference = r.Reference
}).ToList();
Try:
var result = context.Test.Include("Reference").ToList();
or:
var result = context.Test.Include(t => t.Reference).ToList();
or Try Lambda Expressions:
var result = context.Test.Select(t => new {
t,
t.Reference = t.Reference.Select(r => new {
r.Reference })
}).AsEnumerable().Select(x => x.r).ToList();
I want to filter attachment when get a billing:
var billing = db.Billings
.Include(b => b.Client)
.Include(b => b.Attachments.Where(a => a.WorkflowStateID == workflowStateID))
.Where(b => b.BillingID == id)
.FirstOrDefault();
Billing Entity:
public partial class Billing
{
public Billing()
{
this.Attachments = new HashSet<Attachment>();
}
public long BillingID { get; set; }
public int ClientID { get; set; }
public virtual ICollection<Attachment> Attachments { get; set; }
public virtual Client Client { get; set; }
}
but it gives an error
The Include path expression must refer to a navigation property defined on the type.
Use dotted paths for reference navigation properties and the Select operator for
collection navigation properties
How to use where clause on include?
What I want to achieve is if I translate in query sql:
select *
from Billing b
inner join Client c on b.ClientID = c.ClientID
inner join (select * from Attachment a where a.WorkflowStateID = #workflowStateID) t on b.BillingID = t.BillingID
where b.BillingID = #billingID
As stated it is not allowed to use Where inside Include method. As I know it is not possible to filter navigation properties like that. What you could do is using projection
var billing = db.Billings
.Where(b => b.BillingID == id)
.Select(b => new {
Billing = b,
BillingClient = b.Client
FilteredAttachments = b.Attachments.Where(a => a.WorkflowStateID == workflowStateID)
})
.FirstOrDefault();