Unable to write Extension method to wrap a NEST client method - elasticsearch

Basically, I was attempting to write the following extension method to avoid having to constantly write the .Suffix("keyword"). I dislike having string literals all over the place, and multiple properties of my ElasticSearch indices happen to require adding the keyword suffix to query properly.
public static class NestHelperExtensions
{
public static object UseKeywordSuffix(this object #object)
{
return #object.Suffix("keyword");
}
}
If I use the method presented above, it doesn't apply the suffix, but I'm not sure why it wouldn't work. I may be going about this the wrong way, and perhaps there is a way to add a model attribute or something to build a nestCilent.Search<T>, but when I attempted to use the KeywordAttribute, that didn't seem to work either.
Can anyone offer up an explanation as to why it wouldn't be this simple and if it should be possible, can you provide an example or workaround e.g. using attributes?

This won't work because the Suffix method is specifically handled when visiting the field expression
if (methodCall.Method.Name == nameof(SuffixExtensions.Suffix) && methodCall.Arguments.Any())
{
VisitConstantOrVariable(methodCall, _stack);
var callingMember = new ReadOnlyCollection<Expression>(
new List<Expression> { { methodCall.Arguments.First() } }
);
Visit(callingMember);
return methodCall;
}
So, an extension method like above would need to be called Suffix to begin and it would have to have at least one argument. You might think of supplying an optional parameter with a default value of "keyword" would work, but expression trees do not support this, so wouldn't work.
Another approach would be to utilise the AppendSuffix extension method on Expression<Func<T, object>> to build something; the nicest way to use this would be to pull the lambda expression out of the fluent call and into a variable
public static class NestHelperExtensions
{
public static Expression<Func<T, object>> KeywordSuffix<T>(this Expression<Func<T, object>> expression)
{
return expression.AppendSuffix("keyword");
}
}
var client = new ElasticClient();
Expression<Func<Person, object>> firstName = f => f.FirstName;
var searchResponse = client.Search<Person>(s => s
.Query(q => q
.Match(m => m
.Field(firstName.KeywordSuffix())
.Query("bar")
)
)
);
The not so nice way would be casting the lambda expression to Expression<Func<T, object>> inline
var searchResponse = client.Search<Person>(s => s
.Query(q => q
.Match(m => m
.Field(((Expression<Func<Person, object>>)(f => f.FirstName)).KeywordSuffix())
.Query("bar")
)
)
);
Another, perhaps simpler approach, would be to introduce a constant for the string "keyword", and use that in the Suffix extension method; it avoids using a string literal all over the place.

Related

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

Custom IComparer in LINQ OrderBy Lambda expression

I have a custom comparer I want to use with OrderBy. I am trying to build a LINQ expression to make it work. So in essence, I am trying to put together an IComparer, OrderBy inLinq expression.
The expression I am trying to build should look something like:
source => source.OrderBy(lambdaParameter => lambdaParameter.Name, new Parsers.NumericComparer()).
With the code below the expression
'{source => source.OrderBy(lambdaParameter => lambdaParameter.Name)}'
is built and I am trying to add this custom Icomparable to this expression
new Parsers.NumericComparer().
This is because I need to do a natural sort. Can someone please help me on how to include this expression. I am trying to read several threads for the past few hours but I have not done understood LINQ expressions well enough yet to implement this. Thanks!
private void CreateOrderByMethod(PropertyDescriptor prop, string orderByMethodName, string cacheKey)
{
/*
Create a generic method implementation for IEnumerable<T>.
Cache it.
*/
var sourceParameter = Expression.Parameter(typeof(List<T>), "source");
var lambdaParameter = Expression.Parameter(typeof(T), "lambdaParameter");
var accesedMember = typeof(T).GetProperty(prop.Name);
var propertySelectorLambda =
Expression.Lambda(Expression.MakeMemberAccess(lambdaParameter, accesedMember), lambdaParameter);
var orderByMethod = typeof(Enumerable).GetMethods()
.Where(a => a.Name == orderByMethodName &&
a.GetParameters().Length == 2)
.Single()
.MakeGenericMethod(typeof(T), prop.PropertyType);
var orderByExpression = Expression.Lambda<Func<List<T>, IEnumerable<T>>>(
Expression.Call(orderByMethod,
new Expression[] { sourceParameter,
propertySelectorLambda }),
sourceParameter);
cachedOrderByExpressions.Add(cacheKey, orderByExpression.Compile());
}
To create an expression that creates a new instance of an object, use Expression.New.
var newParser = Expression.New(typeof(Parsers.NumericComparer));
Then I suggest you use this overload of Expression.Call instead, so that you don't have to go and manually grab the MethodInfo:
var orderByCall = Expression.Call(
typeof(Enumerable),
"OrderBy",
new [] { typeof(T), prop.PropertyType },
sourceParameter, propertySelectorLambda, newParser);

Generic Linq with specific where/order by clauses

I want to create a generic method that allows me to search a Sitecore 7 index using Linq to Sitecore (.Net 4.5).
This will be used for various searches such as:
Get top 5 latest news pages
Get the last 20 event pages
Generic site search
Etc, etc, etc
I can create a very generic search method, which works for all types of page. It provides a generic template predicate for the Where clause that would be applicable for all types of search.
However, for the searches above I also need to add specific predicates for the Where clause, and specific expressions for Order By etc. The intention would be to create subclasses for each type of search, which would implement these specifics.
I've condensed some code which is shown below. In this I try to add specific functionality for a new page search.
All page classes derive from "Base".
public virtual ReadOnlyCollection<T> Search<T>() where T : Base, new()
{
List<T> results = new List<T>();
using (IProviderSearchContext context = ContentSearchManager.GetIndex("sitecore_web_index").CreateSearchContext())
{
Expression<Func<T, bool>> outerPredicate = PredicateBuilder.True<T>();
// Create a predicate for the template id.
Expression<Func<T, bool>> templatePredicate = PredicateBuilder.False<T>();
templatePredicate = templatePredicate.Or(baseItem => (baseItem.TemplateIdFromIndex.Equals("8b1fc00c76314d32b8e1bce93dd41ccd")));
// Create a predicate for a news page search.
Expression<Func<NewsPageBase, bool>> datePredicate = PredicateBuilder.False<NewsPageBase>();
datePredicate = datePredicate.And(newsPage => newsPage.ArticleDate < DateTime.Now);
// 1. outerPredicate = outerPredicate.And(datePredicate);
// 2. IQueryable<T> searchQuery = context.GetQueryable<T>().Where(outerPredicate).OrderByDescending(newsPage => newsPage.ArticleDate).Take(5);
IQueryable<T> searchQuery = context.GetQueryable<T>().Where(outerPredicate);
results = searchQuery.ToList();
}
return new ReadOnlyCollection<T>(results);
}
This code complies and runs.
However, if I uncomment the line marked as [1], a compiler error me from Anding the generic template predicate with the specific news page predicate.
The error is "The type arguments for method 'Sitecore.ContentSearch.Linq.Utilities.PredicateBuilder.And(System.Linq.Expressions.Expression>, System.Linq.Expressions.Expression>)' cannot be inferred from the usage".
It's a similar error if I uncomment the line marked as [2].
How do I create a generic method which has the specific functionality for each type of search?
Modifying your code as follows achieves what you need..
public virtual ReadOnlyCollection<T> Search<T>() where T : Base, new()
{
List<T> results = new List<T>();
using (IProviderSearchContext context = ContentSearchManager.GetIndex("sitecore_web_index").CreateSearchContext())
{
Expression<Func<T, bool>> outerPredicate = PredicateBuilder.True<T>();
// Create a predicate for a news page search.
Expression<Func<NewsPageBase, bool>> datePredicate = PredicateBuilder.True<NewsPageBase>();
datePredicate = datePredicate.And(newsPage => newsPage.ArticleDate < DateTime.Now);
//outerPredicate = outerPredicate.And((Expression<Func<T,bool>>)(object)datePredicate);
outerPredicate = outerPredicate.And((Expression<Func<T, bool>>)(object)datePredicate);
// 2. IQueryable<T> searchQuery = context.GetQueryable<T>().Where(outerPredicate).OrderByDescending(newsPage => newsPage.ArticleDate).Take(5);
IQueryable<T> searchQuery = context.GetQueryable<T>().Where(outerPredicate);
results = searchQuery.ToList();
}
return new ReadOnlyCollection<T>(results);
}
The changes I made were:
Removed the unused templatePredicate - I'm not sure if you had intended to use this or had forgotten to remove it when condensing your original code.
Create datePredicate using PredicateBuilder.True instead of PredicateBuilder.False - this is needed for use in .And() otherwise no results will be returned.
I (un)box datePredicate for use within outerPredicate.And(). The compiler complains if you try to cast datePredicate to Expression<Func<T, bool>> but casting it to object first solves this.
I have found a work around.
Instead of:
Expression<Func<NewsPageBase, bool>> datePredicate = PredicateBuilder.False<NewsPageBase>();
datePredicate = datePredicate.And(newsPage => newsPage.ArticleDate < DateTime.Now);
You can use:
Expression<Func<T, bool>> innerPredicate = PredicateBuilder.False<T>();
innerPredicate = innerPredicate.Or(item => ((DateTime)item[(ObjectIndexerKey)"article_date"] < DateTime.Now));
For the OrderBy clause, you can use:
searchQuery = searchQuery.OrderByDescending(item => item[(ObjectIndexerKey)"article_date"]);
This will work if your base class is subclassed from the Sitecore class "SearchResultItem".

Linq Parsing Error when trying to create seperation of concerns

I am in the middle of a refactoring cycle where I converted some extension methods that used to look like this:
public static IQueryable<Family> FilterOnRoute(this IQueryable<Family> families, WicRoute route)
{
return families.Where(fam => fam.PODs
.Any(pod => pod.Route.RouteID == route.RouteID));
}
to a more fluent implementation like this:
public class SimplifiedFamilyLinqBuilder
{
private IQueryable<Family> _families;
public SimplifiedFamilyLinqBuilder Load(IQueryable<Family> families)
{
_families = families;
return this;
}
public SimplifiedFamilyLinqBuilder OnRoute(WicRoute route)
{
_families = _families.Where(fam => fam.PODs
.Any(pod => pod.Route.RouteID == route.RouteID));
return this;
}
public IQueryable<Family> AsQueryable()
{
return _families;
}
}
which I can call like this: (note this is using Linq-to-Nhibernate)
var families =
new SimplifiedFamilyLinqBuilder()
.Load(session.Query<Family>())
.OnRoute(new WicRoute() {RouteID = 1})
.AsQueryable()
.ToList();
this produces the following SQL which is fine with me at the moment: (of note is that the above Linq is being translated to a SQL Query)
select ... from "Family" family0_
where exists (select pods1_.PODID from "POD" pods1_
inner join Route wicroute2_ on pods1_.RouteID=wicroute2_.RouteID
where family0_.FamilyID=pods1_.FamilyID
and wicroute2_.RouteID=#p0);
#p0 = 1
my next effort in refactoring is to move the query part that deals with the child to another class like this:
public class SimplifiedPODLinqBuilder
{
private IQueryable<POD> _pods;
public SimplifiedPODLinqBuilder Load(IQueryable<POD> pods)
{
_pods = pods;
return this;
}
public SimplifiedPODLinqBuilder OnRoute(WicRoute route)
{
_pods = _pods.Where(pod => pod.Route.RouteID == route.RouteID);
return this;
}
public IQueryable<POD> AsQueryable()
{
return _pods;
}
}
with SimplifiedFamilyLinqBuilder changing to this:
public SimplifiedFamilyLinqBuilder OnRoute(WicRoute route)
{
_families = _families.Where(fam =>
_podLinqBuilder.Load(fam.PODs.AsQueryable())
.OnRoute(route)
.AsQueryable()
.Any()
);
return this;
}
only I now get this error:
Remotion.Linq.Parsing.ParserException : Cannot parse expression 'value(Wic.DataTests.LinqBuilders.SimplifiedPODLinqBuilder)' as it has an unsupported type. Only query sources (that is, expressions that implement IEnumerable) and query operators can be parsed.
I started to implement IQueryable on SimplifiedPODLinqBuilder(as that seemed more logical than implementing IEnumberable) and thought I would be clever by doing this:
public class SimplifiedPODLinqBuilder : IQueryable
{
private IQueryable<POD> _pods;
...
public IEnumerator GetEnumerator()
{
return _pods.GetEnumerator();
}
public Expression Expression
{
get { return _pods.Expression; }
}
public Type ElementType
{
get { return _pods.ElementType; }
}
public IQueryProvider Provider
{
get { return _pods.Provider; }
}
}
only to get this exception (apparently Load is not being called and _pods is null):
System.NullReferenceException : Object reference not set to an instance of an object.
is there a way for me to refactor this code out that will parse properly into an expression that will go to SQL?
The part fam => _podLinqBuilder.Load(fam.PODs.AsQueryable() is never going to work, because the linq provider will try to parse this into SQL and for that it needs mapped members of Family after the =>, or maybe a mapped user-defined function but I don't know if Linq-to-Nhibernate supports that (I never really worked with it, because I still doubt if it is production-ready).
So, what can you do?
To be honest, I like the extension methods much better. You switched to a stateful approach, which doesn't mix well with the stateless paradigm of linq. So you may consider to retrace your steps.
Another option: the expression in .Any(pod => pod.Route.RouteID == route.RouteID)); could be paremeterized (.Any(podExpression), with
OnRoute(WicRoute route, Expression<Func<POD,bool>> podExpression)
(pseudocode).
Hope this makes any sense.
You need to separate methods you intend to call from expressions you intend to translate.
This is great, you want each of those methods to run. They return an instance that implements IQueryable<Family> and operate on that instance.
var families = new SimplifiedFamilyLinqBuilder()
.Load(session.Query<Family>())
.OnRoute(new WicRoute() {RouteID = 1})
.AsQueryable()
.ToList();
This is no good. you don't want Queryable.Where to get called, you want it to be an expression tree which can be translated to SQL. But PodLinqBuilder.Load is a node in that expression tree which can't be translated to SQL!
families = _families
.Where(fam => _podLinqBuilder.Load(fam.PODs.AsQueryable())
.OnRoute(route)
.AsQueryable()
.Any();
You can't call .Load inside the Where expression (it won't translate to sql).
You can't call .Load outside the Where expression (you don't have the fam parameter).
In the name of "separation of concerns", you are mixing query construction methods with query definition expressions. LINQ, by its Integrated nature, encourages you to attempt this thing which will not work.
Consider making expression construction methods instead of query construction methods.
public static Expression<Func<Pod, bool>> GetOnRouteExpr(WicRoute route)
{
int routeId = route.RouteID;
Expression<Func<Pod, bool>> result = pod => pod.Route.RouteID == route.RouteID;
return result;
}
called by:
Expression<Func<Pod, bool>> onRoute = GetOnRouteExpr(route);
families = _families.Where(fam => fam.PODs.Any(onRoute));
With this approach, the question is now - how do I fluidly hang my ornaments from the expression tree?

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.

Resources