Dynamically compiling LINQ queries to verify a dictionary value - c#

Let us suppose we need to query on a list of entities and we do not know the criteria which is pretty much dynamic and the entity has both dictionaries and simple fields inside Let this be the next entity - Address(I left only one property for simplicity).
public class Address
{
#region Public members
/// <summary>
/// The extra datafield values
/// </summary>
public IDictionary<string, string> DataFieldValues { get; set; }
public string City { get; set; }
#endregion
}
Now if we query on a fixed field called City when I got the implementation:
private static Expression<Func<Address, bool>> BuildLambdaForAQueryItem(string caption, string value)
{
ParameterExpression param = Expression.Parameter(typeof(Address), caption);
BinaryExpression body = Expression.Equal(Expression.PropertyOrField(param, caption),
Expression.Constant(value,
typeof(Address).GetProperty(
caption).PropertyType));
return Expression.Lambda<Func<Address, bool>>(body, param);
}
Now if I want to query on a DataFieldValue coolection I need to write a lambda similar too:
x=>x.DataFieldValues.ContatinsKey(key) && DataFieldValues[key]==value
What I get with the method below is almost similar but still it does not apply the filter correctly:
private static Expression<Func<Address, bool>> BuildLambdaForAnExtraField(PostedQueryItem queryItem)
{
ParameterExpression dataFields = Expression.Parameter(typeof(Address), "x");
var dictionaryExpression = Expression.PropertyOrField(dataFields, "DataFieldValues");
var keyExists = Expression.Call(dictionaryExpression, "ContainsKey", null, Expression.Constant(queryItem.Caption));
Expression dictionaryAccessExpr = Expression.Property(dictionaryExpression, "Item",
Expression.Constant(queryItem.Caption));
var valueCorresponds = Expression.Equal(dictionaryAccessExpr, Expression.Constant(queryItem.Value));
return Expression.Lambda<Func<Address, bool>>(keyExists, dataFields).And(
Expression.Lambda<Func<Address, bool>>(valueCorresponds, dataFields));
}

I think you want to use Expression.AndAlso (a short-circuiting AND) on the two predicate expressions in question to construct the body of the expression-tree.
var body = Expression.AndAlso(keyExists, valueCorresponds);
return Expression.Lambda<Func<Address, bool>>(body, dataFields);
EDIT: (If you want to stick with your existing technique)
My guess is that your And method is an extension-method from the LINQKit library. If so, note that this extension involves 'invoking' the right-hand side expression with the parameters of the first expression as part of producing the result. If this isn't acceptable to you (LINQ provider limitations, perhaps?), you can use the useful Expand extension that also comes with this library to 'inline' the invoked expression.
return Expression.Lambda<Func<Address, bool>>(keyExists, dataFields)
.And(Expression.Lambda<Func<Address, bool>>(valueCorresponds, dataFields))
.Expand();
But this is massive overkill in this case; my advice is to go with my first sample.

Related

Access property of a generic type inside a function

I have a generic function GetDocuments<T> that's querying the CosmosDB API. The generic is constrained by a custom IDocument interface. At the moment, I'm passing an enum as an argument to this function that determines the type of the document -- however, my interface has the document type as a property, so it seems like I should be able to access that somehow instead of passing another arg.
Because my argument is in an Expression, I'm not sure how to access that value (I'm not sure if using the API to access expression params is the right approach). If I had an IDocument as an argument, it seems pretty straightforward to access it.
Given this code, how can I access the DocumentType without passing it to GetDocuments<T>?
Function definition:
public IEnumerable<T> GetDocuments<T>(Expression<Func<T, bool>> predicate, Enumerations.DocumentType type) where T : IDocument
{
var results = Client.CreateDocumentQuery<T>(GetDocumentCollectionUri(), GetFeedOptions())
.Where(predicate)
.Where(s => s.DocumentType == type)
.ToList();
return results;
}
Interface definition:
public interface IDocument
{
[JsonProperty(PropertyName = "id")]
string Id { get; set; }
[JsonProperty(PropertyName = "documentType")]
Enumerations.DocumentType DocumentType { get; }
}
Function call:
var messages = mailboxRepository.GetDocuments<MailboxMessageTemplate>(
s => s.UserId == user.ID,
Enumerations.DocumentType.MessageTemplate);
You can do that by pre-creating your expression and just adding it to your query.
Here is the expression that would do the trick.
internal static Expression<Func<T, bool>> TypeSpecificExpression<T>() where T : class
{
var parameter = Expression.Parameter(typeof(IDocument));
var member = Expression.Property(parameter, nameof(IDocument.Enumerations.DocumentType));
var contant = Expression.Constant(nameof(T));
var body = Expression.Equal(member, contant);
var extra = Expression.Lambda<Func<T, bool>>(body, parameter);
return extra;
}
You can then simple change your method to be:
public IEnumerable<T> GetDocuments<T>(Expression<Func<T, bool>> predicate) where T : IDocument
{
var results = Client.CreateDocumentQuery<T>(GetDocumentCollectionUri(), GetFeedOptions())
.Where(predicate && TypeSpecificExpression())
.ToList();
return results;
}
Obviously I don't have access to the Enumerations.DocumentType enum so you might need to do some tweeting on the value you are setting here: var contant = Expression.Constant(nameof(T));
On a side note, you should not be calling .ToList() like that on CreateDocumentQuery. You are synchornizing a query that can be a serious performance hit. You should be using the .AsDocumentQuery() method to get the query and then call query.ExecuteNextAsync when query.HasMoreResults.
On a second side note, it looks like you are trying to build something that the library Cosmonaut already does, including the feature you just asked a question for (you can find that method here). It's worth taking a look.
Disclaimer: I made Cosmonaut
So you are looking for a way to convert an Expression<Func<T, bool>> to a Func<T, bool>?
You can call Compile.
Compiles the lambda expression described by the expression tree into executable code and produces a delegate that represents the lambda expression.
var results = Client.CreateDocumentQuery<T>(GetDocumentCollectionUri(), GetFeedOptions())
.Where(predicate.Compile())
.Where(s => s.DocumentType == type)
.ToList();

Filtering but property and child entity property

I got a small problem with building dynamic expression tree for search logic. Creating an expression tree for entity own properties is working fine, but I've no idea how to add an expression which will filter by child entity properties.
Here is my Task entity:
public class Task: Entity
{
public TaskType Type { get; set; }
public TaskPriority Priority { get; set; }
public int ProjectId { get; set; }
public Project Project { get; set; }
}
And here is Project entity:
public class Project: Entity
{
public int CustomerId { get; set; }
public Customer Customer { get; set; }
}
And logic for building dynamic expression:
public Func<TaskItem, bool> Build(IList<Filter> filters)
{
ParameterExpression param = Expression.Parameter(typeof(TaskItem), "task");
List<Filter> priorityFilter = FilterFilters(filters, "Priority");
List<Filter> typeFilter = FilterFilters(filters, "Type");
List<Filter> customerFilter = FilterFilters(filters, "CustomerId");
Expression expression = null;
// BuildExpression is a method which simply creates expression which is using Tasks properties (like Type or Priority)
expression = BuildExpression(param, priorityFilter, expression);
expression = BuildExpression(param, typeFilter, expression);
// This part need's to be reworked
ParameterExpression projectParam = Expression.Parameter(typeof(Project), "project");
Expression projectCustomerExpression = Expression.Equal(Expression.PropertyOrField(projectParam, "CustomerId"), Expression.Constant(customerFilter[0].Value));
Expression customerExpression = Expression.Equal(Expression.PropertyOrField(param, "Project"), projectCustomerExpression);
Expression finall = expression != null ? Expression.AndAlso(expression, projectCustomerExpression) : projectCustomerExpression;
// End of filtering by CutomerId
return Expression.Lambda<Func<TaskItem, bool>>(finall, param).Compile();
}
I've no idea how to filter by CustomerId. The part above code marked as This part need's to be reworked is probably wrong or at least the last part of it. The idea is to extend existing expression (this one build by BuildExpression method) with an expression which will filter by CustomerId.
I already lost some time on this, trying on my own and looking for answers but with no results.
Any help?
As you have provided a minimal code, how you are creating the actual expressions is unknown. So, I will try to provide a general recipe for this scenario.
If you want to filter a list of Task then you still need to use the same ParameterExpression of type Task, like you have already done:
ParameterExpression param = Expression.Parameter(typeof(TaskItem), "task");
There is no need to create another ParameterExpression of type Project even if you want to filter on properties of Project. In stead you just need to reuse the former ParameterExpression. Note that if we build a static predicate like below, this is also the case that we also don't use a different parameter expression:
queryableTask.Where(t => t.Priority == TaskPriority.High && t.Project.CustomerId == 123);
Now to dynamically build filter on navigational (child) property, the key is to form the left expression (i.e. expression for navigational property) correctly.
Lets say our navigational property is in dot notation: Project.CustomerId. Then we can do something like this to create the left expression for property:
// We already have the following commented properties
// prop = "Project.CustomerId";
// ParameterExpression param = Expression.Parameter(typeof(TaskItem), "task");
var leftExpr = prop.Split('.')
.Aggregate<string, MemberExpression>(null,
(acc, p) => acc == null
? Expression.Property(param, p)
: Expression.Property(acc, p));
And then you can do the rest like a normal property, such as creating the right expression and combining them with another expression defining the operator (Equal, Not Equal etc.).
Hope this helps.

How to rebuild a lambda expression to start one level deeper?

I created a generic method that accepts a member access expression identifying a grouping key, just as one would pass to IQueryable<T>.GroupBy.
private static IQueryable<ObjectWithRank<T>> IncludeBestRankPerGroup<T,TGroupKey>(this IQueryable<T> q, Expression<Func<T, TGroupKey>> keySelector)
class ObjectWithRank<T> {
public T RankedObject { get; set; }
public int Rank { get; set; }
}
The IncludeBestRankPerGroup method is a variation of my IncludeRank method that just takes an IQueryable<T> and applies a rank to each element by wrapping it in ObjectWithRank<T>, returning an IQueryable<ObjectWithRank<T>>. I then want to group by the keySelector and select the best ranked element per group.
This requires me to convert a lambda expression from form 1 to 2 so I can pass it to IQueryable<ObjectWithRank<T>>.GroupBy:
(T x) => x.GroupingProperty
(ObjectWithRank<T> x) => x.RankedObject.GroupingProperty
Note that I cannot just change the root object type of the keySelector from T to ObjectWithRank<T>, because the ObjectWithRank<T> class is not exposed in the public method that calls IncludeBestRankPerGroup. The user of the API just provides an IQueryable<T>, and receives back an IQueryable<T> with the highest ranking items per group, so they never see that ObjectWithRank<T> is used under the hood.
I managed to perform the conversion with the following code, but it only works for simple member access expressions. For example, it can convert an expression like x => x.GroupingKey to x => x.RankedObject.GroupingKey, but it won't work with a two-level deep member access expression where I'd have to convert something like x => x.SubObject.GroupingKey to x => x.RankedObject.SubObject.GroupingKey.
private static Expression<Func<ObjectWithRank<T>, TGroupKey>> RebuildMemberAccessForRankedObject<T, TGroupBy>(Expression<Func<T, TGroupKey>> keySelector)
{
Expression<Func<ObjectWithRank<T>, T>> objectAccessExpression = x => x.RankedObject;
return Expression.Lambda<Func<ObjectWithRank<T>, TGroupKey>>(
Expression.Property(objectAccessExpression.Body, (keySelector.Body as MemberExpression).Member as PropertyInfo)
, objectAccessExpression.Parameters
);
}
The above seems like a hack where I first create a member access expression that access the T RankedObject property of the ObjectWithRank<T>, then tack on the provided keySelector member access expression. I'm not sure if there's a simple way to get this to work. It seems like Expression.Property only allows drilling down one property at a time, so maybe I need some kind of loop to rebuild the expression from the top, drilling down one property at a time.
There's a similar question here that does have a simple solution, but goes one level deeper on the opposite end of the expression, which isn't what I'm trying to do.
Alter Lambda Expression to go one level deeper
I was able to replace the root of an expression with a recursive lamba that drills down in the member expression until it reaches the parameter expression, replaces the parameter expression with the new root expression at that deepest level, then unwinds the call stack replacing each member expression's Expression with the updated inner expression all the way back to the top, then create's a new lambda with the updated expression and parameter expression set for the new root.
private static Expression<Func<TInNew, TOut>> UpdateExpressionRoot<TOut, TInOld, TInNew>(Expression<Func<TInNew, TInOld>> newRoot, Expression<Func<TInOld, TOut>> memberAccess)
{
Func<MemberExpression, MemberExpression> updateDeepestExpression = null;
updateDeepestExpression = e =>
{
if (e.Expression is MemberExpression)
{
var updatedChild = updateDeepestExpression((MemberExpression)e.Expression);
return e.Update(updatedChild);
}
if (e.Expression is ParameterExpression)
return e.Update(newRoot.Body);
throw new ArgumentException("Member access expression must be composed of nested member access expressions only.", nameof(memberAccess));
};
return Expression.Lambda<Func<TInNew, TOut>>(updateDeepestExpression(memberAccess.Body as MemberExpression), newRoot.Parameters);
}
It can be called like this:
class Car
{
Manufacturer Manufacturer { get; set; }
}
class Manufacturer
{
string ID { get; set; }
}
Expression<Func<Car, string>> groupKeySelector = x => x.Manufacturer.ID;
Expression<Func<ObjectWithRank<Car>, Car>> rankedObjectSelector = x => x.RankedObject;
var rankedGroupKeySelector = UpdateExpressionRoot(rankedObjectSelector, groupKeySelector);
//rankedGroupKeySelector.ToString() == "x.RankedObject.Manufacturer.ID"
//Essentially this replaced ParameterExpression {x} in x.Manufacturer.ID with MemberExpression {x.RankedObject}.

linq lambda expression for sql contains

I am using a generic repository, like this:
itemsList = (from myrow in UoW.FileRepository.Get()
select new FileModel()
{record_id = myrow.type_id,
descr = myrow.descr}).ToList();});
And this is the Get method:
public virtual IEnumerable<TEntity> Get()
{
// _aQuery = _theDbContext.Set<TEntity>();
IEnumerable<TEntity> query = _aQuery;
return query;
}
How would I implement a generic linq lambda expression if I wanted to create a similar query to search for a particular string in a particular field? In my viewmodel I would like to call something like:
from myrow in UoW.FileRepository.Srch(nameofFieldToSearch, searchString).
The query would look something like this?
public IEnumerable<TEntity> Srch(Expression<Func<TEntity, bool>> expression)
{
IEnumerable<TEntity> srchList = _aQuery.ToList();
return srchList.Where(????);
}
Thank you for your suggestions.
EDIT-----------------------
I have all my queries like Get and Srch in a general repository class and for now just need to know how to declare the query in the repository class and how to call it with the search string from my viewmodel. I am not sure if there is a consensus as to where/when to materialize and compile? I saw another discussion http://www.fascinatedwithsoftware.com/blog/post/2012/01/10/More-on-Expression-vs-Func-with-Entity-Framework.aspx and I quote from it below to inquire whether that is the same approach being suggested here? Thank you again.
"The profiler told us that LoadMyEntities was being called many, many times and it was taking a large fraction of our CPU time. The simple change below solved the problem. Can you guess why?"
public IEnumerable<MyEntity> LoadMyEntities(Func<MyEntity, bool> predicate)
{return Context.MyEntities.Where(predicate);}
"The parameter is now a Func<> instead of an Expression>. The reason this makes a difference is that a predicate that's in the form of an Expression is passed to SQL server, but a predicate that's passed as a Func is not. Normally, you'd want SQL Server to do as much for you as possible, and an Expression would be the right choice, but in this case we'd like to pre-load the entire table in the context -- which is exactly what a Func will do.
The Where extension method has two flavors. One extends IQueryable and takes an Expression parameter. The other extends IEnumerable and takes a Func.
Because 'predicate' is now a Func, the Where that extends IEnumerable is used.
The Entity Framework's fluent interface for constructing SQL queries is based on IQueryables, not IEnumerables. Therefore, the fluency stops just before the Where. The part of the statement that gets passed to the Entity Framework is just Context.MyEntities.
Context.MyEntities therefore returns the entire table to the context.
The entire table is now filtered with the predicate, and the value we really want is returned.
The next time the method is called, the Entity Framework realizes that the record we want is already in the context. (In my case, we were querying by the primary key, and EF is apparently smart enough to know that if there's a record in the context with that ID, it won't find an additional such record in the database.) Since we don't go out to SQL Server, we save lots of time. Obviously there are occasions when you would not want this, but in our case it was exactly what we wanted. The table was relatively small, and the same context was queried hundreds of times.
In the original version, the predicate was an Expression, so the compiler used the Where that extends IQueryable. The predicate was thus passed to SQL Server, which dutifully returned just one row to the context. The next time we called LoadMyEntities, Entity Framework had to call SQL Server again."
Please take a look at msdn repository pattern
public virtual IEnumerable<TEntity> Get(
Expression<Func<TEntity, bool>> filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
string includeProperties = "")
{
IQueryable<TEntity> query = dbSet;
if (filter != null)
{
query = query.Where(filter);
}
foreach (var includeProperty in includeProperties.Split
(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
query = query.Include(includeProperty);
}
if (orderBy != null)
{
return orderBy(query).ToList();
}
else
{
return query.ToList();
}
}
This will provide more complete Generic Get method.
For search you could Try
public IEnumerable<TEntity> GlobalSearch(Expression<Func<TEntity, bool>> expression)
{
return Get(expression);
}
In DataProvider you could try something like this
Note This is not may be exact implementation, but will give you basic idea, how to invoke.
public List<Users> Search(List<string> userList)
{
Expression<Func<User, bool>> expression = x=>UserList.Contains(x.UserName);
return GlobalSearch(expression);
}
Example of Repository pattern
This would be a simple implementation using the Expression trees. Following is the complete solution:
Method to fetch the Expression for Srch method:
Public Expression<Func<TEntity, bool>> SrchExpression(string nameofFieldToSearch, string searchString)
{
var parameterType = Expression.Parameter(typeof(TEntity), "obj");
var memberExpression = Expression.Property(typeof(string), nameofFieldToSearch)
// Calls Extension method created underneath
var filtersMethodInfo = typeof(StringExtensions).GetMethod("Contains", new[] { typeof(string), typeof(string) });
var filtersConstantExpression = Expression.Constant(searchString, typeof(string));
var finalExpression = Expression.Call(null, filtersMethodInfo, memberExpression, filtersConstantExpression)
return Expression.Lambda<Func<TEntity, bool>>(finalExpression, parameterType)
}
// Create a String extension method for Contains
public static class StringExtensions
{
public static bool Contains(this string source, string searchString)
{
return source?.IndexOf(subString, StringComparison.OrdinalIgnoreCase) >= 0;
}
}
Now your Srch method shall look like:
public IEnumerable<TEntity> Srch(Expression<Func<TEntity, bool>> expression)
{
Func<TEntity, bool>> func = expression.Compile();
IEnumerable<TEntity> srchList = _aQuery.Where(o => func(o));
return srchList;
}

Create New Expression With Different Lambda Signature From Existing Expression Body

Is it possible to do what I'm looking for here:
namespace ExpressionProblem
{
class Program
{
private static readonly List<dynamic> Items = new List<dynamic>
{
new { Name = "Foo" },
new { Name = "Bar" }
};
static void Main()
{
var result = DoSomething<Item>(p => p.Name == "Foo");
Console.WriteLine(result.Name);
}
static T DoSomething<T>(Expression<Func<T, bool>> expression)
{
//change expression lambda from Func<T, bool> to Func<dynamic, bool> so below will compile and work
return Items.FirstOrDefault(expression);
}
}
public class Item
{
public string Name { get; set; }
}
}
Basically I need to take the given expression with has a signature of
Expression<Func<T, bool>>
and get and expression which performs the same action but has the signature
Expression<Func<dynamic, bool>>.
Is this even possible? From what I've read I don't think you can change an existing expression as most places I've researched have said you basically have to build a new expression from an existing one. I tried to do the following:
var newExpression = Expression.Lambda<Func<dynamic, bool>>(expression.Body);
...but am getting the error "Incorrect number of parameters supplied for lambda declaration" when trying to create the new expression.
Any ideas on how to get this to work or am I trying to do something that can't (or shouldn't) be done?
Thanks in advance for any help you can provide.
Regards,
Craig
[EDIT] - I know some will ask why I don't just make the list of type Item - in my particular case I am not able to as the list to be queried will be in an app domain that has no knowledge of the Item type. I'm just including it in the same namespace/class here for brevity's sake.[/EDIT]

Categories

Resources