Accessing indexer from expression tree - c#

I am working on a filtering function. The filter will be an expression tree build by an user. There will be about 30 fields the user can use for filtering. I think the best way is to create the object model with indexer and to access required values by index of enum type.
See this example:
enum Field
{
Name,
Date,
}
class ObjectModel
{
object this[Field Key]
{
get
{
//...
return xx;
}
}
}
I would like to ask how can I access an indexer from an expression tree.

I'll post a complete example on how to use an indexer:
ParameterExpression dictExpr = Expression.Parameter(typeof(Dictionary<string, int>));
ParameterExpression keyExpr = Expression.Parameter(typeof(string));
ParameterExpression valueExpr = Expression.Parameter(typeof(int));
// Simple and direct. Should normally be enough
// PropertyInfo indexer = dictExpr.Type.GetProperty("Item");
// Alternative, note that we could even look for the type of parameters, if there are indexer overloads.
PropertyInfo indexer = (from p in dictExpr.Type.GetDefaultMembers().OfType<PropertyInfo>()
// This check is probably useless. You can't overload on return value in C#.
where p.PropertyType == typeof(int)
let q = p.GetIndexParameters()
// Here we can search for the exact overload. Length is the number of "parameters" of the indexer, and then we can check for their type.
where q.Length == 1 && q[0].ParameterType == typeof(string)
select p).Single();
IndexExpression indexExpr = Expression.Property(dictExpr, indexer, keyExpr);
BinaryExpression assign = Expression.Assign(indexExpr, valueExpr);
var lambdaSetter = Expression.Lambda<Action<Dictionary<string, int>, string, int>>(assign, dictExpr, keyExpr, valueExpr);
var lambdaGetter = Expression.Lambda<Func<Dictionary<string, int>, string, int>>(indexExpr, dictExpr, keyExpr);
var setter = lambdaSetter.Compile();
var getter = lambdaGetter.Compile();
var dict = new Dictionary<string, int>();
setter(dict, "MyKey", 2);
var value = getter(dict, "MyKey");
To read from the indexer the IndexExpression contains directly the value of the indexed property. To write to it we must use Expression.Assign. Everything else is quite vanilla Expression. As written by Daniel the Indexer is normally called "Item". Note that Expression.Property has an overload that accepts directly the name of the indexer (so "Item"), but I chose to find it manually (so it can be reused). I have even put an example on how to use LINQ to find the exact overload of indexer you want.
Just as a curiosity, if you look on MSDN for example for Dictionary, under Properties you'll find Item

The indexer is a simple property, normally called Item. This means, you can access the indexer like any other property by using its name.
The name of the indexer property can be changed by the implementor of the class by means of the IndexerName attribute.
To reliably get the actual name of the indexer property, you have to reflect on the class and obtain the DefaultMember attribute.
More information can be found here.

Related

Accessing elements of types with indexers using expression trees

Suppose I have a type like this:
class Context
{
SomeType[] Items { get; set; }
}
I want to be able to access specific Items elements using expression trees. Suppose I need an element at index 0. I can do it like below, where everything works as expected:
var type = typeof (Context);
var param = Expression.Parameter(typeof (object));
var ctxExpr= Expression.Convert(param, context);
var proInfo = type.GetProperty("Items");
Expression.ArrayIndex(Expression.Property(ctxExpr, proInfo), Expression.Constant(0));
If I change the context type to contain .NET provided List<SomeType>, instead of array, i.e.
class Context
{
List<SomeType> Items { get; set; }
}
the same expression results in following error:
System.ArgumentException: Argument must be array at
System.Linq.Expressions.Expression.ArrayIndex(Expression array,
Expression index)
My question is, how to write respective expression which can access an item under appropriate index of List<>, or better - of any collection declaring indexer? E.g. is there is some way to detect, and convert such a collection to an array of appropriate types using expression trees?
An indexer is really just a property with an extra parameter, so you want:
var property = Expression.Property(ctxExpr, proInfo);
var indexed = Expression.Property(property, "Item", Expression.Constant(0));
where "Item" is the name of the indexed property here.
(If you don't know the name of the property beforehand, it's usually Item but you can always find it via reflection, by finding all properties with indexer parameters.)

Combine property selector expression tree and value to create a predicate for EF filtering - create filter from lambda selector and value

Given a simple class with arbitrary properties (for discussion lets say Id, Name, and Description)
and given an instance of that class, I want to find matching entries in the database by specifying the property to match
I'm trying to do something in this respect similar to the AddOrUpdate method of EF, but I need the entity returned to me for further processing.
var match = new SomeClass{Name="Whatever"};
var found = Context.SomeClass.Find(x=>x.Name, match);
public static T Find<T>(this DbSet<T> set, Expression<Func<T, object>> matchOn, T matchAgainst) where T : class {
var func = matchOn.Compile();
var valueToFind = func(matchAgainst);
var combinedExpression = //matchon + "=" + valueToFind;
var found = set.FirstOrDefault(combinedExpression);
return found;
}
That gives me the value of the property in the passed in object, but I need to now combine that value with the passed in expression and pass it to the db set.
IE, the code I'm effectively trying to run is set.FirstOrDefault(x=>x.Name==valueToFind) How do I take the matchon expression (which contains x=>x.Name) and combine that with the ==valueToFind to get the x=>x.Name==valueToFind from them?
How do I build the combined expression? (I realize the "string" code above is completely wrong, but I was trying to get across what I need the function to do, but I have no idea what that syntax would look like.)
For manually coded examples, it would be easy enough just to pass in a hardcoded lambda with the value set, but my use case involves running through a collection of objects and finding the match for each one, so the value will not be known until runtime, and the method must work against arbitrary types and various properties, so I can't hardcode the property name either.
If you have a property selector, and a value to compare to, you can get an expression tree like this:
public static Func<TEntity, bool> GetComparer<TEntity,TProperty>(
Expression<Func<TEntity,TProperty>> selector, TProperty value)
{
var propertyRef = selector.Body;
var parameter = selector.Parameters[0];
var constantRef = Expression.Constant(value);
var comparer
= Expression.Lambda<Func<TEntity, bool>>
(Expression.Equal(propertyRef, constantRef), parameter)
.Compile();
return comparer;
}
Sample usage:
var comparer = GetComparer<Person, string>(p => p.Name, "John");
var persons = Person.GetPersons();
var john = persons.FirstOrDefault(comparer);

Cannot implicitly convert type System.Collections.Generic.Dictionary<System.Tuple<int,int,string>, AnonymousType#1>

I have the following dictionary in a method:
var nmDict = xelem.Descendants(plantNS + "Month").ToDictionary(
k => new Tuple<int, int, string>(int.Parse(k.Ancestors(plantNS + "Year").First().Attribute("Year").Value), Int32.Parse(k.Attribute("Month1").Value), k.Ancestors(plantNS + "Report").First().Attribute("Location").Value.ToString()),
v => {
var detail = v.Descendants(plantNS + "Details").First();
return
new
{
BaseHours = detail.Attribute("BaseHours").Value,
OvertimeHours = detail.Attribute("OvertimeHours").Value
};
});
I need to return nmDict. The problem is that I cannot figure out how to label my method signature. I have tried the following:
protected IDictionary<XElement, XElement> OvertimereportData(HarvestTargetTimeRangeUTC ranges)
The above gives me this error:
Cannot implicitly convert type System.Collections.Generic.Dictionary<System.Tuple<int,int,string>,AnonymousType#1>' to 'System.Collections.Generic.IDictionary<System.Xml.Linq.XElement,System.Xml.Linq.XElement>'. An explicit conversion exists (are you missing a cast?)
protected IDictionary<Tuple, XElement> OvertimereportData(HarvestTargetTimeRangeUTC ranges)
gives me this error:
'System.Tuple': static types cannot be used as type arguments
I do not know what to do.
The short answer: You can't return anonymous types from a function.
The long answer: Your dictionary's value type is anonymous {BaseHours, OvertimeHours} which cannot be returned from a function or passed as an argument (except as an object, but that does nobody any good unless you go through the hassle of reflecting into it). Either define a class/struct with BaseHours and OvertimeHours in it, or use a tuple. The former is probably slightly better because you can keep the names BaseHours and OvertimeHours; with a tuple you just get Value1 and Value2.
If you are using C# 4.0 than you can return the anonymous via dynamic type. So your method signature would look like this
protected IDictionary<Tuple<int,int,string>, dynamic> OvertimereportData(HarvestTargetTimeRangeUTC ranges)
And through the dynamic object you can find the properties at run time.
Hope this will help you.
When you call the ToDictionary method, the resulting dictionary's type has little to do with the type of elements in your source sequence. It's defined entirely by the data types returned by the key and value expressions you supply to the call. For example, if you were to call:
xelem.Descendants(plantNS + "Month").ToDictionary(
k => int.Parse(k.Attribute("Year").Value),
v => k.Attribute("Year).Value
);
You would get an IDictionary<int, string> because that's what your two expressions returned. To return that from a method, you just need to construct the correct type, based on your expressions.
Your first one is easy:
k => new Tuple<int, int, string>(...)
The second one, though, is going to be a problem. The values in your dictionary are of an anonymous type: you return a new { } without specifying a concrete type name for that value. In general, that is going to make it impossible for you to use that dictionary as a return value or parameter. (It can be done, using some very strange-looking generic techniques, but I wouldn't recommend it.)
The first thing you'll need to do, then, is make a concrete type to hold your values, e.g.
public class HoursContainer
{
public string BaseHours { get; set; }
public string OvertimeHouse { get; set; }
}
and change your Linq query appropriately:
var detail = v.Descendants(plantNS + "Details").First();
return new HoursContainer
{
BaseHours = detail.Attribute("BaseHours").Value,
OvertimeHours = detail.Attribute("OvertimeHours").Value
};
Once you've done this, your dictionary will have a concrete type based on the types of things you specified when you created it:
IDictionary<Tuple<int, int, string>, HoursContainer>
(Note: You could also just use another Tuple<int, int> or whatever here, if you wanted, but the resulting generic type would get unwieldy very fast.)

Conversion problem with Expression Trees

I have an expression tree function from a previous SO question. It basically allows the conversion of a data row into a specific class.
This code works fine, unless you're dealing with data types that can be bigger or smaller (eg. Int32/Int64).
The code throws an invalid cast exception when going from an Int64 to an Int32 when the value would fit in an Int32 (eg. numbers in the 3000).
Should I?
Attempt to fix this in the code? (If so, any pointers?)
Leave the code as it is.
private Func<SqlDataReader, T> getExpressionDelegate<T>()
{
// hang on to row[string] property
var indexerProperty = typeof(SqlDataReader).GetProperty("Item", new[] { typeof(string) });
// list of statements in our dynamic method
var statements = new List<Expression>();
// store instance for setting of properties
ParameterExpression instanceParameter = Expression.Variable(typeof(T));
ParameterExpression sqlDataReaderParameter = Expression.Parameter(typeof(SqlDataReader));
// create and assign new T to variable: var instance = new T();
BinaryExpression createInstance = Expression.Assign(instanceParameter, Expression.New(typeof(T)));
statements.Add(createInstance);
foreach (var property in typeof(T).GetProperties())
{
// instance.MyProperty
MemberExpression getProperty = Expression.Property(instanceParameter, property);
// row[property] -- NOTE: this assumes column names are the same as PropertyInfo names on T
IndexExpression readValue = Expression.MakeIndex(sqlDataReaderParameter, indexerProperty, new[] { Expression.Constant(property.Name) });
// instance.MyProperty = row[property]
BinaryExpression assignProperty = Expression.Assign(getProperty, Expression.Convert(readValue, property.PropertyType));
statements.Add(assignProperty);
}
var returnStatement = instanceParameter;
statements.Add(returnStatement);
var body = Expression.Block(instanceParameter.Type, new[] { instanceParameter }, statements.ToArray());
var lambda = Expression.Lambda<Func<SqlDataReader, T>>(body, sqlDataReaderParameter);
// cache me!
return lambda.Compile();
}
Update:
I have now given up and decided it is not worth it. From the comments below, I got as far as:
if (readValue.Type != property.PropertyType)
{
BinaryExpression assignProperty = Expression.Assign(getProperty, Expression.Convert(Expression.Call(property.PropertyType, "Parse", null, new Expression[] { Expression.ConvertChecked(readValue, typeof(string)) }), property.PropertyType));
statements.Add(assignProperty);
}
else
{
// instance.MyProperty = row[property]
BinaryExpression assignProperty = Expression.Assign(getProperty, Expression.Convert(readValue, property.PropertyType));
statements.Add(assignProperty);
}
I don't think I was too far off, feel free to finish it and post the answer if you figure it out :)
You could try to fix it by "convert checked" before assigning i.e. using Expression.ConvertChecked on the value instead of Expression.Convert .
Couldn't try it right now but this should take care of the case you describe...
EDIT - as per comment this could be a boxing issue:
In this case you could try using Expression.TypeAs or Expression.Unbox for the conversion or use Expression.Call for calling a method to do the conversion... an example for using Call can be found at http://msdn.microsoft.com/en-us/library/bb349020.aspx
What you're trying to build is actually much more complicated if you want to support 100% of the primitives in .NET and SQL.
If you don't care about some of the edge cases (nullable types, enums, byte arrays, etc), two tips to get you 90% there:
Don't use the indexer on IDataRecord, it returns an object and the boxing/unboxing will kill performance. Instead, notice that IDataRecord has Get[typeName] methods on it. These exist for all .NET primitive types (note: it's GetFloat, not GetSingle, huge annoyance).
You can use IDataRecord.GetFieldType to figure out which Get method you need to call for a given column. Once you have that, you can use Expression.Convert to coerce the DB column type to the target property's type (if they're different). This will fail for some of the edge cases I listed above, for those you need custom logic.

How do I convert an Enum to an Int for use in an Expression.Equals operation?

I am trying to dynamically build an expression tree in C#, which is compiled and used as the predicate for LINQ-to-SQL Where() call. The problem is that I am trying to compare an Enum (with int as its underlying type) directly against an Int, but this is failing with the error "The member MyEnumType has no supported translation to SQL".
Code:
ParameterExpression param = Expression.Parameter(typeof(MyClass), "obj"); //input parameter - this exposes a property of the Enum type
MemberExpression enumMember = Expression.Property(param, "MyEnumProperty"); //used to get the Enum typed property from the parameter
//MISSING STEP TO CAST THE ENUM OF THE MEMBER EXPRESSION TO AN INT?
BinaryExpression binaryExpr = Expression.Equal(enumMember, Expression.Constant(1));
LambdaExpression<Func<MyClass, bool>> whereClause = Expression.Lambda(binaryExpr, param);
//when whereClause is used to filter LINQ-to-SQL results, the error is thrown
I'm fairly new to expression trees and I can't figure this out. I have tried using
Expression.Convert(enumMember, typeof(int))
as the first part of the BinaryExpression but this doesn't fix it.
Any help is much appreciated.
Simply, you shouldn't have to, as long as you've told LINQ-to-SQL about the enum (rather than mapping it as an int and having a separate property in C# that does the translation). For example, the following works fine:
var param = Expression.Parameter(typeof(DomainObject));
var body = Expression.Equal(Expression.Property(param, "SomeProperty"),
Expression.Constant(YourEnumType.SomeEnum));
var predicate = Expression.Lambda<Func<DomainObject, bool>>(body, param);
var count = db.Table.Where(predicate).Count();
The main point is that my SomeProperty property is mapped in the dbml to the enum. Simply overtype the type name with the enum type (including namespace).
Likewise, you shouldn't be giving it a 1, but rather the typed enum; for example:
Expression.Constant(Enum.ToObject(typeof(YourEnumType), 1))
(if all you know is 1)
Thanks To Marc Gravell. (Expression Guru !) See Correct answer.
I made a change to an Expression Routine to cater for this scenario.
Normal properties or Enums. Incase someone finds this helpful
public static Expression<Func<TPoco, bool>> GetEqualsPredicate<TPoco>(string propertyName,
object value)
Type fieldType )
{
var parameterExp = Expression.Parameter(typeof(TPoco), #"t"); //(tpoco t)
var propertyExp = Expression.Property(parameterExp, propertyName);// (tpoco t) => t.Propertyname
var someValue = fieldType.IsEnum // get and eXpressionConstant. Careful Enums must be reduced
? Expression.Constant(Enum.ToObject(fieldType, value)) // Marc Gravell fix
: Expression.Constant(value, fieldType);
var equalsExp = Expression.Equal(propertyExp, someValue); // yes this could 1 unreadble state if embedding someValue determination
return Expression.Lambda<Func<TPoco, bool>>(equalsExp, parameterExp);
}
look my friend first of all you have to modify your enum to be like that:
public enum myenum : int
{
item1 = 0,
item2 = 1
}
after that you can convert between int and that eunm by that way:
int x = (int) myenum.item1;
just you must convert enum to object
you can use this :
Expression.Constant(Enum.ToObject(enumMember.Type, enumMember .value))
Try
(int) enumMember

Categories