I'm trying to create a function where I can pass in an expression to say which properties I'm interested in. I have it working for top level properties but not for nested properties.
Example model
public class Foo {
public string Name { get; set; }
public List<Foo> List { get; set; }
}
What I have so far
private PropertyInfo GetPropertyInfo<TModel>(Expression<Func<TModel, object>> selector)
{
if (selector.NodeType != ExpressionType.Lambda)
{
throw new ArgumentException("Selector must be lambda expression", nameof(selector));
}
var lambda = (LambdaExpression)selector;
var memberExpression = ExtractMemberExpression(lambda.Body);
if (memberExpression == null)
throw new ArgumentException("Selector must be member access expression", nameof(selector));
if (memberExpression.Member.DeclaringType == null)
{
throw new InvalidOperationException("Property does not have declaring type");
}
return memberExpression.Member.DeclaringType.GetProperty(memberExpression.Member.Name);
}
private static MemberExpression ExtractMemberExpression(Expression expression)
{
if (expression.NodeType == ExpressionType.MemberAccess)
{
return ((MemberExpression)expression);
}
if (expression.NodeType == ExpressionType.Convert)
{
var operand = ((UnaryExpression)expression).Operand;
return ExtractMemberExpression(operand);
}
return null;
}
So:
GetPropertyInfo<Foo>(x => x.Name); // works
GetPropertyInfo<Foo>(x => x.List.Select(y => y.Name); <-- how do I get this?
I'm looking for a way to pick any property from a complex object.
You need to extend your ExtractMemberExpression just a bit to accept Select call expression:
private MemberExpression ExtractMemberExpression(Expression expression) {
if (expression.NodeType == ExpressionType.MemberAccess) {
return ((MemberExpression) expression);
}
if (expression.NodeType == ExpressionType.Convert) {
var operand = ((UnaryExpression) expression).Operand;
return ExtractMemberExpression(operand);
}
if (expression.NodeType == ExpressionType.Lambda) {
return ExtractMemberExpression(((LambdaExpression) expression).Body);
}
if (expression.NodeType == ExpressionType.Call) {
var call = (MethodCallExpression) expression;
// any method named Select with 2 parameters will do
if (call.Method.Name == "Select" && call.Arguments.Count == 2) {
return ExtractMemberExpression(call.Arguments[1]);
}
}
return null;
}
How can I iterate through an expression and change the property names based on a custom attribute that I decorated them with?
I use the following code to get the custom attribute of a property, but it works for a simple expression with one property:
var comparison = predicate.Body as BinaryExpression;
var member = (comparison.Left.NodeType == ExpressionType.Convert ?
((UnaryExpression)comparison.Left).Operand :
comparison.Left) as MemberExpression;
var value = comparison.Right as ConstantExpression;
var attribute = Attribute.GetCustomAttribute(member.Member, typeof(MyAttribute)) as MyAttribute;
var columnName = attribute.Name ?? member.Member.Name;
var columnValue = value.Value;
EDIT
Deriving from ExpressionVisitor, I can change the property name by overriding the method VisitMember.
Is this the only place where a property name is used to build the expression?
You can implement a System.Linq.Expressions.ExpressionVisitor to rewrite MemberExpression with the new mapped property. And yes VisitMember is the only place you have to implement this remapping, this is one of the advantages to expression trees and visitors. The only weird case you have to deal with is if the property's type is different to the type of the mapped property.
void Main()
{
var data = new List<TestClass>();
data.Add(new TestClass()
{
FirstName = "First",
LastName = "Last",
});
var q = data.AsQueryable().Select(x => x.FirstName);
var vistor = new MyRewriter();
var newExpression = vistor.Visit(q.Expression);
var output = newExpression.ToString();
//System.Collections.Generic.List`1[UserQuery+TestClass].Select(x => x.LastName)
}
class TestClass
{
[MyAttribute(nameof(LastName))]
public string FirstName { get; set; }
public string LastName { get; set; }
}
class MyAttribute : Attribute
{
public string MapTo { get; }
public MyAttribute(string mapTo)
{
MapTo = mapTo;
}
}
class MyRewriter : ExpressionVisitor
{
protected override Expression VisitMember(System.Linq.Expressions.MemberExpression node)
{
var att = node.Member.GetCustomAttribute<MyAttribute>();
if (att != null)
{
var newMember = node.Expression.Type.GetProperty(att.MapTo);
if (newMember != null)
{
return Expression.Property(
Visit(node.Expression), // Its very important to remember to visit the inner expression
newMember);
}
}
return base.VisitMember(node);
}
}
You can run this in LinqPad to test it. This code assumes that you are mapping to a property.
I have the following LINQ query:
var result = from person in dbContext.Person
select new
{
FirstName = person.FirstName,
LastName = person.LastName,
// I want to save this logic
JobCount = person.Jobs.Count(x => x.Completed)
};
}
To avoid repeating myself in other LINQ queries, I would like to make the JobCount lambda logic available for use in other queries.
I thought I might be able to use Func<Person, int>, like so:
public Func<Person, int> GetCompletedJobsForPerson = person => person.Jobs.Count(x => x.Completed);
var result = from person in dbContext.Person
select new
{
FirstName = person.FirstName,
LastName = person.LastName,
// Use Invoke to get amount
JobCount = GetCompletedJobsForPerson.Invoke(person)
};
}
PROBLEM STATEMENT: This fails because the method cannot be mapped to an SQL statement and causes a NotSupportedException
NotSupportedException was unhandled
The LINQ expression node type 'Invoke' is not supported in LINQ to Entities.
How can I make the lambda reusable from multiple LINQ queries?
It can't be done in an easy way (and if it could be done easily, someone would have done it :-) )
What could be done is use the same trick of PredicateBuilder and create an AsExpandable that will replace some "tokens" (function calls) in your query with some other function calls. But I don't think it's worth the trouble. It is some hundred lines of code to do it "correctly".
The other problem is that the query would then need this special method to be called:
var result = (from person in dbContext.Person
select new
{
FirstName = person.FirstName,
LastName = person.LastName,
// Use Invoke to get amount
JobCount = GetCompletedJobsForPerson(person)
}).FixMethodCalls();
Ok... it was difficult, but doable:
// v0.11 Codename: Handle with Care+ (+ == Plus)
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property)]
public class ExpandableAttribute : Attribute
{
// Just to know the suffix to use :-)
public static readonly string ExpandableSuffix = "Expression";
}
// Replaces method and properties calls to "special" method calls that
// are Expression(s). These method/property calls can be used anywhere in
// the query (Select, Where, GroupBy, ...)
// Remember to use .AsExpandable2() somewhere in your query (it must be a
// "top level" part of the query):
// OK:
// var res1 = (from x in table select x).AsExpandable2();
// var res2 = table.AsExpandable().Where(x => true);
// var res3 = table.Where(x => true).AsExpandable2();
// var res4 = table.Where(x => true).AsExpandable2().Select(x => x);
// var res5 = table.Where(x => true).AsExpandable2().Select(x => x).AsExpandable2();
// Not OK:
// var res1 = table.Select(x => x.subtable.AsExpandable2());
// **Method calls**
// The methods to be expanded can be static or instance. There must be
// a corresponding **static* method with same name and suffix
// "Expression", that doesn't have parameters and returns an Expression
// with a certain signature.
// Static:
// var res2 = table.AsExpandable2().Select(x => MyClass.StaticMethod(1, x, 2, 3));
// There must be in the class MyClass
// public/private/protected static Expression<Func<int, MyClass, int, int, returnType(StaticMethod)>> StaticMethodExpression()
// Instance:
// var res1 = table.AsExpandable2().Select(x => x.InstanceMethod(1, 2, 3));
// There must be in the class x.GetType()
// public/private/protected static Expression<Func<x.GetType(), int, int, int, returnType(InstanceMethod)>> InstanceMethodExpression()
// Note that multiple "tables" can be passed as parameters:
// Static:
// var res3 = (from x in table1 from y in table2 select new { x, y }).AsExpandable2().Select(z => MyClass.StaticMethod(1, z.x, z.y, 2, 3));
// There must be in the class MyClass
// public/private/protected/internal static Expression<Func<int, x.GetType(), y.GetType(), int, int, returnType(StaticMethod)>> StaticMethodExpression()
// Instance:
// var res4 = (from x in table1 from y in table2 select new { x, y }).AsExpandable2().Select(z => z.x.StaticMethod(1, z.y, 2, 3));
// There must be in the class x.GetType()
// public/private/protected/internal static Expression<Func<x.GetType(), int, y.GetType(), int, int, returnType(StaticMethod)>> InstanceMethodExpression()
// **Properties**
// Same as with method calls, but with properties :-)
// (useful for things like FullName, where
// FullName = Name + ' ' + Surname)
// Remember that the *Expression property must be **static**!
// Static (not very useful :-) ):
// var res1 = table.AsExpandable2().Select(x => MyClass.StaticProperty);
// There must be in the class MyClass
// public/private/protected/internal static Expression<Func<MyClass.StaticProperty.GetType()>> StaticPropertyExpression { get; }
// Instance:
// var res2 = table.AsExpandable2().Select(x => x.InstanceProperty);
// There must be in the class x.GetType()
// public/private/protected/internal static Expression<Func<x.GetType(), x.InstanceProperty.GetType())>> InstancePropertyExpression { get; }
public static class MethodsPropertiesExpander
{
// Because AsExpandable() is already used by http://www.albahari.com/nutshell/linqkit.aspx
public static IQueryable<T> AsExpandable2<T>(this IQueryable<T> source)
{
if (source is MethodsPropertiesExpander<T>)
{
return source;
}
return new MethodsPropertiesExpander<T>(source);
}
}
public interface IMethodsPropertiesExpander
{
}
public class MethodsPropertiesExpander<T> : IOrderedQueryable<T>, IQueryProvider, IMethodsPropertiesExpander
{
public readonly IQueryable<T> Query;
public MethodsPropertiesExpander(IQueryable<T> query)
{
if (!(query is IMethodsPropertiesExpander))
{
Expression expression = MethodsPropertiesReplacer.Default.Visit(query.Expression);
Query = expression == query.Expression ? query : query.Provider.CreateQuery<T>(expression);
}
else
{
Query = query;
}
}
/* IQueryable<T> */
public IEnumerator<T> GetEnumerator()
{
return Query.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public Type ElementType
{
get { return Query.ElementType; }
}
public Expression Expression
{
get { return Query.Expression; }
}
public IQueryProvider Provider
{
get { return this; }
}
/* IQueryProvider */
public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
{
return new MethodsPropertiesExpander<TElement>(Query.Provider.CreateQuery<TElement>(expression));
}
public IQueryable CreateQuery(Expression expression)
{
Type iqueryableArgument = GetIQueryableTypeArgument(expression.Type);
MethodInfo createQueryImplMethod = typeof(MethodsPropertiesExpander<T>)
.GetMethod("CreateQuery", BindingFlags.Instance | BindingFlags.NonPublic)
.MakeGenericMethod(iqueryableArgument);
return (IQueryable)createQueryImplMethod.Invoke(this, new[] { expression });
}
public TResult Execute<TResult>(Expression expression)
{
if (!(Query.Provider is IMethodsPropertiesExpander))
{
// We want to expand it only once :-)
expression = MethodsPropertiesReplacer.Default.Visit(expression);
}
return Query.Provider.Execute<TResult>(expression);
}
public object Execute(Expression expression)
{
if (!(Query.Provider is IMethodsPropertiesExpander))
{
// We want to expand it only once :-)
expression = MethodsPropertiesReplacer.Default.Visit(expression);
}
return Query.Provider.Execute(expression);
}
/* Implementation methods */
/// <summary>
/// Gets the T of IQueryablelt;T>
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
protected static Type GetIQueryableTypeArgument(Type type)
{
IEnumerable<Type> interfaces = type.IsInterface ?
new[] { type }.Concat(type.GetInterfaces()) :
type.GetInterfaces();
Type argument = (from x in interfaces
where x.IsGenericType
let gt = x.GetGenericTypeDefinition()
where gt == typeof(IQueryable<>)
select x.GetGenericArguments()[0]).FirstOrDefault();
return argument;
}
/* Utility classes */
protected sealed class MethodsPropertiesReplacer : ExpressionVisitor
{
// Single instance is enough!
public static readonly MethodsPropertiesReplacer Default = new MethodsPropertiesReplacer();
private MethodsPropertiesReplacer()
{
}
protected override Expression VisitMember(MemberExpression node)
{
PropertyInfo property = node.Member as PropertyInfo;
MethodInfo getter;
// We handle only properties (that aren't indexers) that have
// a get
if (property != null && property.GetIndexParameters().Length == 0 && (getter = property.GetGetMethod(true)) != null)
{
// We work only on methods marked as [ExpandableAttribute]
var attribute = property.GetCustomAttributes(typeof(ExpandableAttribute), false).FirstOrDefault();
if (attribute != null)
{
string name = property.Name + ExpandableAttribute.ExpandableSuffix;
var property2 = property.DeclaringType.GetProperty(name, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, null, Type.EmptyTypes, null);
if (property2 == null || property2.GetGetMethod(true) == null)
{
if (property2 == null)
{
if (property.DeclaringType.GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, null, Type.EmptyTypes, null) != null)
{
throw new NotSupportedException(string.Format("{0}.{1} isn't static!", property.DeclaringType.FullName, name));
}
throw new NotSupportedException(string.Format("{0}.{1} not found!", property.DeclaringType.FullName, name));
}
// property2.GetGetMethod(true) == null
throw new NotSupportedException(string.Format("{0}.{1} doesn't have a getter!", property.DeclaringType.FullName, name));
}
// Instance Parameters have the additional
// "parameter" of the declaring type
var argumentsPlusReturnTypes = getter.IsStatic ?
new[] { node.Type } :
new[] { property.DeclaringType, node.Type };
var funcType = typeof(Func<>).Assembly.GetType(string.Format("System.Func`{0}", argumentsPlusReturnTypes.Length));
var returnType = typeof(Expression<>).MakeGenericType(funcType.MakeGenericType(argumentsPlusReturnTypes));
if (property2.PropertyType != returnType)
{
throw new NotSupportedException(string.Format("{0}.{1} has wrong return type!", property.DeclaringType.FullName, name));
}
var expression = (LambdaExpression)property2.GetValue(null, null);
// Instance Members have the additional "parameter"
// of the declaring type
var arguments2 = getter.IsStatic ? new Expression[0] : new[] { node.Expression };
var replacer = new SimpleExpressionReplacer(expression.Parameters, arguments2);
var body = replacer.Visit(expression.Body);
return this.Visit(body);
}
}
return base.VisitMember(node);
}
protected override Expression VisitMethodCall(MethodCallExpression node)
{
MethodInfo method = node.Method;
// We work only on methods marked as [ExpandableAttribute]
var attribute = method.GetCustomAttributes(typeof(ExpandableAttribute), false).FirstOrDefault();
if (attribute != null)
{
string name = method.Name + ExpandableAttribute.ExpandableSuffix;
var method2 = method.DeclaringType.GetMethod(name, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
if (method2 == null)
{
if (method.DeclaringType.GetMethod(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null) != null)
{
throw new NotSupportedException(string.Format("{0}.{1} isn't static!", method.DeclaringType.FullName, name));
}
throw new NotSupportedException(string.Format("{0}.{1} not found!", method.DeclaringType.FullName, name));
}
// Instance methods have the additional "parameter" of
// the declaring type
var argumentsPlusReturnTypes = method.IsStatic ?
node.Arguments.Select(x => x.Type).Concat(new[] { node.Type }).ToArray() :
new[] { method.DeclaringType }.Concat(node.Arguments.Select(x => x.Type)).Concat(new[] { node.Type }).ToArray();
var funcType = typeof(Func<>).Assembly.GetType(string.Format("System.Func`{0}", argumentsPlusReturnTypes.Length));
var returnType = typeof(Expression<>).MakeGenericType(funcType.MakeGenericType(argumentsPlusReturnTypes));
if (method2.ReturnType != returnType)
{
throw new NotSupportedException(string.Format("{0}.{1} has wrong return type!", method.DeclaringType.FullName, name));
}
var expression = (LambdaExpression)method2.Invoke(null, null);
// Instance methods have the additional "parameter" of
// the declaring type
var arguments2 = method.IsStatic ? node.Arguments : new[] { node.Object }.Concat(node.Arguments);
var replacer = new SimpleExpressionReplacer(expression.Parameters, arguments2);
var body = replacer.Visit(expression.Body);
return this.Visit(body);
}
return base.VisitMethodCall(node);
}
}
}
// A simple expression visitor to replace some nodes of an expression
// with some other nodes
public class SimpleExpressionReplacer : ExpressionVisitor
{
public readonly Dictionary<Expression, Expression> Replaces;
public SimpleExpressionReplacer(Dictionary<Expression, Expression> replaces)
{
Replaces = replaces;
}
public SimpleExpressionReplacer(IEnumerable<Expression> from, IEnumerable<Expression> to)
{
Replaces = new Dictionary<Expression, Expression>();
using (var enu1 = from.GetEnumerator())
using (var enu2 = to.GetEnumerator())
{
while (true)
{
bool res1 = enu1.MoveNext();
bool res2 = enu2.MoveNext();
if (!res1 || !res2)
{
if (!res1 && !res2)
{
break;
}
if (!res1)
{
throw new ArgumentException("from shorter");
}
throw new ArgumentException("to shorter");
}
Replaces.Add(enu1.Current, enu2.Current);
}
}
}
public SimpleExpressionReplacer(Expression from, Expression to)
{
Replaces = new Dictionary<Expression, Expression> { { from, to } };
}
public override Expression Visit(Expression node)
{
Expression to;
if (node != null && Replaces.TryGetValue(node, out to))
{
return base.Visit(to);
}
return base.Visit(node);
}
}
I've added a bonus: you can even "expand" special properties. The instructions on how to use it are in the big comment at the beginning. Now I'll give you some examples:
// Generated by EF
public partial class MyClass
{
public int ID { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public ICollection<MyInnerClass> MyInnerClass;
}
// Written by you (remember the partial!)
public partial class MyClass
{
[Expandable]
public int CountMyInnerClass()
{
// Not necessary to implement, unless you want to use it C#-side
throw new NotImplementedException();
}
[Expandable]
public int CountMyInnerClassPlus(int num)
{
// Not necessary to implement, unless you want to use it C#-side
throw new NotImplementedException();
}
[Expandable]
public int CountMyInnerClassProperty
{
get
{
// Not necessary to implement, unless you want to use it C#-side
throw new NotImplementedException();
}
}
[Expandable]
public string FullName
{
get
{
// Not necessary to implement, unless you want to use it C#-side
return Name + " " + Surname;
}
}
protected static Expression<Func<MyClass, int>> CountMyInnerClassExpression()
{
return x => x.MyInnerClass.Count();
}
protected static Expression<Func<MyClass, int, int>> CountMyInnerClassPlusExpression()
{
return (x, num) => x.MyInnerClass.Count() + num;
}
protected static Expression<Func<MyClass, int>> CountMyInnerClassPropertyExpression
{
get
{
return x => x.MyInnerClass.Count();
}
}
protected static Expression<Func<MyClass, string>> FullNameExpression
{
get
{
return x => x.Name + " " + x.Surname;
}
}
}
and then, in some other class class (perhaps the ones of the query):
[Expandable]
public static int LocalCountMyInnerClassPlus(MyClass x, int num)
{
// Not necessary to implement, unless you want to use it C#-side
throw new NotImplementedException();
}
public static Expression<Func<MyClass, int, int>> LocalCountMyInnerClassPlusExpression()
{
return (x, num) => x.MyInnerClass.Count() + num;
}
and then
var query = (from x in db.MyClasses
select new
{
x.ID,
x.FullName,
Count1 = x.CountMyInnerClass(),
Count2 = x.CountMyInnerClassPlus(5),
Count3 = x.CountMyInnerClassProperty,
Count4 = LocalCountMyInnerClassPlus(x, 10),
}).AsExpandable2().ToList();
and it just works :-)
I currently have a custom ValidationAttribute that ensures that a property is unique. It looks like this:
public class UniqueLoginAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
Context db = new Context();
if (db.Users.SingleOrDefault(user => user.Login == (string)value) != null)
{
return new ValidationResult(validationContext.DisplayName + " is already taken.");
}
return null;
}
}
}
What I'd like to is make this Validation work with any Entity/Property combination. In other words, set both the Entity (in this case "Users") and Property (in this case "Login"), at runtime. I've found some examples of DynamicLINQ, but I'd like a pure EF solution, which I can't seem to find.
This will work. You need to build the expression tree manually.
Usage
Expression<Func<User, string>> userExp = x => x.Login;
UniqueAttribute u = new UniqueAttribute(userExp);`
EDIT: Removed generics to work w/ Attribute. You need to use reflection w/ the runtime types to get the appropriate SingleOrDefault method. Please note there is no compile time type checking on your expressions with this code. You should always declare the expression first (the way I did in the usage sample) to avoid type problems.
public class UniqueAttribute
{
private LambdaExpression Selector { get; set; }
private Type EntityType { get; set; }
public UniqueAttribute(LambdaExpression selector) {
this.EntityType = selector.Parameters[0].Type;
this.Selector = selector;
}
private LambdaExpression GeneratePredicate(object value) {
ParameterExpression param = Selector.Parameters[0];
Expression property = Selector.Body;
Expression valueConst = Expression.Constant(value);
Expression eq = Expression.Equal(property, valueConst);
LambdaExpression predicate = Expression.Lambda(eq, new ParameterExpression[]{param});
return predicate;
}
private TEntity SingleOrDefault<TEntity>(IQueryable<TEntity> set, LambdaExpression predicate) {
Type queryableType = typeof(Queryable);
IEnumerable<MethodInfo> allSodAccessors = queryableType.GetMethods(BindingFlags.Static | BindingFlags.Public).Where(x => x.Name=="SingleOrDefault");
MethodInfo twoArgSodAccessor = allSodAccessors.Single(x => x.GetParameters().Length == 2);
MethodInfo withGenArgs = twoArgSodAccessor.MakeGenericMethod(new []{typeof(TEntity)});
return (TEntity) withGenArgs.Invoke(null, new object[]{set, predicate});
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
Context db = new Context();
if (SingleOrDefault(db.Set(EntityType), GeneratePredicate(value)) != null)
{
return new ValidationResult(validationContext.DisplayName + " is already taken.");
}
return null;
}
}
This may work for you, but has the downside of needing to retrieve the full list of entities and loop through them to look for a match. You can specify the entity and property as arguments to the attribute.
public class UniqueAttribute : ValidationAttribute
{
public UniqueAttribute(Type entityType, string propertyName)
{
_entityType = entityType;
_propertyName = propertyName;
}
private Type _entityType;
private string _propertyName;
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
Context db = new Context();
foreach (var item in db.Set(_entityType).ToList())
{
if (value.Equals(GetPropertyValue(item, _propertyName))
{
return new ValidationResult(validationContext.DisplayName + " is already taken.");
}
}
return null;
}
private object GetPropertyValue(object item, string propertyName)
{
var type = item.GetType();
var propInfo = type.GetProperty(propertyName);
return (propInfo != null) ? propInfo.GetValue(value, null) : null;
}
}
}
Usage:
[Unique(typeof(User), "Login")]
public string Login { get; set; }
I am trying to copy the behavior of Entity Framework in creating the query from expression and i found my way using ExpressionVisitor when getting the property of the model by using Attribute
this is what i got so far
internal class NVisitor : ExpressionVisitor
{
private readonly ParameterExpression _parameter;
private readonly Type _type;
public NVisitor(Type type)
{
_type = type;
_parameter = Expression.Parameter(type);
}
protected override Expression VisitParameter(ParameterExpression node)
{
return _parameter;
}
protected override Expression VisitMember(MemberExpression node)
{
if (node.Member.MemberType == MemberTypes.Property)
{
var memberName = node.Member.Name;
PropertyInfo otherMember = _type.GetProperty(memberName);
var ncols = node.Member.GetCustomAttributes(typeof(NColumn), true);
if (ncols.Any())
{
var ncol = (NColumn)ncols.First();
otherMember = _type.GetProperty(ncol.Name);
}
var inner = Visit(node.Expression);
return Expression.Property(inner, otherMember);
}
return base.VisitMember(node);
}
}
i have an attribute NColumn that indicates the real name of the property from table column so i mark the property of the model by Attribute
public class BonusTypeX
{
[NColumn("BonusTypeName")]
public string Name { get; set; }
}
now when im trying to get the expression,
[TestMethod]
public void ExpressionTesting2()
{
string searchKey = "Xmas";
Expression<Func<BonusTypeX, bool>> expression = x => x.Name.Contains(searchKey);
Type t = typeof(tbl_BonusType);
var body = new NVisitor(t).Visit(expression.Body);
string a = string.Join(".", body.ToString().Split('.').Skip(1));
Assert.AreEqual("BonusTypeName.Contains(\"Xmas\")", a);
}
i got this
BonusTypeName.Contains(value(Payroll.Test.Administration.TestRepositories+<>c__DisplayClass13).searchKey)
what i am expecting to get is
BonusTypeName.Contains("Xmas")
is there any method that gets the expression string? i am using
string a = string.Join(".", body.ToString().Split('.').Skip(1));
which i think it might be wrong.. :)
any help would be appreciated.
Local variable are captured in a compiler generated class at runtime, this explains the Payroll.Test.Administration.TestRepositories+<>c__DisplayClass13).searchKey part. To get the generated field's value in your expression you must explicitly replace it's value when visiting the expression:
protected override Expression VisitMember(MemberExpression node)
{
if (node.Member.MemberType == MemberTypes.Property)
{
var memberName = node.Member.Name;
PropertyInfo otherMember = _type.GetProperty(memberName);
var ncols = node.Member.GetCustomAttributes(typeof(NColumn), true);
if (ncols.Any())
{
var ncol = (NColumn)ncols.First();
otherMember = _type.GetProperty(ncol.Name);
}
var inner = Visit(node.Expression);
return Expression.Property(inner, otherMember);
}
if (node.Member.MemberType == MemberTypes.Field)
{
if (node.Expression is ConstantExpression)
{
var owner = ((ConstantExpression)node.Expression).Value;
var value = Expression.Constant(((FieldInfo)node.Member).GetValue(owner));
return value;
}
}
return base.VisitMember(node);
}