Create generic selector for Select() while using Entity Framework - c#

I would like to create a function to retrieve a List of a given property name's Type. But i dont know yet how to create a working lambda selector.
public IList<object> GetDistinctListOfProperty(string propertyName)
{
var propInfoByName = typeof(T).GetProperty(propertyName);
if (propInfoByName == null) return new List<object>();
using (var db = new ApplicationDbContext())
{
var lambda = //TODO
return db.Set<T>().Select(lambda).Distinct().ToList();
}
}

You can use this method to generate lambda
public static Expression<Func<T, object>> LambdaGenerator<T>(string propertyName)
{
var arg = Expression.Parameter(typeof(T), "current");
var property = Expression.Property(arg, propertyName);
var conv = Expression.Convert(property, typeof(object));
var exp = Expression.Lambda<Func<T, object>>(conv, new ParameterExpression[] { arg });
return exp;
}
and then use this method to create your expression
var lambda = LambdaGenerator<T>("Your Property Name");

You can use an expression for this:
private IList<Tout> GetDistinctListOfProperty<Ttable, Tout>(Expression<Func<Ttable, Tout>> returnField) where Ttable : class
{
using (var db = new ApplicationDbContext())
{
return db.Set<Ttable>().Select(returnField).Distinct().ToList();
}
}
You need to wrap the Func into an Expression so that entity can translate it into a valid sql. This version allows you to use intellisense when you choose your parameter. You would call it like this:
var result = GetDistinctListOfProperty<YourTableType>(x => x.YourProperty);
This version will work on every table that is known to your ApplicationDbContext

Related

Converting Action to LambdaExpression

I am using NRules and trying to load rules from database.
For that, I have to use reflection to generate an Expression.
public class Product
{
public string Attribute1 { get; }
public List<int> Category { get; set; }
public void AddCategory (int category){
this.Category.Add(category);
}
}
using NRules.RuleModel;
using NRules.RuleModel.Builders;
var builder = new RuleBuilder();
//some logic for buildin lefthand side
Expression<Action<IContext, Product>> action = (ctx, product) => product.AddCategory(25);
builder.RightHandSide().Action(action);
My goal is to generate "Expression<Action<IContext, Product>> action = (ctx, product) => product.AddCategory(25);" on runtime. I think the only way to do this to use reflection. Because I am reading some values from database.
I could generate the Action by using reflection:
Type actionType = typeof(Action<>).MakeGenericType(new Type[] { typeof(IContext),
Type.GetType(actualModelName) });
MethodInfo eventMethodInfo = type.GetMethod("AddCategory");
Action actionFromReflection = (Action)Delegate.CreateDelegate(actionType, eventMethodInfo);
But NRules method is expecting a LambdaExpression as a parameter.
How can I convert "actionFromReflection" to LambdaExpression?
LambdaExpression le = actionFromReflection ???
A delegate refers to real compiled Code, while a Lambda Expression is an Expression Tree, close to Source-Code, just not in Text-Form. You can Call your Delegate, but nothing else. Creating Source-Code from IL-Code would be a job for a Disassembler.
Using Reflection means "Use something already compiled", that is the opposite of creating it at runtime. So this is the wrong approach.
To create a LambdaExpression at runtime you do something like
ParameterExpression ctx = Expression.Parameter(typeof(Context), "ctx");
ParameterExpression product = Expression.Parameter(typeof(Product), "product");
LambdaExpression lambdaExpr = Expression.Lambda(
/* your actual code, depending on what you want to do */
Expression.Add(
ctx,
Expression.Constant(1)
),
new List<ParameterExpression>() { ctx, product })
Actually this sample does build ctx => ctx +1
If you have no return value, you probably have an Expression.Call.
You have to further investigate, how to express what you want, as an expression Tree. That's a broad topic.
The LambdaExpression you can just cast:
var expr = (Expression<Action<ctx, product>>) lambdaExpr;
After reading Holger's answer struggling couple of hours I've came up with this solution:
In order to create an expression like below on runtime:
Expression<Action<IContext, Model>> printExpression = (ctx, model) => model.AddCategory(5);
I've made a static method which generates an Action for my need:
public static Expression<Action<T, K>> GetActionExpression<T, K>(string methodName, int val)
{
ParameterExpression ctx = Expression.Parameter(typeof(T), "ctx");
ParameterExpression product = Expression.Parameter(typeof(K), "model");
ParameterExpression actionMethodParam = Expression.Parameter(typeof(int), "val");
var lambdaExpr = Expression.Lambda<Action<T, K>>(
Expression.Call(
product,
typeof(K).GetMethod(methodName, new Type[] { typeof(int) }),
Expression.Constant(val)), new ParameterExpression[] {
ctx, product
}
);
return lambdaExpr;
}
This method generates an expression like this:
(ctx, model) => model.{methodName}(val);
Then I called this method by using reflection:
foreach(var item in actions) {
var actionMethodInfo = typeof(ExpressionBuilder).GetMethod("GetActionExpression")
.MakeGenericMethod(typeof(IContext), type);
LambdaExpression action = (LambdaExpression)actionMethodInfo.Invoke(null, new object[] {item.MethodName, item.Value });
}
And that's it.

PropertyInfo into Expression<Func<PropertyType>>

I try to make an expression with a PropertyInfo. The expression should look like:
Expression < Func < PropertyType >>
I need this Expression for this:
https://github.com/aspnet/AspNetCore/blob/8d46b3a64ea784c95dddeb9d421c7cda6de993a2/src/Components/Web/src/Forms/ValidationMessage.cs
The For property needs this kind of Expression.
Can anyone direct me to some links how to get this kind of Expression?
var entity = new Entity;
var propertyInfo = entity.GetType().GetProperty("OneProperty");
var expr = GetExpression(propertyInfo);
// could be:
// Expression<Func<string>> or Expression<Func<int>>
edited;
If you need a property accessor for a property name given as string, you must also pass it the the object as parameter. Therefore you need an
Expression<Func<Entity, PropertyType>>
This function creates such an expression:
private static LambdaExpression CreatePropertyGetterExpression(Type entityType,
string propertyName)
{
PropertyInfo property = entityType.GetProperty(propertyName);
var parameter = Expression.Parameter(entityType, "e");
var body = Expression.MakeMemberAccess(parameter, property);
return Expression.Lambda(body, parameter);
}
This overload allows you to pass the entity type as generic parameter:
private static LambdaExpression CreatePropertyGetterExpression<TEntity>(string propertyName)
{
return CreatePropertyGetterExpression(typeof(TEntity), propertyName);
}
This test
var expr = CreatePropertyGetterExpression<Entity>("OneProperty");
Console.WriteLine(expr);
Console.WriteLine(expr.GetType());
prints (assuming OneProperty to be of type int):
e => e.OneProperty
System.Linq.Expressions.Expression`1[System.Func`2[MyProject.Entity, System.Int32]]
You can get an Expression<Func<PropertyType>> if you capture a variable like this:
Entity e = new Entity { PropertyOne = 42 };
Expression<Func<int>> expr = () => e.PropertyOne;
But how do you want to capture a variable if you are constructing an expression dynamically? This is not possible.
I found this HtmlHelperValidationExtensions.ValidationMessageFor Method doing what you need:
public static IHtmlContent ValidationMessageFor<TModel,TResult> (
this IHtmlHelper<TModel> htmlHelper,
Expression<Func<TModel,TResult>> expression,
string message,
object htmlAttributes);
You can pass it the Expression<Func<TModel,TResult>> expression from above! However, you must know the result type in advance. Otherwise you will have to use reflection to call it.
I found a solution while trying to bind blazor inputs dynamically:
Entity e = new Entity { PropertyOne = 42 };
Expression<Func<int>> expr = () => e.PropertyOne;
to get the expression for this statement you should use a constant expression:
var e = new Entity() { PropertyOne = 42 };
var property = typeof(Entity).GetProperty("PropertyOne");
var entityType = typeof(Entity);
var parameter = Expression.Constant(e);
var body = Expression.MakeMemberAccess(parameter, property);
var resultToReturn = Expression.Lambda<func<TValue>>(body);
best regards.

Store Static Filter By Key Expression

I've got an function which generates an expression to filter a table by it's primary key, when passed in an Object[], this is very similar to Find function except that it doesn't materialize so you can pass an IQueryable around afterwards
public static Expression<Func<T, bool>> FilterByPrimaryKeyPredicate<T>(this DbContext dbContext, object[] id)
{
var keyProperties = dbContext.GetPrimaryKeyProperties<T>();
var parameter = Expression.Parameter(typeof(T), "e");
var body = keyProperties
// e => e.{propertyName} == new {id = id[i]}.id
.Select((p, i) => Expression.Equal(
Expression.Property(parameter, p.Name),
Expression.Convert(
Expression.PropertyOrField(Expression.Constant(new { id = id[i] }), "id"),
p.ClrType)))
.Aggregate(Expression.AndAlso);
return Expression.Lambda<Func<T, bool>>(body, parameter);
}
This works by first getting the primary keys for a table, it creates binary expression foreach property, the Id is wrapped in an anonymous type to leverage the query cache. This is working fine. However, I'd like to take this a step further.
I'd like to preserve the Expression so I don't have to generate it each time I pass on a new set of ids, How can I store this Expression while still leveraging the Query Cache?
Edit TL;DR
So I'm attempt to cache it using array access in a static class as suggest, however I'm encountering an error:
public class PrimaryKeyFilterContainer<T>
{
const string ANON_ID_PROP = "id";
static Expression<Func<T, bool>> _filter;
Type ANON_TYPE = new { id = (object)0 }.GetType();
public object[] id { get; set; }
public PrimaryKeyFilterContainer()
{
}
public Expression<Func<T, bool>> GetFilter(DbContext dbContext, object[] id)
{
this.id = id;
if(null == _filter)
{
var keyProperties = dbContext.GetPrimaryKeyProperties<T>();
var parameter = Expression.Parameter(typeof(T), "e");
var body = keyProperties
// e => e.PK[i] == id[i]
.Select((p, i) => Expression.Equal(
Expression.Property(parameter, p.Name),
Expression.Convert(BuildNewExpression(i),
p.ClrType)))
.Aggregate(Expression.AndAlso);
_filter = Expression.Lambda<Func<T, bool>>(body, parameter);
}
return _filter;
}
NewExpression BuildNewExpression(int index)
{
var currentObject = Expression.Constant(this);
var fieldAccess = Expression.PropertyOrField(currentObject, nameof(id));
var arrayAccess = Expression.ArrayAccess(fieldAccess, Expression.Constant(index));
return Expression.New(ANON_TYPE.GetConstructor(new[] { typeof(object) }), arrayAccess);
}
}
No coercion operator is defined between types '<>f__AnonymousType0`1[System.Object]' and 'System.Int32'
I'm getting closer but I'm not sure if it's going to work still.
As I mentioned in the comments, the main problem is that we cannot use array index access inside the expression tree - EF6 throws not supported exception and EF Core turns it into client evaluation.
So we need to store the keys in a class with dynamic count of properties and property types. Fortunately the System.Tuple generic classes provide such functionality, and can be used in both EF6 and EF Core.
Following is a class that implements the above idea:
public class PrimaryKeyFilter<TEntity>
where TEntity : class
{
object valueBuffer;
Func<object[], object> valueArrayConverter;
public PrimaryKeyFilter(DbContext dbContext)
{
var keyProperties = dbContext.GetPrimaryKeyProperties<TEntity>();
// Create value buffer type (Tuple) from key properties
var valueBufferType = TupleTypes[keyProperties.Count - 1]
.MakeGenericType(keyProperties.Select(p => p.ClrType).ToArray());
// Build the delegate for converting value array to value buffer
{
// object[] values => new Tuple(values[0], values[1], ...)
var parameter = Expression.Parameter(typeof(object[]), "values");
var body = Expression.New(
valueBufferType.GetConstructors().Single(),
keyProperties.Select((p, i) => Expression.Convert(
Expression.ArrayIndex(parameter, Expression.Constant(i)),
p.ClrType)));
valueArrayConverter = Expression.Lambda<Func<object[], object>>(body, parameter).Compile();
}
// Build the predicate expression
{
var parameter = Expression.Parameter(typeof(TEntity), "e");
var valueBuffer = Expression.Convert(
Expression.Field(Expression.Constant(this), nameof(this.valueBuffer)),
valueBufferType);
var body = keyProperties
// e => e.{propertyName} == valueBuffer.Item{i + 1}
.Select((p, i) => Expression.Equal(
Expression.Property(parameter, p.Name),
Expression.Property(valueBuffer, $"Item{i + 1}")))
.Aggregate(Expression.AndAlso);
Predicate = Expression.Lambda<Func<TEntity, bool>>(body, parameter);
}
}
public Expression<Func<TEntity, bool>> Predicate { get; }
public void SetValues(params object[] values) =>
valueBuffer = valueArrayConverter(values);
static readonly Type[] TupleTypes =
{
typeof(Tuple<>),
typeof(Tuple<,>),
typeof(Tuple<,,>),
typeof(Tuple<,,,>),
typeof(Tuple<,,,,>),
typeof(Tuple<,,,,,>),
typeof(Tuple<,,,,,,>),
typeof(Tuple<,,,,,,,>),
};
}
You can create and store an instance of the class. Then use the expression returned by the Predicate property inside the query. And SetValues method to set the parameters.
The drawback is that the value storage is bound to the class instance, hence it cannot be used concurrently. The original approach works well in all scenarios, and the performance impact IMO should be negligible, so you might consider staying on it.

Reading Properties of an Object with Expression Trees

I want to create a Lambda Expression for every Property of an Object that reads the value dynamically.
What I have so far:
var properties = typeof (TType).GetProperties().Where(p => p.CanRead);
foreach (var propertyInfo in properties)
{
var getterMethodInfo = propertyInfo.GetGetMethod();
var entity = Expression.Parameter(typeof (TType));
var getterCall = Expression.Call(entity, getterMethodInfo);
var lambda = Expression.Lambda(getterCall, entity);
var expression = (Expression<Func<TType, "TypeOfProperty">>) lambda;
var functionThatGetsValue = expression.Compile();
}
The code Works well when i call functionThatGetsValue as long as "TypeOfProperty" is hardcoded. I know that I can't pass the "TypeOfPoperty" dynamically. What can I do to achive my goal?
Assuming that you're happy with a Func<TType, object> delegate (as per the comments above), you can use Expression.Convert to achieve that:
var properties = typeof(TType).GetProperties().Where(p => p.CanRead);
foreach (var propertyInfo in properties)
{
MethodInfo getterMethodInfo = propertyInfo.GetGetMethod();
ParameterExpression entity = Expression.Parameter(typeof(TType));
MethodCallExpression getterCall = Expression.Call(entity, getterMethodInfo);
UnaryExpression castToObject = Expression.Convert(getterCall, typeof(object));
LambdaExpression lambda = Expression.Lambda(castToObject, entity);
var functionThatGetsValue = (Func<TType, object>)lambda.Compile();
}
After hours of googling found the answer here. I've added the snippets from the blog post as it might help others having the same troubles:
public static class PropertyInfoExtensions
{
public static Func<T, object> GetValueGetter<T>(this PropertyInfo propertyInfo)
{
if (typeof(T) != propertyInfo.DeclaringType)
{
throw new ArgumentException();
}
var instance = Expression.Parameter(propertyInfo.DeclaringType, "i");
var property = Expression.Property(instance, propertyInfo);
var convert = Expression.TypeAs(property, typeof(object));
return (Func<T, object>)Expression.Lambda(convert, instance).Compile();
}
public static Action<T, object> GetValueSetter<T>(this PropertyInfo propertyInfo)
{
if (typeof(T) != propertyInfo.DeclaringType)
{
throw new ArgumentException();
}
var instance = Expression.Parameter(propertyInfo.DeclaringType, "i");
var argument = Expression.Parameter(typeof(object), "a");
var setterCall = Expression.Call(
instance,
propertyInfo.GetSetMethod(),
Expression.Convert(argument, propertyInfo.PropertyType));
return (Action<T, object>)Expression.Lambda(setterCall, instance, argument).Compile();
}
}
I've modified gsharp's post above to actually set the value directly and make it a bit easier to use. It's not ideal as there is the introduction of the DynamicCast function which requires you to know your type up front. My goal was to try to keep us strongly typed and not return object and avoid dynamic keyword. Also, keep "magic" to a minimum.
public static T DynamicCast<T>(this object value)
{
return (T) value;
}
public static object GetPropertyValue<T>(this PropertyInfo propertyInfo, T objectInstance)
{
if (typeof(T) != propertyInfo.DeclaringType)
{
throw new ArgumentException();
}
var instance = Expression.Parameter(propertyInfo.DeclaringType, "i");
var property = Expression.Property(instance, propertyInfo);
var convert = Expression.TypeAs(property, propertyInfo.PropertyType);
var lambda = Expression.Lambda(convert, instance).Compile();
var result = lambda.DynamicInvoke(objectInstance);
return result;
}
public static void SetPropertyValue<T, TP>(this PropertyInfo propertyInfo, T objectInstance, TP value)
where T : class
where TP : class
{
if (typeof(T) != propertyInfo.DeclaringType)
{
throw new ArgumentException();
}
var instance = Expression.Parameter(propertyInfo.DeclaringType, "i");
var argument = Expression.Parameter(propertyInfo.PropertyType, "a");
var setterCall = Expression.Call(
instance,
propertyInfo.GetSetMethod(),
Expression.Convert(argument, propertyInfo.PropertyType));
var lambda = Expression.Lambda(setterCall, instance, argument).Compile();
lambda.DynamicInvoke(objectInstance, value);
}
Examples:
public void Get_Value_Of_Property()
{
var testObject = new ReflectedType
{
AReferenceType_No_Attributes = new object(),
Int32WithRange1_10 = 5,
String_Requires = "Test String"
};
var result = testObject.GetType().GetProperty("String_Requires").GetPropertyValue(testObject).DynamicCast<string>();
result.Should().Be(testObject.String_Requires);
}
public void Set_Value_Of_Property()
{
var testObject = new ReflectedType
{
AReferenceType_No_Attributes = new object(),
Int32WithRange1_10 = 5,
String_Requires = "Test String"
};
testObject.GetType().GetProperty("String_Requires").SetPropertyValue(testObject, "MAGIC");
testObject.String_Requires.Should().Be("MAGIC");
}
You could write a helper method which uses MakeGenericMethod or an expression tree to make a lambda do the typed call to call DynamicCast based on the PropertyInfo object and avoid having to know it up front. But that is less elegant.

Using the Select method for dynamic queries and expression trees

I am attempting to create a dynamic query using expression trees to match the following statement:
var items = data.Where(i => i.CoverageType == 2).Select(i => i.LimitSelected);
I can create the where method and get a result from it; however, I cannot create the select method.
Here is my where method:
var parm = Expression.Parameter(typeof(BaseClassData), "baseCoverage");
var queryData = data.AsQueryable();
var left = Expression.Property(parm, "CoverageType");
var right = Expression.Constant(2m);
var e1 = Expression.Equal(left, right);
var whereMethod = Expression.Call(
typeof(Queryable),
"Where",
new Type[] { queryData.ElementType },
queryData.Expression,
Expression.Lambda<Func<BaseClassData, bool>>(e1, new ParameterExpression[] { parm }));
This is what I am using for the select method:
var selectParm = Expression.Property(parm, "LimitSelected");
var selectMethod = Expression.Call(
typeof(Enumerable),
"Select",
new Type[]{typeof(BaseClassData), typeof(decimal)},
whereMethod,
Expression.Lambda<Func<BaseClassData, decimal>>(selectParm, new ParameterExpression[]{ parm})
);
When I run the code I get this error:
No generic method 'Select' on type 'System.Linq.Enumerable' is compatible with the supplied type arguments and arguments. No type arguments should be provided if the method is non-generic.
I have also tried changing Enumerable to Queryable and I get the same error.
No need to use Expression.Call, you can directly construct Expression Tree instead; I have create a static method which help me generate dynamic query:
public static void Test(string[] args) {
using (var db = new DBContext()) {
//query 1
var query1 = db.PrizeTypes.Where(m => m.rewards == 1000).Select(t => t.name);
//query 2 which equal to query 1
Expression<Func<PrizeType, bool>> predicate1 = m => m.rewards == 1000;
Expression<Func<PrizeType, string>> selector1 = t => t.name;
var query2 = db.PrizeTypes.Where(predicate1).Select(selector1);
Console.WriteLine(predicate1);
Console.WriteLine(selector1);
Console.WriteLine();
//query 3 which equal to query 1 and 2
Expression<Func<PrizeType, bool>> predicate2 = GetPredicateEqual<PrizeType>("rewards", (Int16)1000);
Expression<Func<PrizeType, string>> selector2 = GetSelector<PrizeType, string>("name");
var query3 = db.PrizeTypes.Where(predicate2).Select(selector2);
Console.WriteLine(predicate2);
Console.WriteLine(selector2);
//as you can see, query 1 will equal query 2 equal query 3
}
}
public static Expression<Func<TEntity, bool>> GetPredicateEqual<TEntity>(string fieldName, object fieldValue) where TEntity : class {
ParameterExpression m = Expression.Parameter(typeof(TEntity), "t");
var p = m.Type.GetProperty(fieldName);
BinaryExpression body = Expression.Equal(
Expression.Property(m, fieldName),
Expression.Constant(fieldValue, p.PropertyType)
);
return Expression.Lambda<Func<TEntity, bool>>(body, m);
}
public static Expression<Func<T, TReturn>> GetSelector<T, TReturn>(string fieldName)
where T : class
where TReturn : class {
var t = typeof(TReturn);
ParameterExpression p = Expression.Parameter(typeof(T), "t");
var body = Expression.Property(p, fieldName);
return Expression.Lambda<Func<T, TReturn>>(body, new ParameterExpression[] { p });
}
This might help with the error you describe above.
You don't need to create your own where/select however, the ones built into c#/linq work just fine with your own classes:
void Main()
{
List<testdata> data = new List<testdata>();
Directory.GetFiles(#"C:\").ToList().ForEach(x=>data.Add(new testdata(){file=x,returnable=1}));
data.Where(x=>x.file.Contains("g")).Select(x=>x.file).Dump();
}
class testdata
{
public string file {get; set;}
public string returnable {get; set;}
}

Categories

Resources