I am having difficulty with using Linq against a dbContext, getting the counts of objects where collections of child object of child objects have particular highest values.
I have the classes Request, Event, User. My simplified object graph is like this:-
Request Event User
| | |-> Username
|-> EventsCollection |-> Description
|-> User |-> WhenDone
I'm sure the domain model could be altered but regardless, using the current structure
what I want is the count of the number of requests by a particular user where the most recent event has a particular description.
I initially thought something like the following would give me the value I am looking for
value = _db.Requests.Where(r => r.User.UserName.Equals("username") && r.Events.OrderByDescending(e => e.WhenDone).First().Description.Equals( "description")).Count();
However on running this I get an error 'First' can only be used as a final query operation changing it I get other errors such as unsupported method etc.
I'm guessing I ought to use sub queries but haven't been able to get the syntax right.
Any pointers to useful sources or suggested solutions would be very much appreciated
Try something like this:
int count = _db.Requests.Where(r => r.User.UserName == "username")
.Select(r =>
r.Events.OrderByDescending(e => e.WhenDone).FirstOrDefault())
.Count(r => r != null && r.Description == "description");
Related
Here is my scenario. I have a Document class. The document is associated with a DocumentClasses table through a many to many relationship, so a document can have one or more classes. When running a search, the user can choose to filter documents by class. So, I need to be able to append a where clause to my query if the user chooses to select any classes. The logic is that if the document is assigned to any of the classes in the classes the user selected, the document should be returned. So the basic query needed in pseudo code. So basically, if any number in list A belongs to list B, then return the record.
I have tried this (RestrictByClasses is just a List(Of Integer)):
query = query.where(Function(resultItem) RestrictByClasses.Contains(resultItem.DocumentClassIds.Any())
But I get the following exception:
The nested query is not supported. Operation1='Case' Operation2='Collect'
Is there any way to get linq to filter records out like this?
Thanks!
UPDATE:
After doing a little more debugging, I think that it is more to do with how I am projecting onto the object in order to load it with values that can be used to filter. Here is how I am doing the projection:
Dim query = From document In dbContext.Documents
Select New FeeAndReceptionReportIntermediateItem With
{
.BookTypeId = If(restrictByBookTypes AndAlso document.DocumentInstruments.Any(), document.DocumentInstruments.FirstOrDefault().Instrument.BookTypeId, Nothing),
.CustomerId = document.CustomerId,
.DocumentClassIds = If(restrictByDocumentClasses, document.DocumentClasses.Select(Function(group) group.ClassId), Nothing),
.DocumentId = document.DocumentId,
.DocumentNumber = document.DocumentNumber,
.DepartmentId = document.DepartmentId,
.InstrumentGroupIds = If(restrictByInstrumentGroup, document.DocumentInstruments.FirstOrDefault().Instrument.InstrumentGroups.Select(Function(group) group.InstrumentGroupId), Nothing),
.RecordDateTime = document.RecordDateTime,
.RestrictedInstrument = (includeRestrictedDocuments AndAlso document.DocumentInstruments.Any() AndAlso document.DocumentInstruments.FirstOrDefault().Instrument.Restricted)
}
I think it is complaining about how the .DocumentClassIds and .InstrumentGroupId's are being loaded into the POCO object (FeeAndReceptionReportIntermediateItem). I would really like to load these up in the initial query, before a .ToList() has been called and I would really like to not even do the join if the user did not pass in the restrictions that require me to create the join, that's why I am using the navigation properties and an if statement when loading these collecctions, because I am assuming if "restrictByDocumentClasses" is false, the navigation property won't be accessed and the join won't be included.
This works as a general pattern. The first line gets an IQueryable<> from the DbSet<>. The select does this for us, so that we can continue reusing query to hold our query as we build it up. Then just keep adding on If...Then...query=query.Where(...)...Endif to continue whittling down the resultset.
var query=db.MyTable.Select(x=>x);
if (RestrictByClasses.Any())
query=query.Where(r =>
r.DocumentClasses.Select(x=>x.ClassId)
.Intersect(RestrictByClasses)
.Any());
if (RestrictBySomethingElse)
query=query.Where(x=>SomethingElse)
I think this is the equivalent in VB.NET:
Dim query = db.MyTable.[Select](Function(x) x)
If RestrictByClasses.Any() Then
query = query.Where(Function(r) r.DocumentClasses.Select(Function(group) group.ClassId).Intersect(RestrictByClasses).Any())
End If
'Repeat as necessary
If RestrictBySomethingElse Then
query = query.Where(Function(x) SomethingElse)
End If
'End repeat
' Rest here is pseudo code
' Sort
SELECT/SWITCH sortonfield
CASE 'name': query=query.OrderBy(Function(x) x.name)
CASE 'dob': query=query.OrderBy(Function(x) x.dob)
DEFAULT: query=query.OrderBy(Function(x) x.id)
END CASE
'Paginate
query=query.Skip((pagenumber-1)*pagesize).Take(pagesize)
'Project
Dim finalresult=query.Select(Function(x) new something {
name=x.Name,
id=x.id,
things=x.things
});
Once all your filters have been put in place (and optionally a sort, and pagination), then project your resultset into whatever you need.
I have a repository method that gets all the Users from the database. Each User has a single child object called Profile and one or many child objects that fall under a List called Companies. This repository method has been tested, works fine and returns type IQueryable, which I am using as a base to later get narrowed down results once a query is triggered.
What I am trying to do is get a list of users from that repository method that have at least one associated company that has an ID that matches an element in an existing list called 'targetCompanyIDs'. The list is of type List<int> and the company's ID is also an int. Here is my attempt to generate that list of users:
List<User> usersData = rep.GetUsersProfilesAndCompanies().Where(u => targetCompanyIDs.Contains(u.CompanyStructures.ID)).ToList();
The error I get is that type List does not contain a definition for ID. Makes sense, right? So what I tried to do is treat the User's associated companies as some type of group or aggregate. Here is my second attempt:
List<User> usersData = rep.GetUsersProfilesAndCompanies().Where(u => targetCompanyIDs.Contains(u.CompanyStructures.Any(cs => cs.ID))).ToList();
What I am trying to say is, for any company the user has associated with it, does that company's ID match with the list targetCompanyIDs? If so, include the user on the list. Unfortunately this gives an 'invalid argument' error.
Is there any way in LINQ to query against multiple child elements like I am trying to do here?
Try this way :
List<User> usersData = rep.GetUsersProfilesAndCompanies()
.Where(u => u.CompanyStructures.Any(o => targetCompanyIDs.Contains(o.ID)))
.ToList();
I am building an ASP.Net MVC 3 Web application which uses Entity Framework 4.1. I also use the Unit of Work pattern with a Generic Repository. I have two Entities in my Model, Shift and ShiftDate. Please see the diagram below for their structure. The idea is, a Shift can have 1 or many ShiftDates, and a ShiftDate can belong to only one Shift.
I wish to create a query which will return a List of all Shifts like so
public IList<Shift> GetShifts(int id)
{
return _UoW.Shifts.Get(s => s.organisationID == id).OrderBy(s => s.startDate).ToList();
}
Then in my Razor View, I wish to use a foreach loop and list the details of each Shift. One of these details is the total number of ShiftDates each Shift has. To do this I will do something like
foreach(var shiftDate in Model.Shifts)
{
#Html.DisplayFor(modelItem => item.ShiftDates.Count)
}
This works fine, however, I want the count to exclude ShiftDates whose shiftDateStatusID is set to 442. To do this I tried amending my Linq Query to this
return _UoW.Shifts.Get(s => s.organisationID == id && s.ShiftDates.Any(sd => sd.shiftDateStatusID != 442))
.OrderBy(s => s.startDate).ToList();
However, this still pulls back ShiftDates who have a shiftDateStatusID of 442.
Hopefully this makes sense.
Any feedback would be greatly appreciated.
Thanks.
If you do
_UoW.Shifts.Get(s => s.organisationID == id
&& s.ShiftDates.Any(sd => sd.shiftDateStatusID != 442)
you get shifts that have at least one ShiftDate having a shiftDateStatusID other than 442. Well, there are probably a lot of shifts meeting this condition. The ShiftDates collections of these shifts will be loaded completely, including those with 442 (if any).
Either you have to exclude the ShiftDates with status 442 when you populate your view model, or when it's only for the count, exclude them when you do the count:
item.ShiftDates.Count(sd => sd.shiftDateStatusID != 442)
I want to do something like
from table1
where col5="abcd"
select col1
I did like
query_ = From g In DomainService.GetGEsQuery Select New GE With {.Desc = g.codDesc}
"This cause a runtime error, i tried various combinations but failed"
please help.
I'm assuming your trying to do this on the client side. If so you could do something like this
DomainService.Load(DomainService.GetGEsQuery().Where(g => g.codDesc == "something"), lo =>
{
if (lo.HasError == false)
{
List<string> temp = lo.Entities.Select(a => a.Name).ToList();
}
}, null);
you could also do this in the server side (which i would personally prefer) like this
public IQueryable<string> GetGEStringList(string something)
{
return this.ObjectContext.GE.Where(g => g.codDesc == something).Select(a => a.Name);
}
Hope this helps
DomainService.GetGEsQuery() returns an IQueryable, that is only useful in a subsequent asynchronous load. Your are missing the () on the method call, but that is only the first problem.
You can apply filter operations to the query returned using Where etc, but it still needs to be passed to the Load method of your domain context (called DomainService in your example).
The example Jack7 has posted shows an anonymous callback from the load method which then accesses the results inside the load object lo and extracts just the required field with another query. Note that you can filter the query in RIA services, but not change the basic return type (i.e. you cannot filter out unwanted columns on the client-side).
Jack7's second suggestion to implement a specific method server-side, returning just the data you want, is your best option.
when using a MVC 3 collection that uses IEnumerable, is there a way to make small queries work to find single values? I've been experimenting with little success.
I have a collection of settings that have descriptions and settings. The problem is exposing one of them, as each is unique. I've tried something like this:
var appl = _service.GetSettings(id, app); //Call to service layer & repository
appl.Select(a => a.setting_value.Where(a.setting_name.StartsWith("Login")));
With little success (unless I'm missing something). Is it possible to select one item from an enumerable collection and either pass it to a ViewBag or ViewData object? What I would like to do is something like the following:
var appl = _service.GetSettings(id, app); //Call to service layer & repository
ViewBag.Login = appl.Select(a => a.setting_value.Where(a.setting_name.StartsWith("Login")));
And pass this to the view, since I have a view that now combines a collection with single values.
The following seems to work within the view:
Application Name #Html.TextBox("application_name", #Model.FirstOrDefault().app_name)
But I'm not sure if this violates separation of concerns. Am I on the wrong path here? Thank you!
EDIT: Here is what I needed. The answers below got me very very close +1 +1
ViewBag.Login = appl.Where(a => a.setting_name.StartsWith("Login")).FirstOrDefault().setting_value
ViewBag.Login = appl.Select(a => a.setting_value.Where(a.setting_name.StartsWith("Login"))).FirstOrDefault();
This will select the first object that matches your criteria and return a single result or null
var appl = _service.GetSettings(id, app);
ViewBag.Login = appl
.Where(a => a.setting_name.StartsWith("Login"))
.FirstOrDefault();