I have class Request with list of Comments. Each Request can have zero, one or many Comments.
public class Request
{
public virtual string Id
{ get; protected set; }
public virtual DateTime Date
{ get; set; }
public virtual byte RequestStatusId
{ get; set; }
public virtual Payment Payment
{ get; set; }
public virtual IList<RequestComment> RequestComments
{ get; set; }
}
public class RequestComment
{
public virtual int Id
{ get; protected set; }
public virtual DateTime Date
{ get; set; }
public virtual string CommentText
{ get; set; }
public virtual Request Request
{ get; set; }
public virtual User User
{ get; set; }
}
I'm using NHibernate.Linq to get data from database.
When I sort, for example by Request Id, it looks like this:
var query = _session.Query<Request>()
.Where(r => r.RequestStatusId == requestStatusId)
.OrderBy(r => r.Id)
.Skip(pageNo * pageSize)
.Take(pageSize);
return query.ToFuture().AsQueryable();
When I need last comment for Request I get it like this:
public RequestComment GetLastCommentForRequest(string requestId)
{
var query = _session.Query<RequestComment>()
.Where(r => r.Request.Id == requestId)
.OrderByDescending(r => r.Date)
.FirstOrDefault();
return query;
}
Now I need to get Requests, with their last comment (if exists) and all sorted by CommentText. I was trying to do order in Request query with:
.OrderBy(x => x.RequestComments.Where(y => y.Request.Id == x.Id).OrderByDescending(y => y.Date).FirstOrDefault())
But it's not working and I'm getting error »Exception has been thrown by the target of an invocation.«
UPDATE
This is ok, but it's not sorted by last comment, but by first found:
.OrderBy(r => r.RequestComments.Select(x => x.CommentText).FirstOrDefault())
How about this :
.OrderBy(x => x.RequestComments
.OrderByDescending(y => y.Date)
.Select(x => x.CommentText)
.FirstOrDefault()
)
UPDATE :
It seems that linq above translated to query with subquery having order by clause which is not allowed. Try this instead :
.OrderBy(x => x.RequestComments
.Where(y => y.Date == x.RequestComments.Max(o => o.Date))
.Select(u => u.CommentText)
.FirstOrDefault()
)
You could try:
.OrderBy(x => x.RequestComments.Select(y => y.Date).DefaultIfEmpty().Max())
Related
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 the following models
public class Person
{
public int PersonId { get; set; }
public string Name { get; set; }
public List<PersonRole> PersonRoles { get; set; }
}
public class RoleInDuty
{
public int roleInDutyId { get; set; }
public string Name { get; set; }
public int typeOfDutyId { get; set; }
public TypeOfDuty typeOfDuty { get; set; }
public List<PersonRole> PersonRoles { get; set; }
}
public class PersonRole
{
public int PersonId { get; set; }
public Person Person { get; set; }
public int RoleInDutyId { get; set; }
public RoleInDuty RoleInDuty { get; set; }
}
And now I can load all people with all their roles using the following code:
var people = _context.Persons
.Include(p => p.PersonRoles)
.ThenInclude(e => e.RoleInDuty).ToList();
But I wantn't load all data to List, I need load PersonRole according entered typeOfDutyId.
I am trying to solve this with the following code
people = _context.Persons
.Include(p => p.PersonRoles
.Where(t=>t.RoleInDuty.typeOfDutyId == Id)).ToList();
But VS throw error
InvalidOperationException: The Include property lambda expression 'p
=> {from PersonRole t in p.PersonRoles where ([t].RoleInDuty.typeOfDutyId == __typeOfDuty_typeOfDutyId_0) select
[t]}' is invalid. The expression should represent a property access:
't => t.MyProperty'. To target navigations declared on derived types,
specify an explicitly typed lambda parameter of the target type, E.g.
'(Derived d) => d.MyProperty'. For more information on including
related data, see http://go.microsoft.com/fwlink/?LinkID=746393.
As I understand I can't access property RoleInDuty.typeOfDutyId because i'm not include it yet.
I solved this problem with the following code
people = _context.Persons
.Include(p => p.PersonRoles)
.ThenInclude(e=>e.RoleInDuty).ToList();
foreach (Person p in people)
{
p.PersonRoles = p.PersonRoles
.Where(e => e.RoleInDuty.typeOfDutyId == Id)
.ToList();
}
Finally, filter in Include with ef core 5. Details in MSDN doc: https://learn.microsoft.com/en-us/ef/core/querying/related-data#filtered-include
Var people = _context.Persons
.Include(p => p.PersonRoles
.Where(t=>t.RoleInDuty.typeOfDutyId == Id))
.ToList();
var blogs = context.Blogs
.Include(e => e.Posts.OrderByDescending(post => post.Title).Take(5)))
.ToList();
devnull show the next How to filter "Include" entities in entity framework?, and there the same problem, I read it, and find the answer. Solve my problem can with the next:
var temp = _context.Persons.Select(s => new
{
Person = s,
PersonRoles= s.PersonRoles
.Where(p => p.RoleInDuty.typeOfDutyId == this.typeOfDuty.typeOfDutyId)
.ToList()
}).ToList();
I have a DB structure which is not ideal, but I have coded it into EF like this:
[Table("Item")]
public class Item
{
[Key] public int Id { get; set; }
public int CategoryId { get; set; }
public int ItemTypeId { get; set; } // An ItemTypeId of 1 means this row refers to an Article
public int ItemId { get; set; } // this refers to the Article primary key
}
[Table("Article")]
public class Article
{
[Key] public int Id { get; set; }
...
public virtual ICollection<SubArticle> SubArticles { get; set; }
}
[Table("SubArticle")]
public class SubArticle
{
...
public int ArticleId { get; set; }
}
modelBuilder.Entity<Article>().Collection(_ => _.SubArticles).InverseReference(_ => _.Article).ForeignKey(_ => _.ArticleId);
What I want to do is get all articles (with the corresponding sub-articles) that belong to a specific category. I have this query which is working:
var result = await Context.Set<Item>()
.Where(i => i.CategoryId == 200)
.Where(i => i.ItemTypeId == 1)
.Join(
Context.Set<Article>().Include(a => a.SubArticles),
i => i.ItemId,
a => a.Id,
(i,a) => a)
.ToListAsync();
result.SubArticles.First(); // works
My question is why does this query not work:
var result = await Context.Set<Item>()
.Where(i => i.CategoryId == 200)
.Where(i => i.ItemTypeId == 1)
.Join(
Context.Set<Article>(),
i => i.ItemId,
a => a.Id,
(i,a) => a)
.Include(a => a.SubArticles)
.ToListAsync();
result.SubArticles.First(); // error: result.SubArticles is null
I have a table called InvestigatorGroup and a table called InvestigatorGroupUsers which is used to see what groups have what users. I am trying to get the common investigator group between two users
My query is as follows:
public InvestigatorGroup GetCommonGroup(string userId, string investigatorUserId)
{
using (GameDbContext entityContext = new GameDbContext())
{
string[] ids = new[] { userId, investigatorUserId };
return entityContext.InvestigatorGroups
.Where(i => i.IsTrashed == false)
.Include(i => i.InvestigatorGroupUsers)
.Where(i => i.InvestigatorGroupUsers.Any(e => ids.Contains(e.UserId)))
.OrderByDescending(i => i.InvestigatorGroupId)
.GroupBy(i => i.InvestigatorGroupId)
.Where(i => i.Count() > 1)
.SelectMany(group => group).FirstOrDefault();
}
}
The entity InvestigatorGroup is as follows:
public class InvestigatorGroup : IIdentifiableEntity
{
public InvestigatorGroup()
{
this.InvestigatorGroupGames = new HashSet<InvestigatorGroupGame>();
this.InvestigatorGroupUsers = new HashSet<InvestigatorGroupUser>();
}
// Primary key
public int InvestigatorGroupId { get; set; }
public string InvestigatorGroupName { get; set; }
public bool HasGameAssignment { get; set; }
public string GroupRoleName { get; set; }
public bool IsTrashed { get; set; }
// Navigation property
public virtual ICollection<InvestigatorGroupUser> InvestigatorGroupUsers { get; private set; }
public virtual ICollection<InvestigatorGroupGame> InvestigatorGroupGames { get; private set; }
public int EntityId
{
get { return InvestigatorGroupId; }
set { InvestigatorGroupId = value; }
}
}
The problem is that it keeps returning a value of 0. It doesn't see the shared group with a count of 2 between the two users.
I did a test to return the groups (I removed the count>1 condition) and it returned all the groups for both users not only the one they have in common
I believe the issue is with this line: .Where(i => i.InvestigatorGroupUsers.Any(e => ids.Contains(e.UserId)))
Thanks for the help!
I've resolved this by changing my query so that it searches for the rows containing one of the UserId's. Then it queries through those selected rows and selects the ones containing the other UserId (InvestigatorUserId). This way only the rows containing both are returned
My new code is as follows:
public InvestigatorGroup GetCommonGroup(string userId, string investigatorUserId)
{
using (GameDbContext entityContext = new GameDbContext())
{
IEnumerable<InvestigatorGroup> userGroups = entityContext.InvestigatorGroups
.Where(i => i.IsTrashed == false)
.Include(i => i.InvestigatorGroupUsers)
.Where(i => i.InvestigatorGroupUsers.Any(e => e.UserId.Contains(userId)))
.OrderByDescending(i => i.InvestigatorGroupId);
return userGroups.Where(i => i.InvestigatorGroupUsers.Any(e => e.UserId.Contains(investigatorUserId))).FirstOrDefault();
}
}
I want to take the last 4 elements from a groupby and then add it to a List. I'm using MySQL as database and asp MVC5. I tried this:
List<Payments> payments= db.Payments
.Where(e => e.Account.Active==true)
.GroupBy(e => e.IdAccount)
.Select(e => e.OrderBy(x => x.PaidDate).Take(4))
.SelectMany(e => e).ToList();
I got the following error:
Unknown column 'Join2.IdAccount' in 'where clause'
This is the Payment class:
public partial class Payment
{
public int Id { get; set; }
[ForeignKey("Account")]
[DisplayName("ID Account")]
public int IdAccount { get; set; }
[ForeignKey("Client")]
[DisplayName("ID Client")]
public int IdClient { get; set; }
public Nullable<decimal> Amount { get; set; }
[DisplayName("Paid date")]
public System.DateTime PaidDate { get; set; }
public virtual Account Account { get; set; }
public virtual Client Client { get; set; }
}
Could be that it's MySQL provider (SQL expression creator) error. But, have you tried this way?
List<Payments> payments = db.Payments
.Where(e => e.Account.Active == true)
.GroupBy(e => e.IdAccount)
.Select(e => e.OrderBy(x => x.PaidDate))
.Take(4)
.SelectMany(e => e).ToList();
If this doesn't help you, you can always pass all query to code this way:
List<Payments> payments = db.Payments
.Where(e => e.Account.Active == true)
.GroupBy(e => e.IdAccount)
.ToList() // From here all calculations will be on web server, not in SQL. So sql query will be simlier
.Select(e => e.OrderBy(x => x.PaidDate))
.Take(4)
.SelectMany(e => e).ToList();
But, it will affect perfomance.