need help around IQueryable query result - c#

Assuming following tables
Person
id
name
PersonTeam
id
person_id
is_supervisor
team_id
Team
id
TimeSheet
id
team_id
I would like to obtain all TimeSheets for a supervisor. I got name of supervisor, then I need select which team he is got supervisor role. Then select all time sheet of those teams.
I believe following query does
var allTimeSheets = ctx.PersonTeam.Where(y => y.Person.name == supervisor_name).Where(x => x.is_supervisor == true).Select(z => z.Team).Select(t => t.TimeSheet);
afer this operation I cannot understand allTimeSheets is a
IQueryable<ICollection<TimeSheet>>
I expected more a
<ICollection<TimeSheet>>
or any IEnumrable.
Then questions are :
why I got that kind of result ?
how to obtain TimeSheet[] where I got IQueryable < ICollection < TimeSheet > > ?

why did I get that kind of result ? I expected more a ICollection<TimeSheet>
An IQueryable<T> is an IEnumerable<T>. The reason it's returning an IQueryable is so you can chain other methods like OrderBy onto it and project those to the actual SQL.
I just realized what you're asking. To "flatten" the collection of collections, use SelectMany instead of two chained Selects:
var allTimeSheets = ctx.PersonTeam
.Where(y => y.Person.name == supervisor_name
&& y.is_supervisor == true)
.SelectMany(z => z.Team, (z, t) => t.TimeSheet);
The answer to your second question still applies:
how do I obtain a TimeSheet[] from a IQueryable<ICollection<TimeSheet>>
(first of all use the first part to change to an IQueryable<TimeSheet>)
You can call one of the "conversion" methods like ToArray, ToList, to "hydrate" the query into a concrete type.
You can also call "AsEnumerableto cast to anIEnumerableto convert the query to Linq-To-Objects, which has better support for custom functions in sorts, filters, etc. Note that callingAsEnunerable` does no immediately fetch the objects, but will do as as soon as the collection in enumerated.

Related

LINQ include a subset of a query

I have an Entity in EF Core that is using the structure like:
Course has an entity CourseUserRoles which has the CourseId, UserId and RoleId.
CourseViewModel has the same structure as Course, except CourseUserRoles, instead it has two booleans IsAdministrator and IsContributor, that are related to the RoleId.
I am trying to make a query that won't bring all CourseUserRoles for every course queried, but only the ones specific for that user.
I saw that syntactically the below is correct:
query = query.Include(x => x.CourseUserRoles.Where(y => y.UserId == userId));
where the query is trying to return a list of courses, I just want to include the ones that have the same Id as the user.
The problem is that the above is throwing an exception.
Is it possible to only include the CourseUserRoles when the course has the UserId? If it doesn't have it would return null or empty list.
I typically do this by creating separate queries and concatenating as follows:
query = query.Where(x => x.CourseUserRoles.UserId != userId)
var queryWithInclude = query.Where(x => x.CourseUserRoles.UserId == userId)
.Include(x => x.CourseUserRoles)
var fullDataset = query.Concat(queryWithInclude);
This keeps both query objects as type IQueryable and not IEnumerable, allowing execution to happen in SQL/server-side rather than in memory.

EF: LINQ - orderby using child collection with condition - ArgumentException

I'm running into troubles trying to sort IQueryable of my EF Entity.
My object structure is something like this:
Item
Item.CustomFieldValue [List<CustomFieldValue>]
Item.CustomFieldValue.DefinitionID
Item.CustomFieldValue.Value
and I'm working with
IQueryable<Item>
I'd need to sort it conditionally with values having desired definition id being sorted first something like this:
queryable = queryable
.OrderBy(p => p.CustomFieldValue
.Where(p2 => p2.DefinitionID == defId)
.Select(p3 => p3.Value)
.OrderBy(p4 => p4)
);
This however throws ArgumentException "DbSortClause expressions must have a type that is order comparable.".
I indeed understand what's the exception trying to say to me, I just can't figure out on how to change this so that valid query is generated.
Any help greatly appreciated
EDIT:
To bring some more light into the issue, I want to achieve something similar that this query does
SELECT * FROM ticketnumber t, customfieldvalue c
WHERE t.id like '%00000047%' and c.ticketnumberid = t.id
ORDER BY CASE
WHEN DefinitionId = 2125 THEN 1
ELSE 2
END, c.Value ASC
Alternatively, as time is starting to become a factor for me, is there a way I could append OrderBy in string form?
You probably want to use FirstOrDefault() at the end of the end of the first OrderBy so you won't be dealing with enumerables but with values.
queryable = queryable
.OrderBy(p => p.CustomFieldValue
.Where(p2 => p2.DefinitionID == defId)
.Select(p3 => p3.Value)
.OrderBy(p4 => p4)
.FirstOrDefault()
);
Modification of Joanvo's answer did the trick, this is the working code [I've removed the inner OrderBy]
queryable = queryable.OrderBy(p => p.CustomFieldValue.Where(p2 => p2.DefinitionID == defId).Select(p3 => p3.Value).FirstOrDefault());

Linq to sql expression tree execution zone issue

I have got a bit of an issue and was wondering if there is a way to have my cake and eat it.
Currently I have a Repository and Query style pattern for how I am using Linq2Sql, however I have got one issue and I cannot see a nice way to solve it. Here is an example of the problem:
var someDataMapper = new SomeDataMapper();
var someDataQuery = new GetSomeDataQuery();
var results = SomeRepository.HybridQuery(someDataQuery)
.Where(x => x.SomeColumn == 1 || x.SomeColumn == 2)
.OrderByDescending(x => x.SomeOtherColumn)
.Select(x => someDataMapper.Map(x));
return results.Where(x => x.SomeMappedColumn == "SomeType");
The main bits to pay attention to here are Mapper, Query, Repository and then the final where clause. I am doing this as part of a larger refactor, and we found that there were ALOT of similar queries which were getting slightly different result sets back but then mapping them the same way to a domain specific model. So take for example getting back a tbl_car and then mapping it to a Car object. So a mapper basically takes one type and spits out another, so exactly the same as what would normally happen in the select:
// Non mapped version
select(x => new Car
{
Id = x.Id,
Name = x.Name,
Owner = x.FirstName + x.Surname
});
// Mapped version
select(x => carMapper.Map(x));
So the car mapper is more re-usable on all areas which do similar queries returning same end results but doing different bits along the way. However I keep getting the error saying that Map is not able to be converted to SQL, which is fine as I dont want it to be, however I understand that as it is in an expression tree it would try to convert it.
{"Method 'SomeData Map(SomeTable)' has no supported translation to SQL."}
Finally the object that is returned and mapped is passed further up the stack for other objects to use, which make use of Linq to SQL's composition abilities to add additional criteria to the query then finally ToList() or itterate on the data returned, however they filter based on the mapped model, not the original table model, which I believe is perfectly fine as answered in a previous question:
Linq2Sql point of retrieving data
So to sum it up, can I use my mapping pattern as shown without it trying to convert that single part to SQL?
Yes, you can. Put AsEnumerable() before the last Select:
var results = SomeRepository.HybridQuery(someDataQuery)
.Where(x => x.SomeColumn == 1 || x.SomeColumn == 2)
.OrderByDescending(x => x.SomeOtherColumn)
.AsEnumerable()
.Select(x => someDataMapper.Map(x));
Please note, however, that the second Where - the one that operates on SomeMappedColumn - will now be executed in memory and not by the database. If this last where clause significantly reduces the result set this could be a problem.
An alternate approach would be to create a method that returns the expression tree of that mapping. Something like the following should work, as long as everything happening in the mapping is convertible to SQL.
Expression<Func<EntityType, Car>> GetCarMappingExpression()
{
return new Expression<Func<EntityType, Car>>(x => new Car
{
Id = x.Id,
Name = x.Name,
Owner = x.FirstName + x.Surname
});
}
Usage would be like this:
var results = SomeRepository.HybridQuery(someDataQuery)
.Where(x => x.SomeColumn == 1 || x.SomeColumn == 2)
.OrderByDescending(x => x.SomeOtherColumn)
.Select(GetCarMappingExpression());

IQueryable queried data count

I am trying to get the count of the items as I'm applying a query to a IQueryable.
I'am trying to do it like:
this.lblSth.Text = new Repository<Sth>().GetAll().Where(p => p.PersonId == personId).ToList().Count().ToString();
I think this gets all the data across the condition and takes the objects, then it takes the count; so I'm curious if for example I'd just take the Id columns and cast it to the list or some other smart way; that count operation would be quicker?
Info: GetAll() => It's a repository pattern method that returns IQueryable objects T from linqToSql data entity.
I'm open to all types of different ideas. Thanks
I think the call to Where and ToList is redundant.
see below.
this.lblSth.Text = new Repository<Sth>().GetAll().Count(p => p.PersonId == personId).ToString();
If you want to do this quicker, just don't call ToList():
this.lblSth.Text = new Repository<Sth>().GetAll()
.Where(p => p.PersonId == personId)
.Count()
.ToString();
This way, (assuming it's an SQL-backed IQueryable<T>) it will execute a query like SELECT COUNT(*) FROM …, not SELECT * FROM … like your approach. And this query should be much faster.
ToList() will execute the query and turn your IQUeryable into IEnumerable. I would call the count on the where clause. That way the Count will become part of the end query

How to use two conditions in Linq lambda which has different where clause

I want to query my item in table Items, where the last update of each item must be less than 91 days old (from last update till now) and the quantity > 0.
This is my code in the Model:
public IList<Item> GetAllProducts()
{
var ien_item = from i in this.DataContext.Items
orderby i.LastUpdated descending
select i;
return ien_item.ToList().Where(
s =>
HelperClasses.HelperClass.IsLastUpdate(s.LastUpdated.Value) == true
&&
(s => s.Quantity) > 0
)
.ToList();
}
Anyone can solve it? Thanks.
We don't really know what's not working here. EDIT: Merlyn spotted it; your lambda syntax is messed up. There's more to do here though.
However, I'd have thought you'd want this:
public IList<Item> GetAllProducts()
{
var lastUpdateLimit = DateTime.UtcNow.Date.AddDays(-91);
var query = from item in DataContext.Items
where item.Quantity > 0 && item.LastUpdated >= lastUpdateLimit
orderby item.LastUpdated descending
select item;
return query.ToList();
}
Note that this is able to do all the querying at the database side instead of fetching all the items and filtering at the client side. It does assume that HelperClasses.HelperClass.IsLastUpdate is simple though, and basically equivalent to the filter I've got above.
(One additional point to note is that by evaluating UtcNow.Date once, the result will be consistent for all items - whereas if your code evaluates "today" on every call to IsLastUpdate, some values in the query may end up being filtered against a different date to other values, due to time progressing while the query is evaluating.)
EDIT: If you really need to use HelperClasses.HelperClass.IsLastUpdate then I'd suggest:
public IList<Item> GetAllProducts()
{
var query = from item in DataContext.Items
where item.Quantity > 0
orderby item.LastUpdated descending
select item;
return query.AsEnumerable()
.Where(s => HelperClass.IsLastUpdate(s.LastUpdated.Value))
.ToList();
}
... then at least the quantity filter is performed at the database side, and you're not creating a complete buffered list before you need to (note the single call to ToList).
The problem is your lambda syntax. You're trying to define a second lambda while in the middle of defining a first lambda. While this is possible to do, and useful in some contexts, it is sort of an advanced scenario, and probably won't be useful to you until you know you need it.
Right now, you don't need it. Unless you know you need it, you don't need it :)
So -
Instead of what you've written:
.Where(
s =>
HelperClasses.HelperClass.IsLastUpdate(s.LastUpdated.Value) == true
&& (s => s.Quantity) > 0
)
Write this instead:
.Where(
s =>
HelperClasses.HelperClass.IsLastUpdate(s.LastUpdated.Value) == true
&& s.Quantity > 0 // Notice I got rid of the extra lambda here
)
If you're morbidly curious:
The compile error you got is because you didn't define your second lambda correctly. It redefined a variable you'd already used (s), and you were trying to check if a lambda was greater than zero. That makes no sense. You can only compare the result of a lambda to some value. It's like calling a function. You don't compare functions to numbers - you compare the result you get when calling a function to a number.
Easy ...
public IList<Item> GetAllProducts()
{
var ien_item =
from i in DataContext.Items
where
HelperClasses.HelperClass.IsLastUpdate(i.LastUpdated.Value)
&& s.Quantity > 0
orderby i.LastUpdated descending
select i;
return ien_item.ToList();
}
Linq to SQL: Methods are not allowed (linq is not magic and can not convert C# methods to TSQL)
http://msdn.microsoft.com/en-us/library/bb425822.aspx
Linq to Object: while looking the same, it is much more powerful than linq to SQL... but can not query SQL databases :)
http://msdn.microsoft.com/en-us/library/bb397919.aspx
Linq to XML: same as linq to Object, with xml object
http://msdn.microsoft.com/en-us/library/bb387098.aspx
Linq to Dataset: not the same as Linq to SQL !
http://msdn.microsoft.com/en-us/library/bb386977.aspx
Other linq providers:
http://en.wikipedia.org/wiki/Language_Integrated_Query

Categories

Resources