Ormlite + PredicateBuilder "variable referenced from scope '', but it is not defined" - linq

When I attempt the following:
public List<MatterViewModel> ReturnMatchingMatters(IEnumerable<string> matterNames)
{
var filter = PredicateBuilder.True<tblMatter>();
filter = x => matterNames.Any(mattername => mattername.ToLowerInvariant() == x.Matter.ToLowerInvariant());
return this.dal.DB.GetList<MatterViewModel>(OrmLiteConfig.DialectProvider.ExpressionVisitor<tblMatter>().Where(filter).ToSelectStatement());
}
I receive the error:
variable 'x' of type '[...]tblMatter' referenced from scope '', but it is not defined
([...] mine)
Essentially, what I'm trying to accomplish is to have the predicate return true if the matter string is contained within any of the matters.
What am I missing? Do I need to do some sort of foreach with a temp variable?

The following code accomplishes what I need to do, though might not be the prettiest.
public List<string> ReturnMatchingMatters(IEnumerable<string> matterNames)
{
var filter = PredicateBuilder.True<tblMatter>();
filter = x => Sql.In(x.Matter, matterNames);
SqlExpressionVisitor<tblMatter> ev = OrmLiteConfig.DialectProvider.ExpressionVisitor<tblMatter>();
ev.Select("select Matter from tblmatter");
ev.Where(filter);
return this.dal.DB.GetList<string>(ev.ToSelectStatement());
}

Related

Linq IEqualityComparer<string> Ignore Case [duplicate]

This question already has answers here:
Case insensitive 'Contains(string)'
(29 answers)
Closed 2 years ago.
I am sorting a list of elements:
var matchEle = listOfElements.Where(e => e.Properties().Any(p => p.Name.Contains("Key", Asking for IEqualityComparer))).First();
I am used to just going straight to a StringComparer, OrdinalIgnoreCase or CurrentCultureIgnoreCase, however when calling Contains() in this context, it is asking for an IEqualityComparer. I imagine because of the data structure/level. I saw an example of how to set up an IEqualityComparer such as
strEqualityComparer = new IEqualityComparer();
and defining the class for strEqualityComparer but I am not sure beyond that. Can someone help me get my linq statement to work with an ignore case?
Update:
Just so I'm clear here is an example of the data structure:
listOfElements = [element1, element2, etc..]
element1.Properties = ["Prop1", "Key1", "Prop2", "Key2", etc.]
I need to extract the elements which pass the filter if any of its properties has a value containing the keyword, in this case "Key" therefore it cannot be .Equals or IndexOf.
Update as per comment
Search string inside another string:
var matchEle = listOfElements
.Where(e => e.Properties().Any(p => p.Name.IndexOf("Key", System.StringComparison.OrdinalIgnoreCase) >= 0))
.First();
Old solutions
You have two options, that depends on Name type:
1 - Without IEqualityComparer, and if Name in Properties is a string. replace Contains by Equals like :
var matchEle = listOfElements
.Where(e => e.Properties().Any(p => p.Name.Equals("Key", StringComparison.OrdinalIgnoreCase)))
.First();
2 - With IEqualityComparer, and if Name in Properties is a list of string:
2.1 : Create a custom comparer, like:
public class StringIEqualityComparer : IEqualityComparer<string>
{
public bool Equals(string x, string y)
{
return x.Equals(y, StringComparison.OrdinalIgnoreCase);
}
public int GetHashCode(string obj)
{
return obj.GetHashCode();
}
}
2.2 : change little your query to :
var matchEle = listOfElements
.Where(e => e.Properties().Any(p => p.Name.Contains("Key", new StringIEqualityComparer())))
.First();
I hope this helps you.

ef core - multi tenancy using global filters

OnModelCreating is called once per db context. This is a problem since the tenant Id is set per request.
How do I re-configure the global filter everytime I create an new instance of the dbcontext?
If I can't use global filter, what is the alternative way?
Update:
I needed to provide a generic filter with an expression like e => e.TenantId == _tenantId. I am using the following expression:
var p = Expression.Parameter(type, "e");
Expression.Lambda(
Expression.Equal(
Expression.Property(p, tenantIdProperty.PropertyInfo),
Expression.Constant(_tenantId))
p);
Since this is run once, _tenantId is fixed. So even if I update it, the first value is captured in the linq expression.
So my question, what is the proper way to set the right side of that equality.
With EF.Core you can actually use the following filter and syntax
protected void OnModelCreating(ModelBuilder modelBuilder)
{
var entityConfiguration = modelBuilder.Entity<MyTenantAwareEntity>();
entityConfiguration.ToTable("my_table")
.HasQueryFilter(e => EF.Property<string>(e, "TenantId") == _tenantProvider.GetTenant())
[...]
The _tenantProvider is the class responsible to get your tenant, in your case from the HttpRequest, to do it you can use HttpContextAccessor.
This is fixed with the following as the right expression
Expression.MakeMemberAccess(
Expression.Constant(this, baseDbContextType),
baseDbContextType.GetProperty("TenantId")
I use a base class for all my db contexts.
GetProperty() works as is because TenantId is public property.
and..
If you use it with soft delete, the solution is; --for ef core 3.1
internal static void AddQueryFilter<T>(this EntityTypeBuilder
entityTypeBuilder, Expression<Func<T, bool>> expression)
{
var parameterType = Expression.Parameter(entityTypeBuilder.Metadata.ClrType);
var expressionFilter = ReplacingExpressionVisitor.Replace(
expression.Parameters.Single(), parameterType, expression.Body);
var currentQueryFilter = entityTypeBuilder.Metadata.GetQueryFilter();
if (currentQueryFilter != null)
{
var currentExpressionFilter = ReplacingExpressionVisitor.Replace(
currentQueryFilter.Parameters.Single(), parameterType, currentQueryFilter.Body);
expressionFilter = Expression.AndAlso(currentExpressionFilter, expressionFilter);
}
var lambdaExpression = Expression.Lambda(expressionFilter, parameterType);
entityTypeBuilder.HasQueryFilter(lambdaExpression);
}
Usage:
if (typeof(ITrackSoftDelete).IsAssignableFrom(entityType.ClrType))
modelBuilder.Entity(entityType.ClrType).AddQueryFilter<ITrackSoftDelete>(e => IsSoftDeleteFilterEnabled == false || e.IsDeleted == false);
if (typeof(ITrackTenant).IsAssignableFrom(entityType.ClrType))
modelBuilder.Entity(entityType.ClrType).AddQueryFilter<ITrackTenant>(e => e.TenantId == MyTenantId);
Thanks to YZahringer

LINQ ForEach with Replace

I am trying to replace a string date value "01/01/1700" with an empty string in LINQ.
The date is of type string.
Something like this but I cant get it to work.
Query<Client>(sql).ToList().ForEach(x => x.DateOfBirth =
x.DateOfBirth.Replace("01/01/1700", ""));
This code works but its not LINQ.
var result = Query<Client>(sql).ToList();
foreach (var client in result)
{
if (client.DateOfBirth == "01/01/1700")
{
client.DateOfBirth = "n/a";
}
}
Thanks for your help.
The problem is the ToList(). The result is not visible in the variable you use afterwards.
Try out the following:
var list = Query<Client>(sql).ToList();
list.ForEach(l => l.DateOfBirth = l.DateOfBirth.Replace("01/01/1700", "n/a"));
Should work fine. Use the list variable afterwards.
var result = Query<Client>(sql).ToList();
result.ForEach(l => l.DateOfBirth = l.DateOfBirth.Replace("01/01/1700", "n/a"));
Your code assumes that changes made to an object in a List will be reflected in the Query<Client> that the object came from. Apparently this is not the case. One thing you could try is assigning the list before calling ForEach() and using the list from that point on:
var clients = Query<Client>(sql).ToList();
clients.ForEach(x => x.DateOfBirth = x.DateOfBirth.Replace("01/01/1700", ""));
Also, ForEach is not a LINQ operator. It is a method in the List class. Unlike LINQ operators, it will modify the list that called it and will not return anything. The way to "modify" data with LINQ is by using select:
var clients = (from client in Query<Client>(sql).ToList()
select new Client(client)
{
DateOfBirth = client.DateOfBirth.Replace("01/01/1700", "")
}).ToList();

Dynamic LINQ OR Conditions

I'm looking to use LINQ to do multiple where conditions on a collection similar to
IEnumerable<Object> items;
items.Where(p => p.FirstName = "John");
items.Where(p => p.LastName = "Smith");
except for rather than having multiple AND conditions (as with this example), I'd like to have multiple OR conditions.
EDIT
Sorry, to clarify I don't know how many of these conditions I will have so
items.Where(p => p.FirstName = "John" || p => p.LastName = "Smith")
won't work.
Basically, here's what I'm trying to do:
foreach(var name in names)
{
items = items.Where(p => p.Name == name);
}
Use PredicateBuilder:
Suppose you want to write a LINQ to SQL or Entity Framework query that implements a keyword-style search. In other words, a query that returns rows whose description contains some or all of a given set of keywords...
The ideal approach is to dynamically construct a lambda expression tree that performs an or-based predicate.
Of all the things that will drive you to manually constructing expression trees, the need for dynamic predicates is the most common in a typical business application. Fortunately, it’s possible to write a set of simple and reusable extension methods that radically simplify this task. This is the role of our PredicateBuilder class...
It sounds like your whitelist of names is only known at runtime. Perhaps try this:
string[] names = new string[] {"John", "foo", "bar"};
var matching = items.Where(x => names.Contains(x.Name));
You can use .Union() to return results that satisfy any condition.
var results = items.Where(p => p.FirstName == "John")
.Union(items.Where(p => p.LastName == "Smith"));
This is inferior to using the || operator. It isn't clear from your edit why that wouldn't work.
public static Expression<Func<T, bool>> OrTheseFiltersTogether<T>(
this IEnumerable<Expression<Func<T, bool>>> filters)
{
Expression<Func<T, bool>> firstFilter = filters.FirstOrDefault();
if (firstFilter == null)
{
Expression<Func<T, bool>> alwaysTrue = x => true;
return alwaysTrue;
}
var body = firstFilter.Body;
var param = firstFilter.Parameters.ToArray();
foreach (var nextFilter in filters.Skip(1))
{
var nextBody = Expression.Invoke(nextFilter, param);
body = Expression.OrElse(body, nextBody);
}
Expression<Func<T, bool>> result = Expression.Lambda<Func<T, bool>>(body, param);
return result;
}
Then, later:
List<Expression<Func<Person, bool>>> filters = names
.Select<string, Expression<Func<Person, bool>>>(name =>
p => p.Name == name
).ToList();
Expression<Func<Person, bool>> filterOfOrs = filters.OrTheseFiltersTogether();
query = query.Where<Person>(filterOfOrs);
You can't make the Where clause dynamic, but you can dynamically create the Lambda Expression you pass to it. Create the right Expression, compile it and pass the resulting lambda expression as a parameter to the Where clause.
EDIT:
Okay, seems like you can skip the part where you have to manually create the Expression and can use PredicateBuilder for it, as already answered by AS-CII.

How to dynamically add OR operator to WHERE clause in LINQ

I have a variable size array of strings, and I am trying to programatically loop through the array and match all the rows in a table where the column "Tags" contains at least one of the strings in the array. Here is some pseudo code:
IQueryable<Songs> allSongMatches = musicDb.Songs; // all rows in the table
I can easily query this table filtering on a fixed set of strings, like this:
allSongMatches=allSongMatches.Where(SongsVar => SongsVar.Tags.Contains("foo1") || SongsVar.Tags.Contains("foo2") || SongsVar.Tags.Contains("foo3"));
However, this does not work (I get the following error: "A lambda expression with a statement body cannot be converted to an expression tree")
allSongMatches = allSongMatches.Where(SongsVar =>
{
bool retVal = false;
foreach(string str in strArray)
{
retVal = retVal || SongsVar.Tags.Contains(str);
}
return retVal;
});
Can anybody show me the correct strategy to accomplish this? I am still new to the world of LINQ :-)
You can use the PredicateBuilder class:
var searchPredicate = PredicateBuilder.False<Songs>();
foreach(string str in strArray)
{
var closureVariable = str; // See the link below for the reason
searchPredicate =
searchPredicate.Or(SongsVar => SongsVar.Tags.Contains(closureVariable));
}
var allSongMatches = db.Songs.Where(searchPredicate);
LinqToSql strange behaviour
I recently created an extension method for creating string searches that also allows for OR searches. Blogged about here
I also created it as a nuget package that you can install:
http://www.nuget.org/packages/NinjaNye.SearchExtensions/
Once installed you will be able to do the following
var result = db.Songs.Search(s => s.Tags, strArray);
If you want to create your own version to allow the above, you will need to do the following:
public static class QueryableExtensions
{
public static IQueryable<T> Search<T>(this IQueryable<T> source, Expression<Func<T, string>> stringProperty, params string[] searchTerms)
{
if (!searchTerms.Any())
{
return source;
}
Expression orExpression = null;
foreach (var searchTerm in searchTerms)
{
//Create expression to represent x.[property].Contains(searchTerm)
var searchTermExpression = Expression.Constant(searchTerm);
var containsExpression = BuildContainsExpression(stringProperty, searchTermExpression);
orExpression = BuildOrExpression(orExpression, containsExpression);
}
var completeExpression = Expression.Lambda<Func<T, bool>>(orExpression, stringProperty.Parameters);
return source.Where(completeExpression);
}
private static Expression BuildOrExpression(Expression existingExpression, Expression expressionToAdd)
{
if (existingExpression == null)
{
return expressionToAdd;
}
//Build 'OR' expression for each property
return Expression.OrElse(existingExpression, expressionToAdd);
}
}
Alternatively, take a look at the github project for NinjaNye.SearchExtensions as this has other options and has been refactored somewhat to allow other combinations
There is another, somewhat easier method that will accomplish this. ScottGu's blog details a dynamic linq library that I've found very helpful in the past. Essentially, it generates the query from a string you pass in. Here's a sample of the code you'd write:
Dim Northwind As New NorthwindDataContext
Dim query = Northwind.Products _
.Where("CategoryID=2 AND UnitPrice>3") _
.OrderBy("SupplierId")
Gridview1.DataSource = query
Gridview1.DataBind()
More info can be found at scottgu's blog here.
Either build an Expression<T> yourself, or look at a different route.
Assuming possibleTags is a collection of tags, you can make use of a closure and a join to find matches. This should find any songs with at least one tag in possibleTags:
allSongMatches = allSongMatches.Where(s => (select t from s.Tags
join tt from possibleTags
on t == tt
select t).Count() > 0)

Resources