String.IsNullOrWhiteSpace in LINQ Expression - c#

I have the following code:
return this.ObjectContext.BranchCostDetails.Where(
b => b.TarrifId == tariffId && b.Diameter == diameter
|| (b.TarrifId==tariffId && !string.IsNullOrWhiteSpace(b.Diameter))
|| (!b.TarrifId.HasValue) && b.Diameter==diameter);
And I get this error when I try to run the code:
LINQ to Entities does not recognize the method 'Boolean
IsNullOrWhiteSpace(System.String)' method, and this method cannot be
translated into a store expression."
How can I solve this problem and write code better than this?

You need to replace
!string.IsNullOrWhiteSpace(b.Diameter)
with
!(b.Diameter == null || b.Diameter.Trim() == string.Empty)
For Linq to Entities this gets translated into:
DECLARE #p0 VarChar(1000) = ''
...
WHERE NOT (([t0].[Diameter] IS NULL) OR (LTRIM(RTRIM([t0].[Diameter])) = #p0))
and for Linq to SQL almost but not quite the same
DECLARE #p0 NVarChar(1000) = ''
...
WHERE NOT (LTRIM(RTRIM([t0].[TypeName])) = #p0)

In this case it is important to distinguish between IQueryable<T> and IEnumerable<T>. In short IQueryable<T> is processed by a LINQ provider to deliver an optimized query. During this transformation not all C# statements are supported, as it either is not possible to translate them to a back-end specific query (e.g. SQL) or because the implementer did not foresee the need for the statement.
In contrast IEnumerable<T> is executed against the concrete objects and, therefore, will not be transformed. So, it is quite common that constructs, which are useable with IEnumerable<T>, cannot be used with IQueryable<T> and also that IQueryables<T> backed by different LINQ providers do not support the same set of functions.
However, there are some workarounds (like Phil's answer), which modify the query. Also, as a more general approach it is possible to drop back to an IEnumerable<T> before continuing with the specification of the query. This, however, might have a performance hit - especially when using it on restrictions (e.g. where clauses). In contrast, when dealing with transformations the performance hit is a lot smaller, sometimes even non existent - depending on your query.
So the above code could also be rewritten like this:
return this.ObjectContext.BranchCostDetails
.AsEnumerable()
.Where(
b => b.TarrifId == tariffId && b.Diameter == diameter
|| (b.TarrifId==tariffId && !string.IsNullOrWhiteSpace(b.Diameter))
||(!b.TarrifId.HasValue) && b.Diameter==diameter
);
NOTE: Ths code will have an higher performance impact than Phil's answer. However, it shows the principle.

Use an expression visitor to detect references to string.IsNullOrWhiteSpace and break them down into a simpler expression (x == null || x.Trim() == string.Empty).
So below is an extended visitor and an extension method to make use of it. It requires no special config to use, simply call WhereEx instead of Where.
public class QueryVisitor: ExpressionVisitor
{
protected override Expression VisitMethodCall(MethodCallExpression node)
{
if (node.Method.IsStatic && node.Method.Name == "IsNullOrWhiteSpace" && node.Method.DeclaringType.IsAssignableFrom(typeof(string)))
{
//!(b.Diameter == null || b.Diameter.Trim() == string.Empty)
var arg = node.Arguments[0];
var argTrim = Expression.Call(arg, typeof (string).GetMethod("Trim", Type.EmptyTypes));
var exp = Expression.MakeBinary(ExpressionType.Or,
Expression.MakeBinary(ExpressionType.Equal, arg, Expression.Constant(null, arg.Type)),
Expression.MakeBinary(ExpressionType.Equal, argTrim, Expression.Constant(string.Empty, arg.Type))
);
return exp;
}
return base.VisitMethodCall(node);
}
}
public static class EfQueryableExtensions
{
public static IQueryable<T> WhereEx<T>(this IQueryable<T> queryable, Expression<Func<T, bool>> where)
{
var visitor = new QueryVisitor();
return queryable.Where(visitor.VisitAndConvert(where, "WhereEx"));
}
}
So if you run myqueryable.WhereEx(c=> !c.Name.IsNullOrWhiteSpace()) it will be converted to !(c.Name == null || x.Trim() == "") before being passes to whatever (linq to sql/entities) and converted to sql.

You can also use this to check for whitespace:
b.Diameter!=null && !String.IsNullOrEmpty(b.Diameter.Trim())

!String.IsNullOrEmpty(b.Diameter.Trim())
will throw exception if b.Diameter is null.
If you still want to use your statement, better use this check
!String.IsNullOrWhiteSpace(b.Diameter), IsNullOrWhiteSpace = IsNullOrEmpty + WhiteSpace

Related

Using Custom Extension Methods inside LinQ (Compare two strings)

I wanna create a comparer for LinQ that is simplified. this is my current code.
DBContext.product_type
.Where(x => x.name.ToLower()(filter.ToLower())
|| x.description..ToLower()(filter.ToLower())
|| x.online_category.description..ToLower()(filter.ToLower()))
I wanna simplify it using linQ extension method. So far this is what I got.
DBContext.product_type
.Where(x => x.name.CompareToLower(filter)
|| x.description.CompareToLower(filter)
|| x.online_category.description.CompareToLower(filter))
public static bool CompareToLower(this string str, string comparer)
{
if (str.ToLower().Contains(comparer.ToLower()))
{
return true;
}
else
{
return false;
}
}
This is the error im getting
linq to Entities does not recognize the method 'Boolean CompareToLower(System.String, System.String)' method, and this method cannot be translated into a store expression.
Extension methods like that cannot be converted to SQL Query.
The answer provided by #meysamasadi will work, but will fetch your entire table and do the comparison on client side.
If you want the comparison to occur on the database-side, the most efficient way would be to change your collation to case-insensitive (as suggested in the comments).
As an alternative you can use following code:
DBContext.product_type
.Where(x =>
EF.Functions.Like(x.Name, filter)
|| EF.Functions.Like(x.Description, filter)
|| EF.Functions.Like(x.online_category.description, filter))
** Note that using the above method you can also include wildcards in the filter and patterns
https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.dbfunctionsextensions.like?view=efcore-5.0

Is there a way to inline external functions into an EF Linq query?

Let say I have a function like this:
var filterValue = GetCurrentFilter(state);
And then an EF query:
var result = context.EntitySet.Where(x=> x.column > filterValue);
this works, but as soon as I try to inline that:
var result = context.EntitySet.Where(x=> x.column > GetCurrentFilter(state));
It does not because EF Linq tried to parse GetCurrentFilter into expression tree and is unable to do that. This is all quite understandable.
My question is, is there a way to let EF Linq know that in needs to execute the GetCurrentFilter function when it builds the tree and use its result in the tree?
Something like
var result = context.EntitySet.Where(x=> x.column > EfUtil.ResultOf(GetCurrentFilter(state)));
Since GetCurrentFilter does not have parameters that is a part of the query this should be technically possible to do that if EF Linq can support it that is. I'm suspecting that I'm just missing the correct syntax for that.
Make GetCurrentFilter a (read only) property instead of a method. EF will evaluate properties to their values, rather than trying to translate them into SQL, unlike methods.
The only other road that you have is to traverse the entire expression tree, search for usage of your ResultOf method, evaluate its parameter to a value, and then inline that value where the ResultOf call once was, rebuiding the query around that value.
In order for this to work it means you need to not only wrap the code you want to inline in a call to EfUtil.ResultOf, but it also means calling a method on the query itself to force it to go back and evaluate it:
public class EfUtil
{
public static T ResultOf<T>(T value)
{
return value;
}
}
//Note this could probably use a better name
public static IQueryable<T> EvaluateResults<T>(this IQueryable<T> query)
{
return query.Provider.CreateQuery<T>(
new ExpressionEvaluator().Visit(query.Expression));
}
internal class ExpressionEvaluator : ExpressionVisitor
{
protected override Expression VisitMethodCall(MethodCallExpression m)
{
if (m.Method.Name == "ResultOf" && m.Method.DeclaringType == typeof(EfUtil))
{
Expression target = m.Arguments[0];
object result = Expression.Lambda(target)
.Compile()
.DynamicInvoke();
return Expression.Constant(result, target.Type);
}
else
return base.VisitMethodCall(m);
}
}
This would allow you to write:
var result = context.EntitySet.Where(x=> x.column > EfUtil.ResultOf(GetCurrentFilter(state)))
.EvaluateResults();
It would then evaluate GetCurrentFilter(state) on the client side and inline the result as a constant into the query.
As a slightly simpler test, we can write the following:
var query = new[] { 1, 2, 3 }
.AsQueryable()
.Where(x => x > EfUtil.ResultOf(Math.Max(1, 2)))
.EvaluateResults();
Console.WriteLine(query.ToString());
And it will print out:
System.Int32[].Where(x => (x > 2))
Which is exactly what we want.
Note that the use of the lambda's parameter (x in these examples) cannot be used anywhere within the call to EfUtil.ResultOf or the code won't work, and couldn't possibly be made to work (although we could generate a better error message if we cared enough).

How to stay DRY whilst using LINQ to Entities and helper methods?

Lets say that I have a particular way of deciding whether some strings "match", like this:
public bool stringsMatch(string searchFor, string searchIn)
{
if (string.IsNullOrEmpty(searchFor))
{
return true;
}
return searchIn != null &&
(searchIn.Trim().ToLower().StartsWith(searchFor.Trim().ToLower()) ||
searchIn.Contains(" " + searchFor));
}
I would like to pull matches out of a database using Linq To Entities and this helper. However, when I try this:
IQueryable<Blah> blahs = query.Where(b => stringsMatch(searchText, b.Name);
I get "LINQ to Entities does not recognize the method..."
If I re-write the code as:
IQueryable<Blah> blahs = query.Where(b =>
string.IsNullOrEmpty(searchText) ||
(b.Name != null &&
(b.Name.Trim().ToLower().StartsWith(searchText.Trim().ToLower()) ||
b.Name.Contains(" " + searchText)));
Which is logically equivalent, then things work fine. The problem is that the code isn't as readable, and I have to re-write it for each different entity I want to match.
As far as I can tell from questions like this one, what I want to do is impossible at the moment, but I'm hoping that I'm missing something, am I?
If all the 'blahs' (classes) that you will be filtering have the same structure, you can use a simple method like this. The main difference is that it returns an Expression that Linq should be able to parse and it brings in the whole instance and filters on Name instead of bringing in just the string name.
public static Expression<Func<T, bool>> BuildStringMatch<T>(string searchFor) where T : IHasName
{
return b =>
string.IsNullOrEmpty(searchFor) ||
(b.Name != null &&
(b.Name.Trim().ToLower().StartsWith(searchFor.Trim().ToLower()) ||
b.Name.Contains(" " + searchFor)));
}
You can use that method like this:
IQueryable<Blah> blahs = query.Where(BuildStringMatch<Blah>(searchText));
That assumes all your classes that you'd want to filter on implement some interface such as:
public interface IHasName
{
string Name { get; }
}
If you want to be filtering on different properties, I don't think that's something you can do with simple code like this. I believe you'll need to build the Expression yourself with reflection (or with the help of a library that uses reflection) - it's still possible but much more difficult.
Edit: It sounds like you need dynamic behavior, so I borrowed some logic from dtb's answer to this question and came up with this:
public static Expression<Func<T, bool>> BuildStringMatch<T>(Expression<Func<T, string>> property, string searchFor)
{
var searchForExpression = Expression.Constant(searchFor, typeof(string));
return
Expression.Lambda<Func<T, bool>>(
Expression.OrElse(
Expression.Call(typeof(string), "IsNullOrEmpty", null, searchForExpression),
Expression.AndAlso(
Expression.NotEqual(property.Body, Expression.Constant(null, typeof(string))),
Expression.OrElse(
Expression.Call(Expression.Call(Expression.Call(property.Body, "Trim", null), "ToLower", null), "StartsWith", null,
Expression.Call(Expression.Call(searchForExpression, "Trim", null), "ToLower", null)),
Expression.Call(property.Body, "Contains", null, Expression.Call(typeof(string), "Concat", null, Expression.Constant(" "), searchForExpression))
)
)
),
property.Parameters
);
}
You would use it like:
IQueryable<Blah> blahs2 = query.Where(BuildStringMatch<Blah>(b => b.Name, searchText));
It's long and verbose but you can see how it's similiar to the original method written in straight C# code. Note: I didn't test this code, so there could be a few small problems - but that is the general idea.
Using a freely available library called LINQKit (as mentioned by #Eranga) this task becomes reasonable. Using LINQKit the code I have now looks like:
protected Expression<Func<T, bool>> stringsMatch(string searchFor, Expression<Func<T, string>> searchIn)
{
if (string.IsNullOrEmpty(searchFor))
{
return e => true;
}
return
e =>
(searchIn.Invoke(e) != null &&
(searchIn.Invoke(e).Trim().ToLower().StartsWith(searchFor.Trim().ToLower()) ||
searchIn.Invoke(e).Contains(" " + searchFor)));
}
And needs to be called like this (note the AsExpandable() call)
IQueryable<Blah> blahs = query().AsExpandable().Where(StringsMatch(searchText, b => b.Name));
The magic parts are the searchIn.Invoke(e) calls and the use of AsExpandable() which adds a wrapper layer that allows them to work.
The AsExpandable() bit is explained in detail by the original author here.
Note that I'm still a bit hazy on some of the details of expressions, so please add a comment/edit this answer if it can be made better/shorter/clearer.

Method 'Boolean Contains..' has no supported translation to SQL

i have this in my query:
var results = (from urls in _context.Urls
join documents in _context.Documents on urls.UrlId equals documents.DocumentId
let words = (from words in _context.Words
join hits in _context.Hits on words.WordId equals hits.WordId
where hits.DocumentId == documents.DocumentId
select words.Text).AsEnumerable<string>()
where urls.ResolvedPath.Contains(breakedQuery, KeywordParts.Url, part) ||
documents.Title.Contains(breakedQuery, KeywordParts.Title, part) ||
documents.Keywords.Contains(breakedQuery, KeywordParts.Keywords, part) ||
documents.Description.Contains(breakedQuery, KeywordParts.Description, part) ||
words.Contains(breakedQuery, KeywordParts.Content, part) ...
and Contains extension method:
for strings
public static bool Contains(this string source, IEnumerable<string> values, KeywordParts valuePart, KeywordParts part)
{
if (!string.IsNullOrWhiteSpace(source))
return source.Split(' ').AsEnumerable<string>().Contains(values, valuePart, part);
return false;
}
for enumerables (main method)
public static bool Contains(this IEnumerable<string> source, IEnumerable<string> values, KeywordParts valuePart, KeywordParts part)
{
if (source != null && source.Count() > 0 &&
values != null && values.Count() > 0 &&
(part == KeywordParts.Anywhere || valuePart == part))
{
foreach (var value in values)
{
var has = false;
var none = (value.StartsWith("-"));
string term = value.Replace("-", "");
if (none)
has = source.Any(q => !q.Contains(value));
else
has = source.Any(q => q.Contains(values));
if (has)
return has;
}
}
return false;
}
and using Contains method throws exception NotSupportedException: Method 'Boolean Contains(String, IEnumerable`1[String], KeywordParts, KeywordParts)' has no supported translation to SQL.
actually i want to check each indexed document if have at lease one of specified conditions
You can't just write your own methods and call them from your query expression - the query translator has no idea what that method's meant to do.
You could force the where clause to be executed in .NET after fetching the documents and words, potentially... although obviously that means fetching all the joined data from the database. Would that be okay?
To do that, you'd want something like:
var tmpQuery = (from urls in _context.Urls
join documents in _context.Documents
on urls.UrlId equals documents.DocumentId
let words = (from words in _context.Words
join hits in _context.Hits
on words.WordId equals hits.WordId
where hits.DocumentId == documents.DocumentId
select words.Text)
select new { urls, documents, words };
var query = from r in tmpQuery.AsEnumerable()
let urls = r.urls.ToList()
let words = r.words.ToList()
let documents = r.documents.ToList()
where urls.ResolvedPath.Contains(breakedQuery,
KeywordParts.Url, part) ||
documents.Title.Contains(breakedQuery,
KeywordParts.Title, part) ||
documents.Keywords.Contains(breakedQuery,
KeywordParts.Keywords, part) ||
documents.Description.Contains(breakedQuery,
KeywordParts.Description, part) ||
words.Contains(breakedQuery, KeywordParts.Content, part)
select new { urls, words, documents };
My understanding and someone please correct me if I am wrong, the problem is that when using an extension method with Linq to SQL the extension method is not executed as .NET code like the extension methods you have in your question.
The Linq to SQL extension methods return expression trees, which the Linq to SQL engine then parses and generates the appropriate SQL query to satisfy the expression tree.
Another way of implementing this is to write a scalar UDF in the database that implements this functionality. Then drag that UDF onto the LINQ-to-SQL designer, which will give you access to your UDF via the data-context. Then you can use things like:
where _context.MyContains(documents.Title, breakedQuery,
KeywordParts.Title, part);
and which will call the UDF after translation (i.e. WHERE dbo.MyContains(...))
An interesting fact about this is that I get the following error when running in .NET 4.0 on my development machine:
"Method 'Boolean Contains(Int32)' has no supported translation to SQL."
But it runs just fine in the production environment that utilizes .NET 3.5.
I am guessing that it is the difference in versions between the two environments. However, it is a fact that I get the error on my development machine, but the queries DO RUN on the production environment and the LINQ query does contain the following code
var resultParts = (
from l in tempDc.LineItems
from wo in tempDc.WorkOrders
where l.WorkOrderNumber == wo.WorkOrderNumber &&
l.OrderID == wo.OrderID &&
workOrderSerialNumbers.Contains(wo.SerialNumber) &&
l.Part.PartTypeID == (int)PartTypes.InventoryPart
orderby l.OrderID_WO, l.WorkOrderNumber
select new PickReportPartDto()
{...
Where 'workOrderSerialNumbers is a List.
This is possible if you take your enumerable and add to a .ToList() prior to the .Contains(r.SomeId). I was searching on this error and originally had an ICollection with a .Contains(r.SomeId) which would throw this exception, however doing a .ToList() resolved my problem. Hope this helps someone else.
Note: I believe there is an expression tree max to Linq2Sql... so a large list may cause you another headache. Just a thought and something to watch out for.
This is Linq2Sql code:
This is the resulting SQL:

Dynamic WHERE clause in LINQ

What is the best way to assemble a dynamic WHERE clause to a LINQ statement?
I have several dozen checkboxes on a form and am passing them back as: Dictionary<string, List<string>> (Dictionary<fieldName,List<values>>) to my LINQ query.
public IOrderedQueryable<ProductDetail> GetProductList(string productGroupName, string productTypeName, Dictionary<string,List<string>> filterDictionary)
{
var q = from c in db.ProductDetail
where c.ProductGroupName == productGroupName && c.ProductTypeName == productTypeName
// insert dynamic filter here
orderby c.ProductTypeName
select c;
return q;
}
(source: scottgu.com)
You need something like this? Use the Linq Dynamic Query Library (download includes examples).
Check out ScottGu's blog for more examples.
I have similar scenario where I need to add filters based on the user input and I chain the where clause.
Here is the sample code.
var votes = db.Votes.Where(r => r.SurveyID == surveyId);
if (fromDate != null)
{
votes = votes.Where(r => r.VoteDate.Value >= fromDate);
}
if (toDate != null)
{
votes = votes.Where(r => r.VoteDate.Value <= toDate);
}
votes = votes.Take(LimitRows).OrderByDescending(r => r.VoteDate);
You can also use the PredicateBuilder from LinqKit to chain multiple typesafe lambda expressions using Or or And.
http://www.albahari.com/nutshell/predicatebuilder.aspx
A simple Approach can be if your Columns are of Simple Type like String
public static IEnumerable<MyObject> WhereQuery(IEnumerable<MyObject> source, string columnName, string propertyValue)
{
return source.Where(m => { return m.GetType().GetProperty(columnName).GetValue(m, null).ToString().StartsWith(propertyValue); });
}
It seems much simpler and simpler to use the ternary operator to decide dynamically if a condition is included
List productList = new List();
productList =
db.ProductDetail.Where(p => p.ProductDetailID > 0 //Example prop
&& (String.IsNullOrEmpty(iproductGroupName) ? (true):(p.iproductGroupName.Equals(iproductGroupName)) ) //use ternary operator to make the condition dynamic
&& (ID == 0 ? (true) : (p.ID == IDParam))
).ToList();
I came up with a solution that even I can understand... by using the 'Contains' method you can chain as many WHERE's as you like. If the WHERE is an empty string, it's ignored (or evaluated as a select all). Here is my example of joining 2 tables in LINQ, applying multiple where clauses and populating a model class to be returned to the view. (this is a select all).
public ActionResult Index()
{
string AssetGroupCode = "";
string StatusCode = "";
string SearchString = "";
var mdl = from a in _db.Assets
join t in _db.Tags on a.ASSETID equals t.ASSETID
where a.ASSETGROUPCODE.Contains(AssetGroupCode)
&& a.STATUSCODE.Contains(StatusCode)
&& (
a.PO.Contains(SearchString)
|| a.MODEL.Contains(SearchString)
|| a.USERNAME.Contains(SearchString)
|| a.LOCATION.Contains(SearchString)
|| t.TAGNUMBER.Contains(SearchString)
|| t.SERIALNUMBER.Contains(SearchString)
)
select new AssetListView
{
AssetId = a.ASSETID,
TagId = t.TAGID,
PO = a.PO,
Model = a.MODEL,
UserName = a.USERNAME,
Location = a.LOCATION,
Tag = t.TAGNUMBER,
SerialNum = t.SERIALNUMBER
};
return View(mdl);
}
Just to share my idea for this case.
Another approach by solution is:
public IOrderedQueryable GetProductList(string productGroupName, string productTypeName, Dictionary> filterDictionary)
{
return db.ProductDetail
.where
(
p =>
(
(String.IsNullOrEmpty(productGroupName) || c.ProductGroupName.Contains(productGroupName))
&& (String.IsNullOrEmpty(productTypeName) || c.ProductTypeName.Contains(productTypeName))
// Apply similar logic to filterDictionary parameter here !!!
)
);
}
This approach is very flexible and allow with any parameter to be nullable.
You could use the Any() extension method. The following seems to work for me.
XStreamingElement root = new XStreamingElement("Results",
from el in StreamProductItem(file)
where fieldsToSearch.Any(s => el.Element(s) != null && el.Element(s).Value.Contains(searchTerm))
select fieldsToReturn.Select(r => (r == "product") ? el : el.Element(r))
);
Console.WriteLine(root.ToString());
Where 'fieldsToSearch' and 'fieldsToReturn' are both List objects.
This is the solution I came up with if anyone is interested.
https://kellyschronicles.wordpress.com/2017/12/16/dynamic-predicate-for-a-linq-query/
First we identify the single element type we need to use ( Of TRow As DataRow) and then identify the “source” we are using and tie the identifier to that source ((source As TypedTableBase(Of TRow)). Then we must specify the predicate, or the WHERE clause that is going to be passed (predicate As Func(Of TRow, Boolean)) which will either be returned as true or false. Then we identify how we want the returned information ordered (OrderByField As String). Our function will then return a EnumerableRowCollection(Of TRow), our collection of datarows that have met the conditions of our predicate(EnumerableRowCollection(Of TRow)). This is a basic example. Of course you must make sure your order field doesn’t contain nulls, or have handled that situation properly and make sure your column names (if you are using a strongly typed datasource never mind this, it will rename the columns for you) are standard.
System.Linq.Dynamic might help you build LINQ expressions at runtime.
The dynamic query library relies on a simple expression language for formulating expressions and queries in strings.
It provides you with string-based extension methods that you can pass any string expression into instead of using language operators or type-safe lambda extension methods.
It is simple and easy to use and is particularly useful in scenarios where queries are entirely dynamic, and you want to provide an end-user UI to help build them.
Source: Overview in Dynamic LINQ
The library lets you create LINQ expressions from plain strings, therefore, giving you the possibility to dynamically build a LINQ expression concatenating strings as you require.
Here's an example of what can be achieved:
var resultDynamic = context.Customers
.Where("City == #0 and Age > #1", "Paris", 50)
.ToList();

Categories

Resources