Passing predicate to LINQ where clause - c#

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.

Related

Dynamic property in Linq query

I have the following Linq expression:
var itemToUpdate = ItemsUpdated.FirstOrDefault(x => x.Id == itemId);
I need to adapt it so that x.Id is a dynamically defined property, for example:
var itemToUpdate = ItemsUpdated.FirstOrDefault(x => x["Id"] == itemId);
I have tried the following already which I thought would be enough:
var itemToUpdate = ItemsUpdated.FirstOrDefault(x => (int)x.GetType().GetProperty("Id").GetValue(x) == itemId);
This runs without any errors, but any code after this statement does not execute therefore I assume something goes wrong with the expression above.

Finding common items is evaluated locally

Using Entity Framework Core 2.2 I have the following query:
IQueryable<User> users = _context.Users.AsNoTracking();
User user = await users
.Include(x => x.UserSkills)
.ThenInclude(x => x.Skill)
.FirstOrDefaultAsync(x => x.Id == 1);
var userSkills = user.UserSkills.ToList();
IQueryable<Lesson> lessons = _context.Lessons.AsNoTracking();
var test = lessons
.Where(x => x.IsEnabled)
.Where(x => x.LessonSkills.All(y => userSkills.Any(z => y.SkillId == z.SkillId)))
.ToList();
I am looking to get User Skills contains all Lesson Skills.
When I run this query I get the following error:
Exception thrown: 'System.InvalidOperationException' in System.Private.CoreLib.dll:
'Error generated for warning 'Microsoft.EntityFrameworkCore.Query.QueryClientEvaluationWarning:
The LINQ expression 'where ([y].SkillId == [z].SkillId)' could not be translated and will be evaluated locally.'.
How to change the query to solve this problem?
Update
I need to extend this query with an extra option (y.SkillLevelId <= z.SkillLevelId):
var test = lessons
.Where(x => x.IsEnabled)
.Where(x => x.LessonSkills.All(y => userSkills.Any(z =>
y.SkillId == z.SkillId
&&
y.SkillLevelId <= z.SkillLevelId)))
.ToList();
userSkills is in-memory collection, and from my experience with EF6 and EF Core so far I can say that the only reliable translatable construct with in-memory collections is Enumerable.Contains method on primitive type in-memory collection.
So the following solves the problem is question.
First (should be outside the query expression tree):
var userSkillIds = user.UserSkills.Select(x => x.SkillId);
Then instead of
.Where(x => x.LessonSkills.All(y => userSkills.Any(z => y.SkillId == z.SkillId)))
use the equivalent (but translatable):
.Where(x => x.LessonSkills.All(y => userSkillIds.Contains(y.SkillId)))
Update: If you can't use Contains, the options you have until EF Core starts supporting it are (1) EntityFrameworkCore.MemoryJoin package (I personally haven't tested it, but the idea is interesting), (2) manually building Or based predicate with Expression class (hard and works for small memory collections) and (3) replace the memory collection with real IQueryable<>, for instance
var userSkills = users
.Where(x => x.Id == 1)
.SelectMany(x => x.UserSkills);
and use the original query.

LINQ deferred (or immediate?) execution

Given the following query:
List<GetMultipleLookupListsOutput> data = await _masterListTranslationsRepository
.GetAll() //<- it returns IQueriable
.GroupBy(q => q.ListLabelID)
.Select(q => q
.OrderByDescending(w=>w.ISOLanguageCode == isoLanguageCode)
.ThenByDescending(w=>w.ISOLanguageCode == "en-US"))
.Select(q => q.FirstOrDefault()) // DB call ?
.GroupBy(q=>q.ListLabels.Lists.ListName)
.Select(q => new GetMultipleLookupListsOutput
{
ListName = q.Key,
LookupLists = q
.OrderByDescending(w => w.ISOLanguageCode == isoLanguageCode)
.ThenByDescending(w => w.ISOLanguageCode == "en-US")
.Select(w => new RegionalFeatureDto
{
Id = w.Id,
Label = w.BaseValue
})
.ToList() // DB call ?
})
.ToListAsync();
How many database calls will it generate ?
GetAll() method returns IQueryable, but does FirstOrDefault() and ToList() in second and third select statements will trigger database call ?
Any help would be greatly appreciated.
If you are concerned with generating multiple calls I would consider using EntityFramework Extensions
You can batch queries together by adding .Future() to the end of a query
Example:
db.BlogPosts.Where(x => x.Category.Any(y => y.Name.Contains("EntityFramework"))).Future();
So to answer your question you could combine these into one call to the database.
To check the SQL/batching you can also include this before your query:
db.Database.Log = s => System.Diagnostics.Debug.WriteLine($"SQL: {s}");
and the log will be displayed in your output window.

could not resolve property: PropertyName of: Class error in NHibernate

I'm using NHibernate first time and in this line it throws exception for me
var total = session
.QueryOver<Comment>().Where(p => p.Entry.Author == username)
.ToRowCountQuery()
.FutureValue<int>();
var results = session
.QueryOver<Comment>().Where(p => p.Entry.Author == username)
.Fetch(x => x.Entry).Eager()
.OrderBy(x => x.Posted).Desc()
.Skip(skip)
.Take(take)
.List();
The Exception is
could not resolve property: Entry.Author of: FunnelWeb.Model.Comment
I guess, the problem is that Entry object doesn't loaded here. How can I do that trick with Nhibernate?
QueryOver is just a strongly-typed wrapper for Criteria, and doesn't allow implicit deep references.
You'd have to use:
session.QueryOver<Comment>()
.JoinQueryOver(x => x.Entry)
.Where(x => x.Author == username)
Or you can use Query<> instead (LINQ) which will work with the syntax you've tried.
You'd need to JoinAlias or JoinQueryOver. I have an example below on how to use Future queries...
Entry entryAlias = null;
var q = session.QueryOver<Comment>()
.JoinAlias(x => x.Entry, () => entryAlias)
.Where(() => entryAlias.Author == username);
var totalFuture = q.ToRowCountQuery().FutureValue<int>(); //ToRowcountQuery clones the query, we can reuse it for results
var resultsFuture = q
//.Fetch(x => x.Entry).Eager() //already joined
.OrderBy(x => x.Posted).Desc()
.Skip(skip)
.Take(take)
.Future<Comment>();
var results = resultsFuture.ToList(); //both future queries are executed in the same batch
var total = totalFuture.Value;

Dealing with null values in chained linq-to-sql query expressions

I have a L2S repository query which I'm stuggling to write in a nice way. It looks something like...
_orderRepository
.GetAllByFilter(o => o.CustomerId == id)
.Select(o =>
new CustomerOrderRecord
(
o.Id,
o.PartNumber,
o.Date
// ... etc, more order details
/* Here I need the last DateTime? the customer placed
an order for this item, which might be null.
So I end up with the following horrible part of
the query */
o.Customer.CustomerOrderRecords
.Where(x => x.PartNumber == o.PartNumber)
.OrderByDescending(x => x.Date).FirstOrDefault()
== null ? null :
o.Customer.CustomerOrderRecords
.Where(x => x.PartNumber == o.PartNumber)
.OrderByDescending(x => x.Date).First().Date;
)).ToList();
So hopefully you can see the problem that I'm having to write the whole query chain twice just to do the null check when receiving the LastOrdered value.
This needs to be written in-line (I think) because GetAllByFilter returns an IQueryable.
I tried to use an intermediate variable within the select statement, so I'd have something a bit like the following, but I couldn't get anything like that to compile.
.Select(o =>
new CustomerOrderRecord
(
o.Id,
o.PartNumber,
o.Date
// ... etc, more order details
var last = o.Customer.CustomerOrderRecords
.Where(x => x.PartNumber == o.PartNumber)
.OrderByDescending(x => x.Date).FirstOrDefault()
== null ? null : last.Date;
)).ToList();
Is there a syntax trick available which solves this problem?
Try using Select to fetch the Date member:
o.Customer.CustomerOrderRecords
.Where(x => x.PartNumber == o.PartNumber)
.OrderByDescending(x => x.Date)
.Select(x => (DateTime?)x.Date)
.FirstOrDefault()

Categories

Resources