Linq Where with variable header - linq

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)");

Related

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)))

Performance Issue with NHibernate Query

I am currently having a performance problem with the following query written in NHibernate. I am trying to transform the data I queried into DTO's. With this complex structure I cannot use QueryOver to transform the entities. On the other side Linq provider is so useful but it takes ~10 seconds to load and transform ~6000 entities with each 30 child items. It creates an SQL query with left outer join. Are there any other ways to write this query with a better approach?
var Entities = session.Query<crmEntity>()
.Where(x => x.EntityType.ID == EntityType)
.Select(entity => new EntityDTO()
{
ID = entity.ID,
EntityType = entity.EntityType.ID,
InstanceID = entity.Instance.ID,
Values = entity.Values.Select(
value => new CustomFieldValueDTO()
{
ID = value.ID,
FieldID = value.Field.ID,
Value = value.Value
}).ToList<CustomFieldValueDTO>()
}).ToList();
Here is my solution. if there is any other better way, I am completely open to it:
session.CreateQuery(#"select vals.ID,
vals.Field.ID,
vals.Value,
ent.ID
from crmEntity ent inner join ent.Values vals
with vals.Value IS NOT NULL
where ent.EntityType.ID=:eID and ent.Instance.ID=:instanceID order by ent.ID")
.SetGuid("instanceID", InstanceID)
.SetGuid("eID", EntityType)
.SetResultTransformer(new EntityListTransformer()).Future<ReadOnlyEntityDTO>();
And this is my custom result transformer to get the same hierarchy like my linq query
public class EntityListTransformer : IResultTransformer
{
private List<ReadOnlyEntityDTO> list;
private ReadOnlyEntityDTO lastEntity;
private Guid instanceID;
public EntityListTransformer()
{
list = new List<ReadOnlyEntityDTO>();
lastEntity = new ReadOnlyEntityDTO();
}
public System.Collections.IList TransformList(System.Collections.IList collection)
{
return list;
}
public object TransformTuple(object[] tuple, string[] aliases)
{
string ValueID = tuple[0].ToString();
string FieldID = tuple[1].ToString();
string Value = (string)tuple[2];
string EntityID = tuple[3].ToString();
if (lastEntity.ID != EntityID)
{
if (lastEntity.ID != null)
{
list.Add(lastEntity);
}
lastEntity = new ReadOnlyEntityDTO()
{
ID = EntityID
};
}
lastEntity.Values.Add(new ReadOnlyCustomFieldValueDTO()
{
FieldID = FieldID,
ID = ValueID,
Value = Value
});
return tuple;
}
}

How can I transform this linq expression?

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.

How does DynamicQueryable support the Array operator?

I have been using the DynamicQueryable Linq extensions featured in Scott Guthrie's blog post.
The documentation has a table of supported operators. One of the primary operators is the following:
x[…]
Array or indexer access. Multi-dimensional arrays are not supported.
However, I cannot figure out how it can be used.
I didn't expect any of the following to work and in fact they don't.
var ctx = new MyDbContext();
var parameters = new Object[] { new int[] { 1, 2, 3 } };
var qry = ctx.Set<User>().Where<User>("it.Id in #0", parameters);
var qry = ctx.Set<User>().Where<User>("it.Id.In(#0)", parameters);
var qry = ctx.Set<User>().Where<User>("it.Id = #0", parameters);
var qry = ctx.Set<User>().Where<User>("#0.Contains(it.Id)", parameters);
It is basically an In query, but I am not sure how to express it.
This is perhaps a misunderstanding. Meant is that it is possible to query for collection elements at a specific index position. For example:
public class Order
{
public List<OrderDetail> OrderDetails { get; set; }
}
public class OrderDetail
{
public string Description { get; set; }
}
Then you can query for all orders which have the Detail description "Bicycle" in the third OrderDetail by:
string parameter = "Bicycle";
var qry = ctx.Set<Order>().Where<Order>("it.OrderDetails[2].Description == #0",
parameter);
I think for your purpose you need to build up an "OR" chain "it.Id == 1 or it.Id == 2 or it.Id == 3" (or build this query string dynamically in a loop) without parameters in the Where method.

How to dynamically choose two fields from a Linq query as a result

If you have a simple Linq query like:
var result = from record in db.Customer
select new { Text = record.Name,
Value = record.ID.ToString() };
which is returning an object that can be mapped to a Drop Down List, is it possible to dynamically specify which fields map to Text and Value?
Of course, you could do a big case (switch) statement, then code each Linq query separately but this isn't very elegant. What would be nice would be something like:
(pseudo code)
var myTextField = db.Customer["Name"]; // Could be an enumeration??
var myValueField = db.Customer["ID"]; // Idea: choose the field outside the query
var result = from record in db.Customer
select new { Text = myTextField,
Value = myValueField };
Right way to do this is with closures.
Func<Customer, string> myTextField = (Customer c) => c["Name"];
Func<Customer, int> myValueField = (Customer c) => c["ID"];
var result = from record in db.Customer
select new { Text = myTextField(record),
Value = myValueField(record) };
The one limitation is that your definition of myTextField always needs to return a string.
You could try something like
class Customer
{
public int ID { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
}
var dict = new Dictionary<string, Func<Customer, string>>
{ { "ID", (Customer c) => c.ID.ToString() },
{ "Name", (Customer c) => c.Name},
{ "Surname", (Customer c) => c.Surname } };
List<Customer> rows = new List<Customer>();
rows.Add(new Customer { ID = 1, Name = "Foo", Surname = "Bar"});
var list = from r in rows
select new { Text = dict["ID"](r), Value = dict["Name"](r) };
To try to access the properties dynamically, you could try something like
var dict = new Dictionary<string, Func<Customer, string>>
{ { "ID", (Customer c) => c.GetType().GetProperty("ID").GetValue(c,null).ToString() },
{ "Name", (Customer c) => c.GetType().GetProperty("Name").GetValue(c,null).ToString()},
{ "Surname", (Customer c) => c.GetType().GetProperty("Surname").GetValue(c,null).ToString() } };

Resources