How to select items from IEnumerable? - c#

My ordersInfo variable has type IEnumerable<Order>. Order is a custom object. I try to make selection like this:
var s = ordersInfo.Select(x => x.Customer.Email == user.Email && x.Status == OrderStatus.Paid).ToList();
But it returns a collection of 161 elements (it is initial count of collection) and each item has value false. It is not Order object either. What is wrong?

It sounds like you want the Where statement, not the Select statement. Select is used to transform one object into another or select only specific parts of a given object. Where is used to filter.
var s = ordersInfo.Where(x => x.Customer.Email == user.Email
&& x.Status == OrderStatus.Paid).ToList();

I think you are looking for Where instead of Select.
var s = ordersInfo
.Where(x => x.Customer.Email == user.Email &&
x.Status == OrderStatus.Paid)
.ToList();

Select is a projection or a conversion for each item. Think like the SELECT clause of SQL - it changes the output. You want to use Where which is a deferred filtering.

From MSDN - IEnumerable.Select
Projects each element of a sequence into a new form.
Remarks
This method is implemented by using deferred execution. The immediate return value is an object that stores all the information that is required to perform the action. The query represented by this method is not executed until the object is enumerated either by calling its GetEnumerator method directly or by using foreach in Visual C# or For Each in Visual Basic.
This projection method requires the transform function, selector, to produce one value for each value in the source sequence, source. If selector returns a value that is itself a collection, it is up to the consumer to traverse the subsequences manually. In such a situation, it might be better for your query to return a single coalesced sequence of values. To achieve this, use the SelectMany method instead of Select. Although SelectMany works similarly to Select, it differs in that the transform function returns a collection that is then expanded by SelectMany before it is returned.
In query expression syntax, a select (Visual C#) or Select (Visual Basic) clause translates to an invocation of Select.
IEnumerable<int> squares = Enumerable.Range(1, 10).Select(x => x * x);
squares.ToList().ForEach(num => Console.WriteLine(num));
The output will be:
1
4
9
16
25
36
49
64
81
100
You may also use the IEnumerable.Select to only select a fewer properties as well from an object, which will cause the creation of an anonymous type.
What you want is to use the IEnumerable.Where() method.
From MSDN - IEnumerable.Where
Filters a sequence of values based on a predicate.
Remarks
This method is implemented by using deferred execution. The immediate return value is an object that stores all the information that is required to perform the action. The query represented by this method is not executed until the object is enumerated either by calling its GetEnumerator method directly or by using foreach in Visual C# or For Each in Visual Basic.
In query expression syntax, a where (Visual C#) or Where (Visual Basic) clause translates to an invocation of Where<TSource>(IEnumerable<TSource>, Func<TSource, Boolean>).
To answer your question
Use the Where method like so.
var s = ordersInfo.Where(x => x.Customer.Email == user.Email
&& x.Status == OrderStatus.Paid)
.ToList();
Which will actually filter the list based on the criterion given as the predicate, and the list shall get filtered upon the call of the ToList() method, as Where<T> is deferred filtering.

Related

How to deconstruct a LINQ IQueryable / Expression into the constituent parts?

I am writing an extension method which is meant to help prepare a LINQ query before it is actually executed in EF Core.
For example:
var context = new SchoolContext();
var studentWithGrade = context.Students
.Where(s => s.FirstName == "Bill")
.Include(s => s.Grade)
.Prepare()
.FirstOrDefault();
I am building the Prepare() extension on the IQueryable<> interface. In the method, I need to know things about the resulting query to be executed, such as:
The predicate (s.FirstName == "Bill")
The selected fields (from the Students table, as well as any fields from s.Grade)
The limit (an implied 1 in this case)
... and so on.
AFAICT, the Expression on the IQueryable is transformed into a SQL query (and executed) when FirstOrDefault() is called. So this transformation is obviously possible. But I would like to examine the data in a structured way, rather than inspecting a SQL query string. My hope was that I could simply inspect the queryable (query.Limit < 0) but the actual storage of these constraints seems buried with reflection in Expression object (?) based upon MethodCallExpression.cs's source code.

SelectMany query with Where produces many SQL queries

I'm using for a GetAppRolesForUser function (and have tried variations of based on answers here):
private AuthContext db = new AuthContext();
...
var userRoles = Mapper.Map<List<RoleApi>>(
db.Users.SingleOrDefault(u => u.InternetId == username)
.Groups.SelectMany(g => g.Roles.Where(r => r.Asset.AssetName == application)));
I end up with this in SQL Profiler for every single RolesId each time:
exec sp_executesql N'SELECT
[Extent2].[GroupId] AS [GroupId],
[Extent2].[GroupName] AS [GroupName]
FROM [Auth].[Permissions] AS [Extent1]
INNER JOIN [Auth].[Groups] AS [Extent2] ON [Extent1].[GroupId] = [Extent2].[GroupId]
WHERE [Extent1].[RolesId] = #EntityKeyValue1',N'#EntityKeyValue1 int',#EntityKeyValue1=6786
How do I refactor so EF produces a single query for userRoles and doesn't take 18 seconds to run?
I think the problem is you're lazy loading the groups and roles.
One solution is eager load them before you call SingleOrDefault
var user = db.Users.Include(x => x.Groups.Select(y => y.Roles))
.SingleOrDefault(u => u.InternetId == username);
var groups = user.Groups.SelectMany(
g => g.Roles.Where(r => r.Asset.AssetName == application));
var userRoles = Mapper.Map<List<RoleApi>>(groups);
Also note : there is no sanity checking for null here.
TheGeneral's answer covers why you are getting caught out with lazy loading. You may also need to include Asset to get AssetName.
With AutoMapper you can avoid the need to Eager Load the entities by employing .ProjectTo<T>() to the IQueryable, provided there is a User accessible in Group.
For instance:
var roles = db.Groups.Where(g => g.User.Internetid == username)
.SelectMany(g => g.Roles.Where(r => r.Asset.AssetName == application))
.ProjectTo<RoleApi>()
.ToList();
This should leverage the deferred execution where AutoMapper will effectively project in the .Select() needed to populate the RoleApi instance based on your mapping/inspection.
Here is another way of avoiding lazy loading. You can also look at projection and have only those fields which you need rather than loading the entire columns.
var userRoles = Mapper.Map<List<RoleApi>>(
db.Users.Where(u => u.InternetId == username).Select(../* projection */ )
.Groups.SelectMany(g => g.Roles.Where(r => r.Asset.AssetName == application)));
EF also comes with Include:
var userRoles = Mapper.Map<List<RoleApi>>(
db.Users.Where(u => u.InternetId == username).Select(../* projection */ )
.Include(g => g.Roles.Where(r => r.Asset.AssetName == application)));
Then can iterate the collection using multiple for loops.
You have to be aware of two differences:
The difference between IEnumerable and IQueryable
The difference between functions that return IQueryable<TResult> (lazy) and functions that return TResult (executing)
Difference between Enumerable and Queryable
. A LINQ statement that is AsEnumerable is meant to be processed in your local process. It contains all code and all calls to execute the statement. This statement is executed as soon as GetEnumerator and MoveNext are called, either explicitly, or implicitly using foreach or LINQ statements that don't return IEnumerable<...>, like ToList, FirstOrDefault, and Any.
In contrast, an IQueryable is not meant to be processed in your process (however it can be done if you want). It is usually meant to be processed by a different process, usually a database management system.
For this an IQueryable holds an Expression and a Provider. The Expression represents the query that must be executed. The Provider knows who must execute the query (the DBMS), and which language this executor uses (usually SQL). When GetEnumerator and MoveNext are called, the Provider takes the Expression and translates it into the language of the Executor. The query is sen't to the executor. The returned data is presented AsEnumerable, where GetEnumerator and MoveNext are called.
Because of this translation into SQL, an IQueryable can't do all the things that an IEnumerable can do. The main thing is that it can't call your local functions. It can't even execute all LINQ functions. The better the quality of the Provider the more it can do. See supported and unsupported LINQ methods
Lazy LINQ methods and executing LINQ methods
There are two groups of LINQ methods. Those that return `IQueryable<...>/IEnumerable<...> and those that do not.
The first group use lazy loading. This means that at the end of the LINQ statement the query has been created, but it is not executed yet. Only 'GetEnumeratorandMoveNextwill make that theProviderwill translate theExpression` and order the DBMS to execute the query.
Concatenating IQueryables will only change the Expression. This is a fairly fast procedure. Hence there is no performance gain if you make one big LINQ expression instead of concatenate them before you execute the query.
Usually the DBMS is smarter and better prepared to do selections than your process. The transfer of selected data to your local process is one of the slower parts of your query.
Advice: Try to create your LINQ statements such, that the executing
statement is the last one that can be executed by the DBMS. Make sure
that you only select the properties that you actually plan to use.
So for example, don't transfer foreign keys if you don't use them.
Back to your question
Leaving the mapper out of the question you start with:
db.Users.SingleOrDefault(...)
SingleOrDefault is a non-lazy function. It doesn't return IQueryable<...>. It will execute the query. It will transport one complete User to your local process, including its Roles.
Advice postpone the SingleOrDefault to the last statement:
var result = myDbcontext.Users
.Where(user => user.InternetId == username)
.SelectMany(user => user.Groups.Roles.Where(role => role.Asset.AssetName == application))
// until here, the query is not executed yet, execute it now:
.SingleOrDefault();
In words: From the sequence of Users, keep only those Users with an InternetId that equals userName. From all remaining Users (which you hope to be only one), select the sequence of Roles of the Groups of each User. However, we don't want to select all Roles, we only keep the Roles with an AssetName equal to application. Now put all remaining Roles into one collection (the many part in SelectMany), and select the zero or one remaining Role that you expect.

Of what type is the result of a LINQ query?

Examples on LINQ gives this
var query = context.Contacts
.Where(q => q.FirstName == "Tom");
I'm wondering what object is "query"? And also is it possible (advisable) to pass it to a method (within the same class)?
The query object is most likely of type IQueryable<Contact>. You can of course pass it to a method, whether that is in the same class or in another class does not matter.
But keep in mind that LINQ does use a mechanism named "deferred execution". That means that query does not get enumerated immediately, but rather when it is needed. All the stuff you put in your query (the Where-clause for example) gets executed then. For more information about deferred execution have a look at MSDN: Query Execution.
NB: You can find out the exact type of the query variable if you hover you mouse over it or the var keyword in Visual Studio.

Why the order of LINQ to objects methods counts

I read this question's answers that explain the order of the LINQ to objects methods makes a difference. My question is why?
If I write a LINQ to SQL query, it doesn't matter the order of the LINQ methods-projections for example:
session.Query<Person>().OrderBy(x => x.Id)
.Where(x => x.Name == "gdoron")
.ToList();
The expression tree will be transformed to a rational SQL like this:
SELECT *
FROM Persons
WHERE Name = 'gdoron'
ORDER BY Id;
When I Run the query, SQL query will built according to the expression tree no matter how weird the order of the methods.
Why it doesn't work the same with LINQ to objects?
when I enumerate an IQueryable all the projections can be placed in a rational order(e.g. Order By after Where) just like the Data Base optimizer does.
Why it doesn't work this way with LINQ to objects?
LINQ to Objects doesn't use expression trees. The statement is directly turned into a series of method calls, each of which runs as a normal C# method.
As such, the following in LINQ to Objects:
var results = collection.OrderBy(x => x.Id)
.Where(x => x.Name == "gdoron")
.ToList();
Gets turned into direct method calls:
var results = Enumerable.ToList(
Enumerable.Where(
Enumerable.OrderBy(collection, x => x.Id),
x => x.Name = "gdoron"
)
);
By looking at the method calls, you can see why ordering matters. In this case, by placing OrderBy first, you're effectively nesting it into the inner-most method call. This means the entire collection will get ordered when the resutls are enumerated. If you were to switch the order:
var results = collection
.Where(x => x.Name == "gdoron")
.OrderBy(x => x.Id)
.ToList();
Then the resulting method chain switches to:
var results = Enumerable.ToList(
Enumerable.OrderBy(
Enumerable.Where(collection, x => x.Name = "gdoron"),
x => x.Id
)
);
This, in turn, means that only the filtered results will need to be sorted as OrderBy executes.
Linq to objects's deferred execution works differently than linq-to-sql's (and EF's).
With linq-to-objects, the method chain will be executed in the order that the methods are listed—it doesn't use expression trees to store and translate the whole thing.
Calling OrderBy then Where with linq-to-objects will, when you enumerate the results, sort the collection, then filter it. Conversely, filtering results with a call to Where before sorting it with OrderBy will, when you enumerate, first filter, then sort. As a result the latter case can make a massive difference, since you'd potentially be sorting many fewer items.
Because, with LINQ for SQL, the SQL grammar for SELECT mandates that the different clauses occur in a particular sequence. The compiler must generate grammatically correct SQL.
Applying LINQ for objects on an IEnumerable involves iterating over the IEnumerable and applying a sequence of actions to each object in the IEnumerable. Order matters: some actions may transform the object (or the stream of objects itself), others may throw objects away (or inject new objects into the stream).
The compiler can't divine your intent. It builds code that does what you said to do in the order in which you said to do it.
It's perfectly legal to use side-effecting operations. Compare:
"crabapple"
.OrderBy(c => { Console.Write(c); return c; })
.Where(c => { Console.Write(c); return c > 'c'; })
.Count();
"crabapple"
.Where(c => { Console.Write(c); return c > 'c'; })
.OrderBy(c => { Console.Write(c); return c; })
.Count();
Linq to Objects does not reorder to avoid a would-be run-time step to do something that should be optimized at coding time. The resharpers of the world may at some point introduce code analysis tools to smoke out optimization opportunities like this, but it is definitely not a job for the runtime.

Why am I unable to acess properties of my object model?

For some odd reason, I am unable to access the properties of this object EVEN if I cast it as its model type. Does anyone have an idea as to why? (It may be obvious, but I'm pretty new to C# so please be patient! :o) )
Users currentUser = new Users();
currentUser = (from x in db_tc.Users where x.Id == Convert.ToInt32(User.Identity.Name) select x);
When I call currentUser, I am only able to access the CRUD methods and a List<Users> property called usrList. I didn't create the list definition, so I imagine this is some piece of the Entity framework that is automagically created.
I did try casting currentUser with (Users) prior to the entity query, it didn't help at all.
That's because you've only created the query, you haven't actually executed it. Add Single() (or First() etc.) to get the result:
var currentUser = (from x in db_tc.Users where x.Id == Convert.ToInt32(User.Identity.Name) select x).SingleOrDefault();
Single(): Gets the first element of the sequence, but will throw an exception if no element is found or if the sequence has more than one element.
First(): Gets the first element of the sequence, but will throw an exception if no element is found.
SingleOrDefault() and FirstOrDefault(): Same as above, but will return default(T) instead of throwing on the empty sequence.
This "deferred execution" aspect of LINQ is probably the most difficult part to understand. The basic idea is that you can build up a query using the query operations (Where(), Select(), etc.), and then you can execute that query to actually get its results, using one of the non-deferred execution operations (Single(), ToList(), etc.)
The operation you have here will return a list of matches the DB will return, it is a collection.
If you intend on it only returning a single record append .First(); to the end of your linq query.
Also remove this line Users currentUser = new Users();
and add this var currentUser =...
Some more tips from the "good LINQ practices" wagon...
LINQ should "usually" return var, then you convert to the data type you are expecting. Another good practice I have found is to immediately validate the return from LINQ, as any usage without validation is highly exception prone. For instance:
var qUser = (from x in db.Users select x);
if (qUser != null && currentUser.Count() > 0)
{
List<User> users = (List<User>)qUser.ToList();
... process users result ...
}
The not null and count greater than 0 check should be required after each LINQ query. ;)
And don't forget to wrap in try-catch for SqlException!

Categories

Resources