How can I transform this linq expression? - linq

Say I have an entity that I want to query with ranking applied:
public class Person: Entity
{
public int Id { get; protected set; }
public string Name { get; set; }
public DateTime Birthday { get; set; }
}
In my query I have the following:
Expression<Func<Person, object>> orderBy = x => x.Name;
var dbContext = new MyDbContext();
var keyword = "term";
var startsWithResults = dbContext.People
.Where(x => x.Name.StartsWith(keyword))
.Select(x => new {
Rank = 1,
Entity = x,
});
var containsResults = dbContext.People
.Where(x => !startsWithResults.Select(y => y.Entity.Id).Contains(x.Id))
.Where(x => x.Name.Contains(keyword))
.Select(x => new {
Rank = 2,
Entity = x,
});
var rankedResults = startsWithResults.Concat(containsResults)
.OrderBy(x => x.Rank);
// TODO: apply thenby ordering here based on the orderBy expression above
dbContext.Dispose();
I have tried ordering the results before selecting the anonymous object with the Rank property, but the ordering ends up getting lost. It seems that linq to entities discards the ordering of the separate sets and converts back to natural ordering during both Concat and Union.
What I think I may be able to do is dynamically transform the expression defined in the orderBy variable from x => x.Name to x => x.Entity.Name, but I'm not sure how:
if (orderBy != null)
{
var transformedExpression = ???
rankedResults = rankedResults.ThenBy(transformedExpression);
}
How might I be able to use Expression.Lambda to wrap x => x.Name into x => x.Entity.Name? When I hard code x => x.Entity.Name into the ThenBy I get the ordering that I want, but the orderBy is provided by the calling class of the query, so I don't want to hard-code it in. I have it hardcoded in the example above for simplicity of explanation only.

This should help. However you are going to have to concrete up the Anonymous type for this to work. My LinqPropertyChain will not work with it, since its going to be difficult to create the Expression<Func<Anonymous, Person>> whilst its still Anonymous.
Expression<Func<Person, object>> orderBy = x => x.Name;
using(var dbContext = new MyDbContext())
{
var keyword = "term";
var startsWithResults = dbContext.People
.Where(x => x.Name.StartsWith(keyword))
.Select(x => new {
Rank = 1,
Entity = x,
});
var containsResults = dbContext.People
.Where(x => !startsWithResults.Select(y => y.Entity.Id).Contains(x.Id))
.Where(x => x.Name.Contains(keyword))
.Select(x => new {
Rank = 2,
Entity = x,
});
var rankedResults = startsWithResults.Concat(containsResults)
.OrderBy(x => x.Rank)
.ThenBy(LinqPropertyChain.Chain(x => x.Entity, orderBy));
// TODO: apply thenby ordering here based on the orderBy expression above
}
public static class LinqPropertyChain
{
public static Expression<Func<TInput, TOutput>> Chain<TInput, TOutput, TIntermediate>(
Expression<Func<TInput, TIntermediate>> outter,
Expression<Func<TIntermediate, TOutput>> inner
)
{
Console.WriteLine(inner);
Console.WriteLine(outter);
var visitor = new Visitor(new Dictionary<ParameterExpression, Expression>
{
{inner.Parameters[0], outter.Body}
});
var newBody = visitor.Visit(inner.Body);
Console.WriteLine(newBody);
return Expression.Lambda<Func<TInput, TOutput>>(newBody, outter.Parameters);
}
private class Visitor : ExpressionVisitor
{
private readonly Dictionary<ParameterExpression, Expression> _replacement;
public Visitor(Dictionary<ParameterExpression, Expression> replacement)
{
_replacement = replacement;
}
protected override Expression VisitParameter(ParameterExpression node)
{
if (_replacement.ContainsKey(node))
return _replacement[node];
else
{
return node;
}
}
}
}
Figured out a way to do this with less Explicite Generics.
Expression<Func<Person, object>> orderBy = x => x.Name;
Expression<Func<Foo, Person>> personExpression = x => x.Person;
var helper = new ExpressionChain(personExpression);
var chained = helper.Chain(orderBy).Expression;
// Define other methods and classes here
public class ExpressionChain<TInput, TOutput>
{
private readonly Expression<Func<TInput, TOutput>> _expression;
public ExpressionChain(Expression<Func<TInput, TOutput>> expression)
{
_expression = expression;
}
public Expression<Func<TInput, TOutput>> Expression { get { return _expression; } }
public ExpressionChain<TInput, TChained> Chain<TChained>
(Expression<Func<TOutput, TChained>> chainedExpression)
{
var visitor = new Visitor(new Dictionary<ParameterExpression, Expression>
{
{_expression.Parameters[0], chainedExpression.Body}
});
var lambda = Expression.Lambda<Func<TInput, TOutput>>(newBody, outter.Parameters);
return new ExpressionChain(lambda);
}
private class Visitor : ExpressionVisitor
{
private readonly Dictionary<ParameterExpression, Expression> _replacement;
public Visitor(Dictionary<ParameterExpression, Expression> replacement)
{
_replacement = replacement;
}
protected override Expression VisitParameter(ParameterExpression node)
{
if (_replacement.ContainsKey(node))
return _replacement[node];
else
{
return node;
}
}
}
}

Since you're ordering by Rank first, and the Rank values are identical within each sequence, you should be able to just sort independently and then concatenate. It sounds like the hiccup here would be that, according to your post, Entity Framework isn't maintaining sorting across Concat or Union operations. You should be able to get around this by forcing the concatenation to happen client-side:
var rankedResults = startsWithResults.OrderBy(orderBy)
.AsEnumerable()
.Concat(containsResults.OrderBy(orderBy));
This should render the Rank property unnecessary and probably simplify the SQL queries being executed against your database, and it doesn't require mucking about with expression trees.
The downside is that, once you call AsEnumerable(), you no longer have the option of appending additional database-side operations (i.e., if you chain additional LINQ operators after Concat, they will use the LINQ-to-collections implementations). Looking at your code, I don't think this would be a problem for you, but it's worth mentioning.

Related

Mock IDocumentQuery with ability to use query expressions

I need to be able to mock IDocumentQuery, to be able to test piece of code, that queries document collection and might use predicate to filter them:
IQueryable<T> documentQuery = client
.CreateDocumentQuery<T>(collectionUri, options);
if (predicate != null)
{
documentQuery = documentQuery.Where(predicate);
}
var list = documentQuery.AsDocumentQuery();
var documents = new List<T>();
while (list.HasMoreResults)
{
documents.AddRange(await list.ExecuteNextAsync<T>());
}
I've used answer from https://stackoverflow.com/a/49911733/212121 to write following method:
public static IDocumentClient Create<T>(params T[] collectionDocuments)
{
var query = Substitute.For<IFakeDocumentQuery<T>>();
var provider = Substitute.For<IQueryProvider>();
provider
.CreateQuery<T>(Arg.Any<Expression>())
.Returns(x => query);
query.Provider.Returns(provider);
query.ElementType.Returns(collectionDocuments.AsQueryable().ElementType);
query.Expression.Returns(collectionDocuments.AsQueryable().Expression);
query.GetEnumerator().Returns(collectionDocuments.AsQueryable().GetEnumerator());
query.ExecuteNextAsync<T>().Returns(x => new FeedResponse<T>(collectionDocuments));
query.HasMoreResults.Returns(true, false);
var client = Substitute.For<IDocumentClient>();
client
.CreateDocumentQuery<T>(Arg.Any<Uri>(), Arg.Any<FeedOptions>())
.Returns(query);
return client;
}
Which works fine as long as there's no filtering using IQueryable.Where.
My question:
Is there any way to capture predicate, that was used to create documentQuery and apply that predicate on collectionDocuments parameter?
Access the expression from the query provider so that it will be passed on to the backing collection to apply the desired filter.
Review the following
public static IDocumentClient Create<T>(params T[] collectionDocuments) {
var query = Substitute.For<IFakeDocumentQuery<T>>();
var queryable = collectionDocuments.AsQueryable();
var provider = Substitute.For<IQueryProvider>();
provider.CreateQuery<T>(Arg.Any<Expression>())
.Returns(x => {
var expression = x.Arg<Expression>();
if (expression != null) {
queryable = queryable.Provider.CreateQuery<T>(expression);
}
return query;
});
query.Provider.Returns(_ => provider);
query.ElementType.Returns(_ => queryable.ElementType);
query.Expression.Returns(_ => queryable.Expression);
query.GetEnumerator().Returns(_ => queryable.GetEnumerator());
query.ExecuteNextAsync<T>().Returns(x => new FeedResponse<T>(query));
query.HasMoreResults.Returns(true, true, false);
var client = Substitute.For<IDocumentClient>();
client
.CreateDocumentQuery<T>(Arg.Any<Uri>(), Arg.Any<FeedOptions>())
.Returns(query);
return client;
}
The important part is where the expression passed to the query is used to create another query on the backing data source (the array).
Using the following example subject under test for demonstration purposes.
public class SubjectUnderTest {
private readonly IDocumentClient client;
public SubjectUnderTest(IDocumentClient client) {
this.client = client;
}
public async Task<List<T>> Query<T>(Expression<Func<T, bool>> predicate = null) {
FeedOptions options = null; //for dummy purposes only
Uri collectionUri = null; //for dummy purposes only
IQueryable<T> documentQuery = client.CreateDocumentQuery<T>(collectionUri, options);
if (predicate != null) {
documentQuery = documentQuery.Where(predicate);
}
var list = documentQuery.AsDocumentQuery();
var documents = new List<T>();
while (list.HasMoreResults) {
documents.AddRange(await list.ExecuteNextAsync<T>());
}
return documents;
}
}
The following sample tests when an expression is passed to the query
[TestMethod]
public async Task Should_Filter_DocumentQuery() {
//Arrange
var dataSource = Enumerable.Range(0, 3)
.Select(_ => new Document() { Key = _ }).ToArray();
var client = Create(dataSource);
var subject = new SubjectUnderTest(client);
Expression<Func<Document, bool>> predicate = _ => _.Key == 1;
var expected = dataSource.Where(predicate.Compile());
//Act
var actual = await subject.Query<Document>(predicate);
//Assert
actual.Should().BeEquivalentTo(expected);
}
public class Document {
public int Key { get; set; }
}

how to declare var keyword for variable globally in mvc controller

I want to declare var keyword in globally for the variable in mvc controller
Because I am using that variable at multiple times thats why I want to use that variable as a globally.
But I dont know that how var is been done by globally because var is different type as per string, int, decimal to place in globally.
For more clear lets see the code
var query = new List<T>();
if (model.CategoryId == -1)
{
query = _Db.Purchase.Where(w => w.IsIncludeIntoStock == true).ToList().GroupBy(x => new { x.ManufacturerId, x.CategoryId, x.Weight, x.WeightTypeId }).ToList();
}
else
{
query = _Db.Purchase.Where(w => w.IsIncludeIntoStock == true && w.CategoryId == model.CategoryId).ToList().GroupBy(x => new { x.ManufacturerId, x.CategoryId, x.Weight, x.WeightTypeId }).ToList();
}
var dataList = (from x in query
select new
{
})
Now, here query variable is used many times in code. Now, I want to declare globally this query variable. This is the latest code that I tried. In this error is showing by giving red line in code.
Firstly, your "Global" definition isn't clear. Do you want to use "query" instance for one Class or want to use for all class that generated by one class.
Secondly if you want to use as Global, you should define your global variable like this
List<dynamic> query=new List<dynamic>();
of course this kind of approach is not healthy way (dynamic using with c#)
By the way, you can't use "var" keyword in out of functions. You have to use certain type of variable destination in class level.
Define Variable in Class Level
In the given piece of code, I try to define global variable at class level.
public class MyTestClass
{
List<dynamic> query=new List<dynamic>();
public MyTestClass()
{
}
public void generateQuery()
{
if (model.CategoryId == -1)
{
query = _Db.Purchase.Where(w => w.IsIncludeIntoStock == true).ToList().GroupBy(x => new { x.ManufacturerId, x.CategoryId, x.Weight, x.WeightTypeId }).ToList();
}
else
{
query = _Db.Purchase.Where(w => w.IsIncludeIntoStock == true && w.CategoryId == model.CategoryId).ToList().GroupBy(x => new { x.ManufacturerId, x.CategoryId, x.Weight, x.WeightTypeId }).ToList();
}
var dataList = (from x in query
select new
{
})
}
}
}
Let's let try to cover another approach.
Use The Generated Class's Variable As Global Variable
At this time you can define your global variable in ancestor class
public class MyAncestorClass
{
List<dynamic> query=query=new List<dynamic>();
}
public class MyChildClass:MyAncestorClass
{
public void generateQuery()
{
if (model.CategoryId == -1)
{
query = _Db.Purchase.Where(w => w.IsIncludeIntoStock == true).ToList().GroupBy(x => new { x.ManufacturerId, x.CategoryId, x.Weight, x.WeightTypeId }).ToList();
}
else
{
query = _Db.Purchase.Where(w => w.IsIncludeIntoStock == true && w.CategoryId == model.CategoryId).ToList().GroupBy(x => new { x.ManufacturerId, x.CategoryId, x.Weight, x.WeightTypeId }).ToList();
}
var dataList = (from x in query
select new
{
})
}
}
}

Linq Where with variable header

I´m trying to do something like this:
public class Person{string name;string surname;}
//...
List<Person> listExample;
//We add Person object in listExample
string variable="name";
listexample.Where(x=>x.(variable)=="John");
Is it posible to do something similar ?
If you need to access non-public fields then you can use reflection:
class Person
{
public Person(string name)
{
this.name = name;
}
string name;
}
List<Person> people = new List<Person>()
{
new Person("Jane"),
new Person("John")
};
string variableName = "name";
string criteria = "John";
var selectedPeople =
people
.Where(person =>
typeof(Person)
.GetFields(BindingFlags.NonPublic | BindingFlags.Instance)
.Where(fieldInfo => String.Equals(fieldInfo.Name, variableName))
.Select(fieldInfo => fieldInfo.GetValue(person) as string)
.SingleOrDefault() == criteria
)
.ToList();
// At this point 'selectedPeople' will contain one 'Person' named "John"
If you need to access properties then you can use GetProperties(BindingFlags bindingAttr) method instead of GetFields(...) method.
class Person
{
public Person(string name)
{
this.Name = name;
}
public string Name { get; set; }
}
// in a method
var selectedPeople =
people
.Where(person =>
typeof(Person)
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(propertyInfo => String.Equals(propertyInfo.Name, variableName))
.Select(propertyInfo => propertyInfo.GetValue(person) as string)
.SingleOrDefault() == criteria
)
.ToList();
You should take a look at BindingFlags enum so that you can select the appropriate enum values.
BindingFlags.Static for static members
BindingFlags.Instance for instance members
BindingFlags.Public for public members
BindingFlags.NonPublic for private / protected members
etc.
The easiest way, considering the class has only two possible fields to filter on, would be to use simple if-else block :
string targetProperty = "name";
string targetValue = "John";
IEnumerable<Person> query = listExample;
if(targetProperty == "name") query = query.Where(x => x.name == targetValue)
else if(targetProperty == "surname") query = query.Where(x => x.surname == targetValue)
var result = query.ToList();
If the actual class has a lot of possible properties/fields to filter on, and you want to avoid too many if-branches, it's time to use Expression as mentioned in the comment below the question :
string targetProperty = "name";
string targetValue = "John";
var param = Expression.Parameter(typeof(Person), "x");
var body = Expression.Equal(
Expression.PropertyOrField(param, targetProperty),
Expression.Constant(targetValue));
var predicate = Expression.Lambda<Func<Person, bool>>(body, param);
var result = listExample.Where(predicate.Compile()).ToList();
The above codes should build predicate expression equivalent to x => x.name == "John" (since targetProperty = "name" and targetValue = "John")
There's a nuget package for this that lets you write;
using System.Linq.Dynamic; //Import the Dynamic LINQ library
//The standard way, which requires compile-time knowledge
//of the data model
var result = myQuery
.Where(x => x.Field1 == "SomeValue")
.Select(x => new { x.Field1, x.Field2 });
//The Dynamic LINQ way, which lets you do the same thing
//without knowing the data model before hand
var result = myQuery
.Where("Field1=\"SomeValue\"")
.Select("new (Field1, Field2)");

How to map an int to its enum description using AutoMapper during a queryable projection?

Here is the enum extension method to get its description attribute.
public static string GetDescription(this Enum enumeration)
{
if (enumeration == null)
throw new ArgumentNullException();
var value = enumeration.ToString();
var type = enumeration.GetType();
var descriptionAttribute =
(DescriptionAttribute[]) type.GetField(value).GetCustomAttributes(typeof (DescriptionAttribute), false);
return descriptionAttribute.Length > 0 ? descriptionAttribute[0].Description : value;
}
Here is the source object:
public class Account {
public int AccountId {get;set;}
public int AccountStatusId {get;set;}
}
Here is the enum:
public enum AccountStatus {
[Description("N/A")]
None,
[Description("OPEN")]
Open,
[Description("CLOSED")]
Closed,
[Description("BAD CREDIT")
Problem
}
Here is the destination object:
public class GetAccountResponse {
public int AccountId {get;set;}
public string Status {get;set;}
}
Here is my attempt to map (using the latest non-static automapper version). Remember this is during an EF queryable projection.
_config = new MapperConfiguration(cfg => cfg.CreateMap<Account, GetAccountsResponse>()
.ForMember(dest => dest.Status,
opts => opts.MapFrom(src => ((AccountStatus) src.AccountStatusId).GetDescription())));
Here is the projection where query is an IQueryable<Account>:
query.ProjectToList<GetAccountResponse>(_config);
This is the exception I get:
Can't resolve this to Queryable Expression
If you check out the signature of the MapFrom method, you'll notice that one of the overloads takes a parameter of type Expression<Func<TSource, TMember>>.
This suggests that you could write a method which builds an expression tree from ternary expressions that can convert any possible value of your enum to its appropriate string. AutoMapper would then convert this into the appropriate SQL expression via LINQ.
Here's an example which just uses the Enum names themselves: you should be able to adapt it straightforwardly to use your Descriptions:
public static class EnumerableExpressionHelper
{
public static Expression<Func<TSource, String>> CreateEnumToStringExpression<TSource, TMember>(
Expression<Func<TSource, TMember>> memberAccess, string defaultValue = "")
{
var type = typeof(TMember);
if (!type.IsEnum)
{
throw new InvalidOperationException("TMember must be an Enum type");
}
var enumNames = Enum.GetNames(type);
var enumValues = (TMember[])Enum.GetValues(type);
var inner = (Expression)Expression.Constant(defaultValue);
var parameter = memberAccess.Parameters[0];
for (int i = 0; i < enumValues.Length; i++)
{
inner = Expression.Condition(
Expression.Equal(memberAccess.Body, Expression.Constant(enumValues[i])),
Expression.Constant(enumNames[i]),
inner);
}
var expression = Expression.Lambda<Func<TSource,String>>(inner, parameter);
return expression;
}
}
You would use it as follows:
CreateMap<Entry, EntryListItem>()
.ForMember(e => e.ReviewStatus,
c => c.MapFrom(EnumerableExpressionHelper.CreateEnumToStringExpression((Entry e) => e.ReviewStatus)))

Transform Expression m => m.Name to m => m[index].Name

Given an Expression<Func<T, TValue>> (like m => m.Name) and an index, I'd like to be able to transform my expression to m => m[index].Name). And I must admit I'm stuck...
I give you the actual scenario if you want the "Why the hell" (and maybe find a better way).
Scenario : imagine a Server Side Editable Grid (without javascript).
I build my grid with an helper which look like that :
#(Html.Grid(Model)
.Columns(columns => {
columns.Edit(m => m.Name);
columns.Edit(m => m.Code);
})
.AsEditable());
Model is an IQueryable<T>
m => m.Name is an Expression<Func<T, TValue>> (TValue is string)
m => m.Code is an Expression<Func<T, TValue>> (TValue is int)
When rendering my view, I'd like to display an html form.
The IQueryable<T> is enumerated (order, pagination). => ok
So I'll have a List<T> of 5, 10 or 20 T items.
And Name and Code should be represented as TextBox, using a classic HtmlHelper.TextBoxFor(Expression<Func<T, TValue>>) (no problem to create the HtmlHelper<T>)
But as I've got a list, if I want correct Model binding, I can't use directly m => m.Name, but should use m => m[indexOfItem in List<T>].Name
Edit : more details :
So let's say we have an entity class
public class Test {
public int Id {get;set;}
public string Name {get;set;}
public string Code {get;set;}
}
Then, a method retrieving an IQueryable<Test>
Then a view
#model IQueryable<Test>
#(Html.Grid(Model)
.Columns(columns => {
columns.Edit(m => m.Name);
columns.Edit(m => m.Code);
})
.AsEditable());
as you see, Model given as parameter is IQueryable<Test>.
m => m.Name
and
m => m.Code
are just properties of the Model (which I wanna display as TextBox in my grid).
The model is an IQueryable<T> (not an IEnumerable<T>), because the Grid manages ordering and Pagination, so that my controller and service layer don't need to know about pagination and ordering.
Is it clearer ?
This could be easily achieved by writing a custom ExpressionVisitor:
public static class ExpressionExtensions
{
private class Visitor : ExpressionVisitor
{
private readonly int _index;
public Visitor(int index)
{
_index = index;
}
protected override Expression VisitParameter(ParameterExpression node)
{
return Expression.ArrayIndex(GetArrayParameter(node), Expression.Constant(_index));
}
public ParameterExpression GetArrayParameter(ParameterExpression parameter)
{
var arrayType = parameter.Type.MakeArrayType();
return Expression.Parameter(arrayType, parameter.Name);
}
}
public static Expression<Func<T[], TValue>> BuildArrayFromExpression<T, TValue>(
this Expression<Func<T, TValue>> expression,
int index
)
{
var visitor = new Visitor(index);
var nexExpression = visitor.Visit(expression.Body);
var parameter = visitor.GetArrayParameter(expression.Parameters.Single());
return Expression.Lambda<Func<T[], TValue>>(nexExpression, parameter);
}
}
and then you could use this extension method like this:
Expression<Func<Test, string>> ex = m => m.Code;
Expression<Func<Test[], string>> newEx = ex.BuildArrayFromExpression(1);
Well, got something (ugly, just for testing purpose) working, but it makes things unclear, so I'll wait for a better solution or build my input another way :
public static Expression<Func<T[], TValue>> BuildArrayFromExpression<T, TValue>(this Expression<Func<T, TValue>> expression, int index)
{
var parameter = Expression.Parameter(typeof(T[]), "m");
Expression body = Expression.ArrayIndex(parameter, Expression.Constant(index));
var type = typeof(T);
var properties = expression.Body.ToString().Split('.');//ugly shortcut for test only
foreach (var property in properties.Skip(1))
{
var pi = type.GetProperty(property);
body = Expression.Property(body, type.GetProperty(property));
type = pi.PropertyType;
}
return Expression.Lambda<Func<T[], TValue>>(body, parameter);
}
used in a RenderMethod, called for each line of my List<T> (the paginated /ordered list returned from my IQueryable<T>)
public override ... RenderContent(T dataItem) {
var helper = new HtmlHelper<T[]>(GridModel.Context, new GridViewDataContainer<T[]>(GridModel.PaginatedItems.ToArray(), GridModel.Context.ViewData));
var modifiedExpression = Expression.BuildArrayFromExpression(GridModel.PaginatedItems.IndexOf(dataItem));//GridModel.PaginatedItems is the List<T> returned when "executing" the IQueryable<T>
var textBox = helper.TextBoxFor(modifiedExpression);
...
}

Resources