Using an expression with a linq query - c#

At the moment I have a linq query with a method residing inside of it. I'm getting the error LINQ to Entities does not recognize the method. So I found I can convert the method to be an expression LINQ to Entities does not recognize the method but I'm wondering is there a clean and easy way to add the expression to my linq query.
Original Method
public bool IsAvailable()
{
return Eligibility.ProgramType == InteractionProgramTypes.Available;
}
Changed to
public System.Linq.Expressions.Expression<Func<InteractionProgram, bool>> IsAvailable()
{
return i => i.Eligibility.ProgramType == InteractionProgramTypes.Available;
}
Linq query without expression
x => x.ActivityDate <= endDate && x.IsAvailable()
Linq query with expression
x => x.ActivityDate <= endDate && x.IsAvailable().Compile()
When doing that I get the compiler error, && operator cannot be applied to operands.
May I ask how do I append the expression to my current linq query.

Since you already converted IsAvailable to return Expression<Func<InteractionProgram,bool>>, all you need to do is to pass the result of calling this method to the Where method of IQueryable<T>:
var res = ctx.InteractionPrograms.Where(InteractionProgram.IsAvailable());
Note that in order for this to compile your IsAvailable method needs to be static. Moreover, you could make it a property for an even better readability:
class InteractionProgram {
public static Expression<Func<InteractionProgram,bool>> IsAvailable {get;} =
i => i.Eligibility.ProgramType == InteractionProgramTypes.Available;
... // other members of the class
}
...
var res = ctx.InteractionPrograms.Where(InteractionProgram.IsAvailable);
what about the other condition x.ActivityDate <= endDate. Where would that go?
Other conditions go into separate Where clauses either immediately before or immediately after IsAvailable condition. EF driver will combine the two expressions for you, resulting in a single query on RDBMS side.
Another alternative to sharing this expression would be creating an extension method on the EF context that returns IQueryable<InteractionProgram> pre-filtered for availability:
public static IQueryable<InteractionProgram> AvailableInteractionPrograms(this MyDbContext dbCtx) =>
dbXtx.InteractionPrograms.Where(i =>
i.Eligibility.ProgramType == InteractionProgramTypes.Available
);
This hides the function behind a shared method.

Related

How to pass func expression in LINQ where clause?

This is my Custom filter(Func) to pass in where clause
Func<Project,bool> filter = f =>
{
bool filteredContent = true;
if (!CreatorId.Equals(0))
filteredContent = f.CreatedBy.Equals(CreatorId);
if (filteredContent && !VerticalMarketId.Equals(0))
filteredContent = f.VerticalMarketsId.Equals(VerticalMarketId);
if (filteredContent && !ProductCategoryId.Equals(0))
filteredContent = f.ProductCategoriesId.Equals(ProductCategoryId);
return filteredContent;
};
This is my code where I get all the projects based on the conditions created in filter expression
getProjects = await _context.Projects.Where(x => x.IsDeleted == false && filter.Invoke(x))// Here I'm getting the exception
.Include(PC => PC.ProjectComments.Where(x => x.IsDeleted == false))
.Include(SP => SP.SharedProjects)
.AsNoTracking().ToListAsync();
Exception:The LINQ expression (DbSet......) could not be
translated. Either rewrite the query in a form that can be translated,
or switch to client evaluation explicitly by inserting a call to
'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'.
Can someone tell how can I filter the data using expression in this?
NOTE: I can do ToListAsync() before applying the filter, but it'll get all the records from DB then filter on client side. But I want to filter the data on server side.
IF you were using Linq To Objects that should work but you are doing Linq To SQL and in this case you must think on how you would translate this function into a valid SQL statement. Question yourself: How could I pass this function call in a SQL Statement? Depending what you do on the body of your expression, you cannot translate it to SQL, you must be simpler sometimes.
Candidate solution
Add PredicateBuilder class on your project. It will give you easily logical operators to you handle expressions.
http://www.albahari.com/nutshell/predicatebuilder.aspx
Try to define an expression and pass it as argument on Where method of your query method chain. For sample (read the comments):
// define a expression with default condition
Expression<Func<Project, bool>> filter = f => !f.IsDeleted;
// check conditions to add new filtes with `And` logical operator
if (!CreatorId.Equals(0))
filter = filter.And(f => f.CreatedBy.Equals(CreatorId));
else if (!VerticalMarketId.Equals(0))
filter = filter.And(f => f.VerticalMarketsId.Equals(VerticalMarketId));
else if (!ProductCategoryId.Equals(0))
filter = filter.And(f => f.ProductCategoriesId.Equals(ProductCategoryId));
// apply the filter on the query and execute it
getProjects = await _context.Projects.Where(filter)
.Include(PC => PC.ProjectComments.Where(x => !x.IsDeleted))
.Include(SP => SP.SharedProjects)
.AsNoTracking()
.ToListAsync();
Note: I didn't test this code and it probably should be fixed somehow!
Important tips on Linq To SQL:
Logical operators are ok and tend to be translated fine to sql;
Where(x => x.Children.Any(j => j.Children.Any())), each Any call generates a subquery on query scope, be careful with it given it can compromise your database performance.
If you just need to check the existence of an item, use queryable.Any(expression).
If you need to check and then do something, prefer using queryable.FirstOrDefault(expression) and check if the result is null before using.
Use paging with .Take(int) and .Skip(int).
Always concrete your queries by calling .ToList(), .ToArray() or async versions of these methods. Avoid passing queryable in the top layers (query can be executed out of the scope you want).
I figured it out by creating a simple Expression as fololows:
private static Expression<Func<Project, bool>> ProjectFilterExpression(
int creatorId,
int verticalMarketId,
int productCategoryId)
{
Expression<Func<Project, bool>> projectFilterExpression = pfe =>
!pfe.IsDeleted
//CreatorId Filter
&& (creatorId.Equals(0) || pfe.CreatedBy.Equals(creatorId))
//Vertical Market Filter
&& (verticalMarketId.Equals(0) || pfe.VerticalMarketsId.Equals(verticalMarketId))
// Product Category Filter
&& (productCategoryId.Equals(0) || pfe.ProductCategoriesId.Equals(productCategoryId));
return projectFilterExpression;
}
Then I call this static method inside my filter method.
var filter = ProjectFilterExpression(CreatorId, VerticalMarketId, ProductCategoryId);
And finally I applied this filter in my LINQ where clause
getProjects = await _context.Projects.Where(filter).AsNoTracking().ToListAsync();
It's working totally fine.

Lambda expression - For a Select New

I am trying to create a custom collection from an IQueryable object, where i am trying to perform a select statement but getting an error cannot convert to store expression. I am new to Lambda Expression. Kindly help me how to fix this problem.
Getting error at line c.Event.FirstUpper()
public static string FirstCharToUpper(string input)
{
if (String.IsNullOrEmpty(input))
return string.Empty;
var trimmed = input.Trim();
return trimmed.First().ToString().ToUpper() + trimmed.Substring(1);
}
public static Expression<Func<string, string>> GetFirstCaseToUpperExpression()
{
var expression = NJection.LambdaConverter.Fluent.Lambda.TransformMethodTo<Func<string, string>>()
.From(() => StringFormatter.FirstCharToUpper)
.ToLambda();
return expression;
}
Calling the Expression
return new List<LoggerModel>(
logDB.PELoggers
.Where(c => (c.SubscriberCode == SubscriberCode)).OrderByField(sortBy, ascendingOrder).Select(c => new LoggerModel()
{
DateTime = c.DateTime.Value,
Event = c.Event.FirstUpper()
})
I suppose you are using Entity Framework or a smiliar O/R mapper.
Think about what you are doing here: you are writing a LINQ query that should be executed against your database. To do this, it will translate your LINQ query into a SQL query which will then be executed against your database.
But FirstCharToUpper() is a custom method in your code. Your database does not know anything about it, so your O/R mapper's LINQ provider cannot translate it into anything meaningful in SQL, hence you get the error.
So what you need to do is to first "finish" the query against your database to have the results in-memory and after that, apply any further processing that can only be done within the boundaries of your code on that in-memory collection.
You can do this simply by inserting .AsEnumerable() in your LINQ query before you do the select with your custom expression:
logDB.PELoggers
.Where(c => (c.SubscriberCode == SubscriberCode))
.OrderByField(sortBy, ascendingOrder)
.AsEnumerable()
.Select(c => new LoggerModel()
{
DateTime = c.DateTime.Value,
Event = c.Event.FirstUpper()
})
When calling AsEnumerable(), the query against your database will be executed and the results are copied into an IEnumerable in memory. The Select() afterwards will now already be executed against the in-memory collection and not against the database anymore, thus it can use your custom FirstCharToUpper() method.
Edit based on your comments below:
Everything above is still valid, but in the comments you said your function needs to return IQueryable. In your case, what your FirstCharToUpper() method is doing is pretty simple and the LINQ-to-Entities provider does support methods like ToUpper and Substring. So I'd recommend to simply get rid of your helper method and instead write your LINQ query to do just that with methods that Entity Framework can translate to valid SQL:
logDB.PELoggers
.Where(c => (c.SubscriberCode == SubscriberCode))
.OrderByField(sortBy, ascendingOrder)
.Select(c => new LoggerModel()
{
DateTime = c.DateTime.Value,
Event = c.Event.Substring(0, 1).ToUpper()
+ c.Event.Substring(1)
})
This will result in a SQL query that will already return the content in Event with an uppercase first letter right from the database.
To also support the IsNullOrEmpty check and the Trim you are doing (both also supported by LINQ-to-Entities) I recommend to change the lambda syntax to the LINQ query syntax so you can use the let statement for the trimming, which makes the code cleaner:
from c in logDB.PELoggers
let trimmedEvent = c.Event.Trim()
where c.SubscriberCode == SubscriberCode
select new LoggerModel()
{
DateTime = c.DateTime.Value,
Event = !string.IsNullOrEmpty(trimmedEvent)
? trimmedEvent.Substring(0, 1).ToUpper()
+ trimmedEvent.Substring(1)
: string.Empty
};
In case you do not want to have this done in the LINQ query, you would need to do the uppercasing at some point later when your query against the DB has been executed, for example right in the View that will show your data. Or one option could be to apply the uppercasing in the Event property setter of your LoggerModel:
public class LoggerModel
{
// ...
private string event;
public string Event
{
get { return event; }
set { event = FirstCharToUpper(value); }
}
// ...
}
But there is no way to make custom functions work inside LINQ-to-Entities queries.

C# NET LINQ to Entities does not recognize the method

I'm getting this error when trying to do a linq query:
LINQ to Entities does not recognize the method 'System.Collections.Generic.IEnumerable`1[FEI.Entities.EF6.Tables.HORSE_IDENTITY_GENDER] GetHorseIdentityGenderQuery(FEI.Entities.EF6.Tables.HORSE_DOCUMENT_PART)' method, and this
method cannot be translated into a store expression.
I've read a lots of previous questions where people get the same error, but I understand that it's because LINQ to Entities requires the whole linq query expression to be translated to a server query, and therefore you can't call an outside method in it. I haven't been able to convert my scenario into something that works yet, and my brain is starting to melt down, so I was hoping someone could point me in the right direction. We're using Entity Framework and the specification pattern (and I'm new to both).
Here's the code that uses the specification:
HORSE_DOCUMENT HorseDocForPart = Bll.GetHorseDocumentForPartUpload(horseId, identityType);
Here's the code that provides from method GetHorseDocumentForPartUpload
public HORSE_DOCUMENT GetHorseDocumentForPartUpload(int horseID, HorseDocGender identityType)
{
// Get the unique horse document
var horseDoc = Tables
.HORSE_DOCUMENT
.Where(hd =>
hd.HORSE_UID == horseID &&
hd.HORSE_IDENTITY_TYPE.HORSE_IDENTITY_TYPE_FULL_CODE == identityType.ToString() &&
!hd
.HORSE_DOCUMENT_PART
.Any(hdp =>
hdp.VALIDATION_STATUS != HorseDocPartStatus.REFUSED.ToString() &&
GetHorseIdentityGenderQuery(hdp).Any(hig => hig.IS_FULL)
)
).SingleOrDefault();
return horseDoc;
}
Here's the last code :
public IEnumerable<HORSE_IDENTITY_GENDER> GetHorseIdentityGenderQuery(HORSE_DOCUMENT_PART horseDocPart)
{
var possibleDocs = Tables
.DOCUMENTs
.Where(doc => doc.DOC_OWNER_UID == horseDocPart.HORSE_DOCUMENT_PART_UID);
return horseDocPart
.HORSE_DOCUMENT
.HORSE_IDENTITY_TYPE
.HORSE_IDENTITY_GENDER
.Join(
possibleDocs,
hig => hig.DOCUMENT_GENDER_CODE.DOCUMENT_GENDER_CODE_UID,
doc => doc.DOCUMENT_GENDER_CODE_UID,
(dgc, doc) => dgc
);
}
You return IEnumerable from the method
public IEnumerable<HORSE_IDENTITY_GENDER> GetHorseIdentityGenderQuery(...)
This is deferred but using IEnumerable does not allow Linq-To-Sql execution, you should be using IQueryable as such.
public IQueryable<HORSE_IDENTITY_GENDER> GetHorseIdentityGenderQuery(...)
Please see more detailed explanation from Returning IEnumerable<T> vs. IQueryable<T>

Convert Method to Linq Expression for query

In our application we want to have standard methods for various conditions in our database. For instance, we have different types of transactions, and we want to create standard methods for retrieving them within other queries. However, this gives us the error:
Method '' has no supported translation to SQL
The method might look like this:
public static bool IsDividend(this TransactionLog tl)
{
return tl.SourceTypeID == (int)JobType.Dividend || tl.SourceTypeID == (int)JobType.DividendAcct;
}
To be used as such:
var dividends = ctx.TransactionLogs.Where(x => x.IsDividend());
Of course, if I copy the logic from IsDividend() into the Where clause, this works fine, but I end up duplicating this logic many places and is hard to track down if that logic changes.
I think if I would convert this to an expression like this it would work, but this is not as preferable a setup as being able to use methods:
public Expression<Func<TransactionLog, bool>> IsDividend = tl => tl.SourceTypeID == (int)JobType.Dividend || tl.SourceTypeID == (int)JobType.DividendAcct;
var dividends = ctx.TransactionLogs.Where(IsDividend);
Is there a way to force Linq to evaluate the method as an expression? Or to "transform" the method call into an expression within a linq query? Something like this:
var dividends = ctx.TransactionLogs.Where(tl => ToExpression(tl.IsDividend));
We are using Linq-to-SQL in our application.
Well having static property containing the expressions seems fine to me.
The only way to make it work with Methods would be to create a method which returns this expression, and then call it inside where:
public class TransactionLog
{
Expression<Func<TransactionLog, bool>> IsDividend() {
Expression<Func<TransactionLog, bool>> expression = tl => tl.SourceTypeID == (int)JobType.Dividend || tl.SourceTypeID == (int)JobType.DividendAcct;
return expression;
}
}
public class TransactionLogExtension
{
Expression<Func<TransactionLog, bool>> IsDividend(this TransactionLog log) {
Expression<Func<TransactionLog, bool>> expression = tl => tl.SourceTypeID == (int)JobType.Dividend || tl.SourceTypeID == (int)JobType.DividendAcct;
return expression;
}
}
and use it via
var dividends = ctx.TransactionLogs.Where(TransactionLog.IsDividend());
or as extension method
var dividends = ctx.TransactionLogs.Where(x.IsDividend());
But none of it is will work with var dividends = ctx.TransactionLogs.Where(x => x.IsDividend()); because x => x.IsDividend(); itself is an expression tree and your database provider can't translate "IsDividend" into an SQL statement.
But the other two options will at least allow you to pass in parameters (which doesn't work if you store the Expressions as instance or static properties).
I think that LINQ to SQL doesn't fully supports even common and build-in functions. (At least EF does not do it). And moreover - when it deals with user defined methods. I predict that your variant with expression will fall as well as the variant with method call unless you call it after enumeration (ToList or similar method). I suggest to keep the balance between 1) stored procedures at server, 2) common conditions hardcoded in Where clauses in C#, 3) expression trees generation in C#. All these points are relatively complex, for sure (and to my mind there is no easy and general solution).
Try using Extension Methods, like so:
public static class TransactionLogExtensions {
public static IQueryable<TransactionLog> OnlyDividends(this IQueryable<TransactionLog> tl)
{
return tl.Where(t=>t.SourceTypeID == (int)JobType.Dividend || t.SourceTypeID == (int)JobType.DividendAcct);
}
}
Call it like so:
var dividends=db.TransactionLogs.OnlyDividends();
or
var dividends=db.TransactionLogs.OnlyDividends().OrderBy(...);
For this scenario you can use Func. Linq works very good with those.
Here is the simple example of using Func in Linq query. Feel free to modify and use.
Func<int,bool> isDivident = x => 3==x;
int[] values = { 3, 7, 10 };
var result = values.Select (isDivident );

Cannot implicity convert type System.Linq.Expression<System.Func<Object, bool>> to bool

actually I have an Expression like this that work very well in the case of Linq to Entity
public static Expression<Func<Tender, bool>> GetPublic()
{
var now = DateTime.Now.GetEasternStandardDateTime();
return tender => tender.EndDate > DateTime.Now &&
tender.IsClosed == false &&
tender.IsCancel == false &&
tender.PrivacyLevelId == 1;
}
I can use the expression like this : _repositoryObject.Where(GetPublic()) and I will get results.
But I cannot use the same expression when I have to do Linq to SQL pattern like this
var results = from t in Tenders
where Tender.GetPublic()
select t;
I have this error
Cannot implicity convert type
System.Linq.Expression> to bool for Linq to
SQL
and I don't know why...
Karine
Firstly, I suspect you actually mean you can call it as:
_repositoryObject.Where(GetPublic())
It's important that you call the method to get the expression tree.
Your query expression is being converted into:
var results = Tenders.Where(t => Tender.GetPublic());
... which isn't what you're looking for. Basically, query expressions are translated into method calls using lambda expressions. You don't want that in this case, so you shouldn't use a query expression.
If you want to do other things with a query expression, you can always mix and match, e.g.
var results = from t in Tenders.Where(Tender.GetPublic())
where t.SomeOtherProperty
select t.SomethingElse;
If you change your method to take an IQueryable<Tender>, and return another IQueryable<Tender> where your criteria are added, then it might work.
public static IQueryable<Tender> AddPublicCriteria(this IQueryable<Tender> tenderQuery) {
// I've omitted your call to DateTime.Now.GetEasternStandardDateTime()
// since you do not use it.
return tenderQuery
.Where(tender => tender.EndDate > DateTime.Now &&
tender.IsClosed == false &&
tender.IsCancel == false &&
tender.PrivacyLevelId == 1
);
}
var query = Tenders; // Original table or data-set
query = query.AddPublicCriteria(); // Add criteria
var results = query.ToList(); // Run query and put the results in a list

Categories