Understanding the processing pipeline - c#

Take
var query = Process.GetProcesses()
.OrderBy(p => p.WorkingSet64)
.ThenByDescending(p => p.Threads.Count);
.Where(p => p.ProcessName.Length < 9);
It works fine. Take
var query = Process.GetProcesses()
.OrderBy(p => p.WorkingSet64)
.ThenByDescending(p => p.Threads.Count);
//.Where(p => p.ProcessName.Length < 9);
query = query.Where(p => p.ProcessName.Length < 9);
This does not work. I do not understand why the first method works. In my mind these queries are the same. ThenByDescending returns IOrderedEnumerable<T> which is piped into Where(). The first method should not work because Where only works with IEnumerable<T>. Alas...it does work.
How does this processing pipeline function?

The difference is because of a misunderstanding of the var keyword and LINQ queries.
var (C# reference)
Using the var keyword is the same as specifying the same type as the right side of the assignment. It does not mean that you can assign any type to the variable.
In LINQ queries, most of the basic expressions return an IEnumerable, but it many cases, they do not return just an IEnumerable. Instead, they return an type that inherits from IEnumerable.
In this case, you are doing the equivalent of this:
IEnumerable<Process> query = Process.GetProcesses()
.OrderBy(p => p.WorkingSet64)
.ThenByDescending(p => p.Threads.Count);
.Where(p => p.ProcessName.Length < 9);
and
IOrderedEnumerable<Process> query = Process.GetProcesses()
.OrderBy(p => p.WorkingSet64)
.ThenByDescending(p => p.Threads.Count);
// Won't work because Where doesn't return an IOrderedEnumerable.
query = query.Where(p => p.ProcessName.Length < 9);
The reason that the first snippet works is because IOrderedEnumerable inherits from IEnumerable, so you can use it as such.
To fix the problem in the second example, you need to explicitly declare query as IEnumerable<Process>.

Related

I can not make the correct linq request

I form a request, it works.
var query = db.Persons.Where(p => p.Date == "27.02.2020").Where(p => p.Country == "USA");
But since I will need to use it in if-else
I changed it a little and now he just selects the last Where.
How can this problem be solved?
var query = db.Persons;
query.Where(p => p.Date == "27.02.2020");
query.Where(p => p.Country == "USA");
The Where method, like all LINQ methods, does not modify the source enumerable in-place, but instead returns a new enumerable that has the operation, or in this case filter, applied to it.
That means you need to assign the result of each operation back into your variable for it to persist. The following should be equivalent to your original snippet:
IQueryable<Person> query = db.Persons;
query = query.Where(p => p.Date == "27.02.2020");
query = query.Where(p => p.Country == "USA");
As pointed out in the comments, the query variable type now needs to be compatible with both the type of db.Persons and the return type of Where.

Passing predicate to LINQ where clause

Hey I have following code:
Func<Assoc, bool> predicate = (x) =>
(
!String.IsNullOrWhiteSpace(version) ? x.Version.Contains(version) : x.Version != null
);
var assocs = _context.Assoc
.Where(x => x.Model == model)
.Where(predicate)
;
But it doesn't work. If I try to execute this server gives me Internal Server Exception but if I change this to
var assocs = _context.Assoc
.Where(x => x.Model == model)
.Where(x => x.Version.Contains(version))
;
it works as I expect.
Why is that?
Is it possible to get preview of Linq generated query?
Using LINQKit you can create predicates that will be expanded for you so you can use them in IQueryable expressions, but you don't need that for just excluding version testing.
var assocs = _context.Assoc
.Where(x => x.Model == model);
if (!String.IsNullOrWhiteSpace(version))
assocs = assocs.Where(x.Version.Contains(version));
becuse the EF can translate Contains method to sql (Version LIKE '%#version%'), but no evaluate your "external" function.
if you load the result to memroy (by export method as ToList()) you can do it as linq2object:
var assocs = _context.Assoc.ToList()
.Where(x => x.Model == model)
.Where(predicate);
My code shows and illustrates but it is definitely not recommended, because it is better to question the DB than to work on memory.

Refactor Linq query

I am trying to cut this linq down
var sys = db.tlkpSystems
.Where(a => db.tlkpSettings.Where(e => e.Hidden < 3)
.Select(o => o.System)
.ToList().Contains(a.System)) //cannot get this part in?
.OrderBy(a => a.SystemName).ToList();
foreach (var item in sys)
model.Add(new SettingSystem {
System = item.System,
SystemName = item.SystemName
});
I have tried the following:
List<SettingSystem> model = new List<SettingSystem>();
model = db.tlkpSettings.Where(e => e.Hidden < 3)
.OrderBy(e => e.Setting)
.Select(e => new SettingSystem
{
System = e.System,
SystemName = e.Setting
}).ToList();
How can I call the .Contains(a.System) part in my query?
Thanks
Some general rules when working with LINQ to Entities:
Avoid using ToList inside the query. It prevents EF to build a correct SQL query.
Don't use Contains when working with entities (tables). Use Any or joins.
Here is your query (in case System is not an entity navigation property):
var sys = db.tlkpSystems
.Where(a => db.tlkpSettings.Any(e => e.Hidden < 3 && e.System == a.System))
.OrderBy(a => a.SystemName).ToList();
As an addendum, there is also AsEnumerable for when you must pull a query into memory (such as calling methods within another clause). This is generally better than ToList or ToArray since it'll enumerate the query, rather than enumerating, putting together a List/Array, and then enumerating that collection.

Only parameterless constructors and initializers are supported in LINQ to Entities message

I have a method that returns data from an EF model.
I'm getting the above message, but I can't wotk our how to circumvent the problem.
public static IEnumerable<FundedCount> GetFundedCount()
{
var today = DateTime.Now;
var daysInMonth = DateTime.DaysInMonth(today.Year, today.Month);
var day1 = DateTime.Now.AddDays(-1);
var day31 = DateTime.Now.AddDays(-31);
using (var uow = new UnitOfWork(ConnectionString.PaydayLenders))
{
var r = new Repository<MatchHistory>(uow.Context);
return r.Find()
.Where(x =>
x.AppliedOn >= day1 && x.AppliedOn <= day31 &&
x.ResultTypeId == (int)MatchResultType.Accepted)
.GroupBy(x => new { x.BuyerId, x.AppliedOn })
.Select(x => new FundedCount(
x.Key.BuyerId,
x.Count() / 30 * daysInMonth))
.ToList();
}
}
FundedCount is not an EF enity, MatchHistory is, so can't understand why it is complaining.
All advice appreciated.
The reason it is complaining is because it doesn't know how to translate your Select() into a SQL expression. If you need to do a data transformation to a POCO that is not an entity, you should first get the relevant data from EF and then transform it to the POCO.
In your case it should be as simple as calling ToList() earlier:
return r.Find()
.Where(x => x.AppliedOn >= day1 && x.AppliedOn <= day31 &&
x.ResultTypeId == (int)MatchResultType.Accepted)
.GroupBy(x => new { x.BuyerId, x.AppliedOn })
.ToList() // this causes the query to execute
.Select(x => new FundedCount(x.Key.BuyerId, x.Count() / 30 * daysInMonth));
Be careful with this, though, and make sure that you're limiting the size of the data set returned by ToList() as much as possible so that you're not trying to load an entire table into memory.
Message is clear : linq to entities doesn't support objects without a parameterless ctor.
So
Solution1
enumerate before (or use an intermediate anonymous type and enumerate on that one)
.ToList()
.Select(x => new FundedCount(
x.Key.BuyerId,
x.Count() / 30 * daysInMonth))
.ToList();
Solution2
add a parameterless ctor to your FundedCount class (if it's possible)
public FundedCount() {}
and use
.Select(x => new FundedCount{
<Property1> = x.Key.BuyerId,
<Property2> = x.Count() / 30 * daysInMonth
})
.ToList();
It's complaining because it can't convert references to FundedCount to SQL statements.
All LINQ providers convert LINQ statements and expressions to operations that their target can understand. LINQ to SQL and LINQ to EF will convert LINQ to SQL, PLINQ will convert it to Tasks and parallel operations, LINQ to Sharepoint will convert it to CAML etc.
What happens if they can't do the conversion, depends on the provider. Some providers will return intermediate results and convert the rest of the query to a LINQ to Objects query. Others will simply fail with an error message.
Failing with a message is actually a better option when talking to a database. Otherwise the server would have to return all columns to the client when only 1 or 2 would be actually necessary.
In your case you should modify your select to return an anonymous type with the data you want, call ToList() and THEN create the FundedCount objects, eg:
.Select( x=> new {Id=x.Key.BuyerId,Count=x.Count()/30 * daysInMonth)
.ToList()
.Select(y => new FundedCount(y.Id,y.Count))
.ToList();
The first ToList() will force the generation of the SQL statement and execute the query that will return only the data you need. The rest of the query is actually Linq to Objects and will get the data and create the final objects
I had the same exception in GroupBy. I found that the exception "Only parameterless constructors and initializers are supported in LINQ to Entities" is not 100% accurate description.
I had a GroupBy() in my "Linq to EntityFramework query" which used a struct as a Key in GroupBy. That did not work. When I changed that struct to normal class everything worked fine.
Code sample
var affectedRegistrationsGrouped = await db.Registrations
.Include(r => r.Person)
.Where(r =>
//whatever
)
.GroupBy(r => new GroupByKey
{
EventId = r.EventId,
SportId = r.SportId.Value
})
.ToListAsync();
...
...
// this does not work
private struct GroupByKey() {...}
// this works fine
private class GroupByKey() {...}

LINQ equivalent of List<T>.Find()?

I'm looking at some code that takes an IEnumerable<T> and converts it to a List<T> so it can use List<T>.Find(predicate):
var myEnumerable = ...;
var myList = new List<T>(myEnumerable);
var match = myList.Find(value => value.Aaa == aaa && value.Bbb == bbb);
Is there a way to rewrite this using the LINQ extension methods that has the same effect, but without building an extra List<T> as an intermediate step?
The FirstOrDefault(source, predicate) extension method looks like a good candidate, but trying to figure out if it's exactly equivalent to Find is making my head hurt.
Just for reference, here is a table of some old .NET 2 style List<> instance methods, and their equivalent extension methods in Linq:
METHOD IN List<> METHOD IN Linq
------------------------------------------------------------------------------------------
list.Contains(item) query.Contains(item)
list.Exists(x => x.IsInteresting()) query.Any(x => x.IsInteresting())
list.TrueForAll(x => x.IsInteresting()) query.All(x => x.IsInteresting())
list.Find(x => x.IsInteresting()) query.FirstOrDefault(x => x.IsInteresting())
list.FindLast(x => x.IsInteresting()) query.LastOrDefault(x => x.IsInteresting())
list.FindAll(x => x.IsInteresting()) query.Where(x => x.IsInteresting())
list.ConvertAll(x => x.ProjectToSomething()) query.Select(x => x.ProjectToSomething())
Of course some of them are not entirely equivalent. In particular Linq's Where and Select use deferred execution, while FindAll and ConvertAll of List<> will execute immediately and return a reference to a new List<> instance.
FindLast will often be faster than LastOrDefault because FindLast actually searches starting from the end of the List<>. On the other hand LastOrDefault(predicate) always runs through the entire sequence (starting from the first item), and only then returns the most "recent" match.
The LINQ equivelent would be to use FirstOrDefault:
var match = myEnumerable.FirstOrDefault(value => value.Aaa == aaa && value.Bbb == bbb);
Or you can do the following way:
var match = myEnumerable.Where(value => value.Aaa == aaa && value.Bbb == bbb)
.FirstOrDefault();

Categories

Resources