Get property value from MemberExpression - c#

I'm attempting to retrieve the value of a property from an instance of MemberExpression.
Here is what I have so far:
protected override void VisitMember(Context context, MemberExpression node)
{
var propertyInfo = node.Member as PropertyInfo;
if(propertyInfo != null)
{
var v = propertyInfo.GetValue(node.Member , null);
val = Convert.ToString(v);
}
context.State.Append(val);
}
Depending on the approach I take there are two problems: I don't know the expected type (string, int, etc...), and/or I have not been able access the instance from the MemberExpression.
I am writing a small lambda expressions to T-SQL converter. For example (u)=> u.FirstName == u.LastName; would convert to FirstName = 'chuck'. I've almost got it working!
update
I tried the following code:
...
var propertyInfo = node.Member as PropertyInfo;
if(propertyInfo != null)
{
var o = propertyInfo.GetValue(node.Expression, null);
}
...
It did not work. I get the following error:
System.Reflection.TargetException : Object does not match target type.
update 2
This is what I am trying to accomplish:
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();
}
But I do not know T at compile time.

I don't know the expected type (string, int, etc...),
Use Expression.Type
I have not been able access the instance from the MemberExpression
Use MemberExpression.Expression - obviously that's another expression, because you might have:
foo.GetBar(20).ToString().Length
in which case the Length property would be a MemberExpression, but the Expression property would give the MethodCallExpression for ToString.

I have not been able access the instance from the MemberExpression.
In your example u => u.FirstName == "chuck", there is no instance for which to fetch the FirstName property.
I think you actually want the name of the property - which is node.Member.Name (and is "FirstName" in your example). Note that this works for all MemberInfo, not just PropertyInfo, so fields will work as well. (You may want to test anyway, because Events are also member expressions, but don't make sense here.)
I don't know the expected type (string, int, etc...),
The expected type is either PropertyInfo.PropertyType or FieldInfo.FieldType.

Related

Pass a class property as parameter [duplicate]

Is there a way to supply a name to a function that then returns the value of either the field or property on a given object with that name? I tried to work around it with the null-coalesce operator, but apparently that doesn't like different types (which is also a bit weird to me because null is null). I could separate it it out into if nulls, but there has to be a better way to do this. Here is my function, and the two lines with Comparison objects don't compile, but I will leave them in there to show what I am trying to do.
private void SortByMemberName<T>(List<T> list, string memberName, bool ascending)
{
Type type = typeof (T);
MemberInfo info = type.GetField(memberName) ?? type.GetProperty(memberName);
if (info == null)
{
throw new Exception("Member name supplied is neither a field nor property of type " + type.FullName);
}
Comparison<T> asc = (t1, t2) => ((IComparable) info.GetValue(t1)).CompareTo(info.GetValue(t2));
Comparison<T> desc = (t1, t2) => ((IComparable) info.GetValue(t2)).CompareTo(info.GetValue(t1));
list.Sort(ascending ? asc : desc);
}
I have heard of something called dynamic LINQ that could be used, but for the sake of learning, I am doing it my way.
Change this line:
MemberInfo info = type.GetField(memberName) ?? type.GetProperty(memberName);
to this:
MemberInfo info = type.GetField(memberName) as MemberInfo ??
type.GetProperty(memberName) as MemberInfo;
because there's no implicit cast to the base class when using the ternary operator like that. The ternary requires that the types of all outputs be the same.
As of C# 9.0, this will finally be possible:
Target typed ?? and ?:
Sometimes conditional ?? and ?: expressions don’t have an obvious shared type between the branches. Such cases fail today, but C# 9.0 will allow them if there’s a target type that both branches convert to:
Person person = student ?? customer; // Shared base type
int? result = b ? 0 : null; // nullable value type
That means the code block in the question will also compile without errors; it will 'see' that FieldInfo and PropertyInfo have a shared base type, MemberInfo.
MemberInfo info = type.GetField(memberName) ?? type.GetProperty(memberName);

Dynamic select projection expression

I have the following class, for which usage is not important. What is important is method SetCacheItemSelector which takes one parameter, a select expression that projects Account entity to AccountCacheDTO:
public class AccountRepositoryCache : RepositoryCache<Account, AccountCacheDTO>
{
public AccountRepositoryCache()
{
SetCacheItemSelector(x => new AccountCacheDTO
{
Id = x.Id,
Login = x.Login
});
}
}
So signature for this method is:
public void SetCacheItemSelector(Expression<Func<TEntity, TCacheItem>> selector)
In this case, TEntity is Account class, and TCacheItem is AccountCacheDTO class.
Is there a way to use reflection to build select expression dynamically for all the properties that are matching for both Account class and AccountCacheDTO class?
Goal is to have method that would look like this:
public Expression<Func<TEntity, TCacheItem>> BuildSelector<TEntity, TCacheItem>()
{
... // implementation with reflection goes here
}
EDIT:
Here is final implementation (pretty much the same as the accepted answer):
public static Expression<Func<TSource, TTarget>> BuildSelector<TSource, TTarget>()
{
Type targetType = typeof(TTarget);
Type sourceType = typeof(TSource);
ParameterExpression parameterExpression = Expression.Parameter(sourceType, "source");
List<MemberBinding> bindings = new List<MemberBinding>();
foreach (PropertyInfo sourceProperty in sourceType.GetProperties().Where(x => x.CanRead))
{
PropertyInfo targetProperty = targetType.GetProperty(sourceProperty.Name);
if (targetProperty != null && targetProperty.CanWrite && targetProperty.PropertyType.IsAssignableFrom(sourceProperty.PropertyType))
{
MemberExpression propertyExpression = Expression.Property(parameterExpression, sourceProperty);
bindings.Add(Expression.Bind(targetProperty, propertyExpression));
}
}
NewExpression newExpression = Expression.New(targetType);
Expression initializer = Expression.MemberInit(newExpression, bindings);
return Expression.Lambda<Func<TSource, TTarget>>(initializer, parameterExpression);
}
I didn't test it, but you should be able to do something like: This is just to convey a general idea and you should be able to tweak it for your requirements.
public Expression<Func<TEntity, TCacheItem>> BuildSelector<TEntity, TCacheItem>(TEntity entity)
{
List<MemberBinding> memberBindings = new List<MemberBinding>();
MemberInitExpression body = null;
foreach (var entityPropertyInfo in typeof(TEntity).GetProperties())
{
foreach (var cachePropertyInfo in typeof(TCacheItem).GetProperties())
{
if (entityPropertyInfo.PropertyType == cachePropertyInfo.PropertyType && entityPropertyInfo.Name == cachePropertyInfo.Name)
{
var fieldExpressoin = Expression.Field(Expression.Constant(entity), entityPropertyInfo.Name);
memberBindings.Add(Expression.Bind(cachePropertyInfo, fieldExpressoin));
}
}
}
var parameterExpression = Expression.Parameter(typeof(TEntity), "x");
var newExpr = Expression.New(typeof(TCacheItem));
body = Expression.MemberInit(newExpr, memberBindings);
return Expression.Lambda<Func<TEntity, TCacheItem>>(body, parameterExpression);
}
Of course, the #Aravol's answer can make sense, but it is a little different which required in OP. Here is the solution which is more suitable to OP requirement.
public Expression<Func<TEntity, TCacheItem>> BuildSelector<TEntity, TCacheItem>()
{
Type type = typeof(TEntity);
Type typeDto = typeof(TCacheItem);
var ctor = Expression.New(typeDto);
ParameterExpression parameter = Expression.Parameter(type, "p");
var propertiesDto = typeDto.GetProperties(BindingFlags.Public | BindingFlags.Instance);
var memberAssignments = propertiesDto.Select(p =>
{
PropertyInfo propertyInfo = type.GetProperty(p.Name, BindingFlags.Public | BindingFlags.Instance);
MemberExpression memberExpression = Expression.Property(parameter, propertyInfo);
return Expression.Bind(p, memberExpression);
});
var memberInit = Expression.MemberInit(ctor, memberAssignments);
return Expression.Lambda<Func<TEntity, TCacheItem>>(memberInit, parameter);
}
Your best bet is to get very comfortable with the System.Linq.Expressions namespace, which contains all of the methods you'll need to dynamically metacode your method calls and compile them into delegates. See especially Expression.Call and Lambda.Compile methods. Note that using Lambda.Compile, you can also have a true, compiled Delegate, instead of an expression tree (Expression) wrapping the call to your desired method. (NOTE: You can also forgo the Compile step if you really want that expression tree for later)
As for building your set, that's Assembly scanning, and is going to be a matter of iterating over all classes in your Assembly. I highly recommend you utilize, at the very least, a custom Attribute on your assembly or future assemblies to mark them for this scan, lest this process end up much more costly. At the most, you should consider using a custom Attribute to mark which properties you want scanned for this expression build.
the actual code to this tends to start with
AppDomain.CurrentDomain // Necessary to get all available Assemblies
.GetAssemblies() // Gets all the assemblies currently loaded in memory that this code can work with
.AsParallel() // Highly recommended to make the attribute-checking steps run asynchronously
// Also gives you a handy .ForAll Method at the end
// TODO: .Where Assembly contains Attribute
.SelectMany(assembly => assembly.GetTypes())
// TODO: .Where Type contains Attribute
.SelectMany(type => type.GetProperties)
// TODO: Make sure Property has the right data...
.Select(CompileFromProperty)
Where CompileFromProperty is a method taking PropertyInfo and returning the desired Expression.
Look into ToList() and ToDictionary after that, as you may need to break out of the parallelization once you start pushing values to your cache
Addendum: you also have .MakeGenericType on the Type class, which will allow you to specify Generic parameters from other Type variables, which will prove invaluable when building the Expressions. Don't forget about Contravariance when you define the generic types!

Checking for Nulls on DB Record Mapping

How can I check for db null values in the attached code? Please understand I am a new C# convert...
What this code does is takes a IDataReader object and converts and maps it to a strongly-typed list of objects. But what I am finding is it completely errors out when there are null columns returned in the reader.
Converter
internal class Converter<T> where T : new()
{
// Declare our _converter delegate
readonly Func<IDataReader, T> _converter;
// Declare our internal dataReader
readonly IDataReader dataReader;
// Build our mapping based on the properties in the class/type we've passed in to the class
private Func<IDataReader, T> GetMapFunc()
{
// declare our field count
int _fc = dataReader.FieldCount;
// declare our expression list
List<Expression> exps = new List<Expression>();
// build our parameters for the expression tree
ParameterExpression paramExp = Expression.Parameter(typeof(IDataRecord), "o7thDR");
ParameterExpression targetExp = Expression.Variable(typeof(T));
// Add our expression tree assignment to the exp list
exps.Add(Expression.Assign(targetExp, Expression.New(targetExp.Type)));
//does int based lookup
PropertyInfo indexerInfo = typeof(IDataRecord).GetProperty("Item", new[] { typeof(int) });
// grab a collection of column names from our data reader
var columnNames = Enumerable.Range(0, _fc).Select(i => new { i, name = dataReader.GetName(i) }).AsParallel();
// loop through all our columns and map them properly
foreach (var column in columnNames)
{
// grab our column property
PropertyInfo property = targetExp.Type.GetProperty(column.name, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
// check if it's null or not
if (property != null)
{
// build our expression tree to map the column to the T
ConstantExpression columnNameExp = Expression.Constant(column.i);
IndexExpression propertyExp = Expression.MakeIndex(paramExp, indexerInfo, new[] { columnNameExp });
UnaryExpression convertExp = Expression.Convert(propertyExp, property.PropertyType);
BinaryExpression bindExp = Expression.Assign(Expression.Property(targetExp, property), convertExp);
// add it to our expression list
exps.Add(bindExp);
}
}
// add the originating map to our expression list
exps.Add(targetExp);
// return a compiled cached map
return Expression.Lambda<Func<IDataReader, T>>(Expression.Block(new[] { targetExp }, exps), paramExp).Compile();
}
// initialize
internal Converter(IDataReader dataReader)
{
// initialize the internal datareader
this.dataReader = dataReader;
// build our map
_converter = GetMapFunc();
}
// create and map each column to it's respective object
internal T CreateItemFromRow()
{
return _converter(dataReader);
}
}
Mapper
private static IList<T> Map<T>(DbDataReader dr) where T : new()
{
try
{
// initialize our returnable list
List<T> list = new List<T>();
// fire up the lamda mapping
var converter = new Converter<T>(dr);
while (dr.Read())
{
// read in each row, and properly map it to our T object
var obj = converter.CreateItemFromRow();
// add it to our list
list.Add(obj);
}
// reutrn it
return list;
}
catch (Exception ex)
{
// make sure this method returns a default List
return default(List<T>);
}
}
I just don't quite understand where the column to typed object happens in here, so I'd try to do it myself... but I just don;t know where it is.
I know this probably won't help much, but the error I am getting is:
Unable to cast object of type 'System.DBNull' to type 'System.String'.
and it happens on the
internal T CreateItemFromRow()
{
return _converter(dataReader); //<-- Here
}
Note
This does not happen if I wrap the columns in the query itself with an ISNULL(column, ''), but I am sure you can understand that this is surely not a solution
The problem lies in the line convertExp = Expression.Convert(propertyExp, property.PropertyType). You can't expect to convert DbNull value to its equivalent in framework type. This is especially nasty when your type is a value type. One option is to check if the read value from db is DbNull.Value and in case yes, you need to find a compatible value yourself. In some cases people are ok with default values of those types in C#. If you have to do this
property = value == DBNull.Value ? default(T): value;
a generic implementation would look like (as far as the foreach in your converter class goes):
foreach (var column in columns)
{
var property = targetExp.Type.GetProperty(
column.name,
BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
if (property == null)
continue;
var columnIndexExp = Expression.Constant(column.i);
var propertyExp = Expression.MakeIndex(
paramExp, indexerInfo, new[] { columnIndexExp });
var convertExp = Expression.Condition(
Expression.Equal(
propertyExp,
Expression.Constant(DBNull.Value)),
Expression.Default(property.PropertyType),
Expression.Convert(propertyExp, property.PropertyType));
var bindExp = Expression.Assign(
Expression.Property(targetExp, property), convertExp);
exps.Add(bindExp);
}
Now this does an equivalent of
property = reader[index] == DBNull.Value ? default(T): reader[index];
You could avoid the double lookup of the reader by assigning it to a variable and using its value in the conditional check. So this should be marginally better, but a lil' more complex:
foreach (var column in columns)
{
var property = targetExp.Type.GetProperty(
column.name,
BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
if (property == null)
continue;
var columnIndexExp = Expression.Constant(column.i);
var cellExp = Expression.MakeIndex(
paramExp, indexerInfo, new[] { columnIndexExp });
var cellValueExp = Expression.Variable(typeof(object), "o7thPropValue");
var convertExp = Expression.Condition(
Expression.Equal(
cellValueExp,
Expression.Constant(DBNull.Value)),
Expression.Default(property.PropertyType),
Expression.Convert(cellValueExp, property.PropertyType));
var cellValueReadExp = Expression.Block(new[] { cellValueExp },
Expression.Assign(cellValueExp, cellExp), convertExp);
var bindExp = Expression.Assign(
Expression.Property(targetExp, property), cellValueReadExp);
exps.Add(bindExp);
}
This does the conditional check this way:
value = reader[index];
property = value == DBNull.Value ? default(T): value;
This is one of the most annoying problems in dealing with datasets in general.
The way I normally get around it is to convert the DBNull value to something more useful, like an actual null or even a blank string in some cases. This can be done in a number of ways, but just recently I've taken to using extension methods.
public static T? GetValueOrNull<T>(this object value) where T : struct
{
return value == null || value == DBNull.Value ? (T?) null : (T) Convert.ChangeType(value, typeof (T));
}
A handy extension method for nullable types, so for example:
int? myInt = DataSet.Tables[0].Rows[0]["DBNullInt"].GetValueOrNull<int>();
Or a more generic one to just convert a DBNull in to a null:
public static object GetValueOrNull(this object value)
{
return value == DBNull.Value ? null : value;
}
string myString DataSet.Tables[0].Rows[0]["DBNullString"].GetValueOrNull();
You'll then get a null string, rather than trying to put a DBNull in to a string.
Hopefully that may help you a little.
As I come across this problem recently
both
Expression.TypeIs(propertyExp,typeof(DBNull));
and
Expression.Equal(propertyExp,Expression.Constant(DBNull.Value));
didn't work for me as they did increase memory allocation (which is my primary concern in this case)
here is the benchmark for both mapper approach compare to Dapper on 10K rows query.
TypeIs
and
Equal
so to fix this problem it came out that an IDataRecord is able to call "IsDBNull" to check whether the column in current reader is DBNull or not
and can be write as expression like
var isReaderDbNull = Expression.Call(paramExp, "IsDBNull", null, readerIndex);
finally, I end up with this solution
and now the performance is acceptable again.

C# Reflection get Field or Property by Name

Is there a way to supply a name to a function that then returns the value of either the field or property on a given object with that name? I tried to work around it with the null-coalesce operator, but apparently that doesn't like different types (which is also a bit weird to me because null is null). I could separate it it out into if nulls, but there has to be a better way to do this. Here is my function, and the two lines with Comparison objects don't compile, but I will leave them in there to show what I am trying to do.
private void SortByMemberName<T>(List<T> list, string memberName, bool ascending)
{
Type type = typeof (T);
MemberInfo info = type.GetField(memberName) ?? type.GetProperty(memberName);
if (info == null)
{
throw new Exception("Member name supplied is neither a field nor property of type " + type.FullName);
}
Comparison<T> asc = (t1, t2) => ((IComparable) info.GetValue(t1)).CompareTo(info.GetValue(t2));
Comparison<T> desc = (t1, t2) => ((IComparable) info.GetValue(t2)).CompareTo(info.GetValue(t1));
list.Sort(ascending ? asc : desc);
}
I have heard of something called dynamic LINQ that could be used, but for the sake of learning, I am doing it my way.
Change this line:
MemberInfo info = type.GetField(memberName) ?? type.GetProperty(memberName);
to this:
MemberInfo info = type.GetField(memberName) as MemberInfo ??
type.GetProperty(memberName) as MemberInfo;
because there's no implicit cast to the base class when using the ternary operator like that. The ternary requires that the types of all outputs be the same.
As of C# 9.0, this will finally be possible:
Target typed ?? and ?:
Sometimes conditional ?? and ?: expressions don’t have an obvious shared type between the branches. Such cases fail today, but C# 9.0 will allow them if there’s a target type that both branches convert to:
Person person = student ?? customer; // Shared base type
int? result = b ? 0 : null; // nullable value type
That means the code block in the question will also compile without errors; it will 'see' that FieldInfo and PropertyInfo have a shared base type, MemberInfo.
MemberInfo info = type.GetField(memberName) ?? type.GetProperty(memberName);

How to convert PropertyInfo to property expression and use it to invoke generic method?

How to convert PropertyInfo to property expression which can be used to invoke StructuralTypeConfiguration<TStructuralType>.Ignore<TProperty>(Expression<Func<TStructuralType, TProperty>> propertyExpression) method?
I tried to use Expression.Property() to construct expression but I am getting following error when I use this expression as propertyExpression parameter:
The type arguments for method cannot be inferred from the usage. Try specifying the type arguments explicitly.
This error probably refers to TProperty type parameter which I don't know how to specify having only PropertyInfo.
I am doing this in relation to: Use Entity Framework's StructuralTypeConfiguration.Ignore() to Ignore all properties but specified set.
UPDATE
Code which is not working:
var propertyInfo = typeof(Foo).GetProperties()[0];
var expression = Expression.Default(typeof(Foo));
var expressionProperty = Expression.Property(expression, propertyInfo);
Ignore(expressionProperty);
var entityType = propertyInfo.DeclaringType;
var parameter = Expression.Parameter(entityType, "entity");
var property = Expression.Property(parameter, propertyInfo);
var funcType = typeof(Func<,>).MakeGenericType(entityType, propertyInfo.PropertyType);
var lambda = Expression.Lambda(funcType, property, parameter);
structureConfiguration.GetType()
.GetMethod("Ignore")
.MakeGenericMethod(propertyInfo.PropertyType)
.Invoke(structureConfiguration, new[]{lambda});
Property expressions require the property access to be on a specific object. There's a few options you can take here. First, if this is being done within one of your entity objects, you can simple use a ConstantExpression to build the property expression:
// Already have PropertyInfo in propInfo
Expression.Property(Expression.Constant(this, this.GetType()), propInfo)
However, since you need a Expression<Func<TStructuralType, TProperty>>, then it seems like you're going to have to build it using a ParameterExpression:
ParameterExpression pe = Parameter.Expression(typeof(MyEntity), "eParam");
Expression propExp = Expression.Property(pe, propInfo);
HOWEVER, here's the kicker... This is just a MemberExpression. To convert to the expression you need, you need to use Expression.Lambda to get a Func<> expression of the type you need. The problem? You don't know the type of the property to define the generic parameters of the lambda expression!
Expression<Func<MyEntity, ????>> eFunc = Expression.Lambda<Func<MyEntity, ????>>(propExp, pe);
This is the crux of the problem of doing it this way. That's not to say it can't be done... It's just that using this method IN THIS WAY isn't going to work. You'll have to use a bit runtime and static typing trickery (as well as judicious use of Actions instead of Funcs) to get this to work correctly.
TProperty exists only in the c# source code text. The compiler always resolves it to a concrete type. If you have a method
void Test<T>(T arg)
{
}
and call it like this
Test("hello");
Test(3);
The compiler generates code for two methods!
void Test(string arg)
{
}
void Test(int arg)
{
}
This means that you have to supply concrete types for your generic parameters if you want to have an invokable method.
This code will get you an Expression<Func<>> of the desired type. Note that there is an Expression.Lambda(...) override that doesn't need you to specify the type of the Func returned.
var t = typeof(Foo);
var pi = t.GetProperty(...);
var prm = Expression.Parameter(t, t.Name);
var prx = Expression.Property(prm, pi);
var lambda = Expression.Lambda(prx, prm);
Note that in many cases you don't have to bother with creating the Expression<Func<>> this way, assuming structureConfiguration below is a StructureConfiguration<Foo>, type inference will allow you to write something like this:
structureConfiguration.Ignore(f => f.Bar);

Categories