In order to use it as a selector in a Grouping Clause at runtime, I want basically retrieve a property from an object T by passing its name. Example with this class:
class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime Birthday { get; set; }
}
I want to use my method like this:
private void MyMethod(string propertyName)
{
var lambda = GetLambdaExpression(propertyName);
// The problem is to specify the Type at compile time...
var selector = GetLambdaSelector<DateTime>(lambda); // Seems mandatory to convert LambdaExpression to Expression<Func<Person, T>> to use in a grouping clause
var result = emps.GroupBy(
selector.Compile(),
p => p.Birthday,
(key, g) => new { PersonName = key, = g.ToList() }).AsQueryable();
}
Here are methods statements :
LambdaExpression GetLambdaExpression(string propertyName)
{
var type = typeof(Person);
var propertyInfo = type.GetProperty(propertyName);
var propertyType = propertyInfo.PropertyType;
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);
return lambda;
}
Expression<Func<Person, T>> GetLambdaSelector<T>(LambdaExpression expression)
{
return (Expression<Func<Person, T>>)expression;
}
I'm stuck because I want to resolve the type (DateTimein the example) at runtime and not at compile time.
So how to call this method with type resolve at runtime:
GetLambdaSelector<?>(lambda); ?
Or maybe can I change the signature of LambdaExpression GetLambdaExpression(string propertyName) to Expression<Func<Person, T>> with reflection but I did not manage it for the moment. ?
Related
If I have a dynamically created ParameterExpression:
class Product
{
public string Name { get; set; }
}
var propertyName = "Name";
var propertyType = typeof(Product).GetProperty(propertyName).PropertyType;
var parameterExpression = Expression.Parameter(propertyType , propertyName);
How can I covert it into a Func<Product, TPropertyType>?
I specifically want to pass this into the Where or OrderBy linq methods used by entity framework.
I'm also open to other suggestions not using Expressions, but I highly doubt it's possible.
Edit 1: Removed the where use case as Where and OrderBy will have different implementations Removed in an attempt to narrow the scope of the question.
Here is an example with generating expressions for OrderBy and Where. As Johnathon Sullinger said in comments, you must know type of property you ordering by at compile time, because it is mentioned in signature of OrderBY. However you don't have to know it for Where:
class Product
{
public string Name { get; set; }
}
static void Main(string[] args)
{
var products = new List<Product> {
new Product { Name = "ZZZ"},
new Product { Name = "AAA"}
};
var propertyName = "Name";
var ordered = products.AsQueryable().OrderBy(GetOrderExpression<string>(propertyName));
Console.WriteLine(ordered.ElementAt(0).Name);
Console.WriteLine(ordered.ElementAt(1).Name);
var filtered = products.AsQueryable().Where(GetWhereExpression(propertyName, "AAA"));
Console.WriteLine(filtered.Count());
Console.WriteLine(filtered.ElementAt(0).Name);
Console.ReadKey();
}
static Expression<Func<Product, TKey>> GetOrderExpression<TKey>(string propertyName)
{
var prm = Expression.Parameter(typeof(Product), "p");
var prop = Expression.Property(prm, typeof(Product), propertyName);
var lambda = Expression.Lambda<Func<Product, TKey>>(prop, "p", new[] { prm });
return lambda;
}
static Expression<Func<Product, bool>> GetWhereExpression(string propertyName, object value)
{
var prm = Expression.Parameter(typeof(Product), "p");
var prop = Expression.Property(prm, typeof(Product), propertyName);
var equal = Expression.Equal(prop, Expression.Constant(value));
var lambda = Expression.Lambda<Func<Product, bool>>(equal, "p", new[] { prm });
return lambda;
}
Hope it helps.
I looked at the other SO versions of this question but it seems the casting out of a method works for others. I am not sure what I am doing wrong here. I am new to the Expression Building part of Linq.
My extensions method is as follows:
void Main()
{
var people = LoadData().AsQueryable();
var expression = people.PropertySelector<Person>("LastName");
expression.Should().BeOfType(typeof(Expression<Func<Person, object>>));
var result = people.OrderBy(expression);
}
public static class Extensions
{
public static Expression<Func<T, object>> PropertySelector<T>(this IEnumerable<T> collection, string propertyName)
{
if (string.IsNullOrWhiteSpace(propertyName))
{
throw new ArgumentException(nameof(propertyName));
}
var properties = typeof(T).GetProperties();
if (!properties.Any(p => p.Name == propertyName))
{
throw new ObjectNotFoundException($"Property: {propertyName} not found for type [{typeof(T).Name}]");
}
var propertyInfo = properties.Single(p => p.Name == propertyName);
var alias = Expression.Parameter(typeof(T), "_");
var property = Expression.Property(alias, propertyInfo);
var funcType = typeof(Func<,>).MakeGenericType(typeof(T), propertyInfo.PropertyType);
var lambda = Expression.Lambda(funcType, property, alias);
return (Expression<Func<T, object>>)lambda;
}
}
#region
private Random rand = new Random();
// Define other methods and classes here
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
}
public IEnumerable<Person> LoadData()
{
IList<Person> people = new List<Person>();
for (var i = 0; i < 15; i++)
{
people.Add(new Person
{
FirstName = $"FirstName {i}",
LastName = $"LastName {i}",
Age = rand.Next(1, 100)
});
}
return people;
}
#endregion
I get an exception on the return during the cast. At this point T is type Person and object is a string. My lambda.GetType() is reporting that it's of type Expression<Func<Person, string>> The exception is:
Unable to cast object of type 'System.Linq.Expressions.Expression`1[System.Func`2[UserQuery+Person,System.String]]' to type 'System.Linq.Expressions.Expression`1[System.Func`2[UserQuery+Person,System.Object]]'.
What about my cast is incorrect? Thanks.
EDIT:
I updated with my full code that I am playing with in LinqPad. I am really just trying to figure out if there is an easy way to generate a lambda expression by passing in the property name. I was doing something like this before but I was just doing a switch on property name then using lambda syntax to create the OrderBy query dynamically.
This is strictly me trying to learn how Expression static methods can be used to achieve the same result as the example below. I am trying to mimic the below via extension method. But it doesn't have to be that way. It was just easiest to try while dinking around in LinqPad.
Expression<Func<Loan, object>> sortExpression;
switch (propertyFilter)
{
case "Age":
sortExpression = (l => l.Age);
break;
case "LastName":
sortExpression = (l => l.LastName);
break;
default:
sortExpression = (l => l.FirstName);
break;
}
var sortedLoans = loans.AsQueryable().OrderBy(sortExpression);
sortedLoans.Dump("Filtered Property Result");
Your code is creating a Func<UserQuery, String> because you're geting it's intrinsic type with
var propertyInfo = properties.Single(p => p.Name == propertyName);
var funcType = typeof(Func<,>).MakeGenericType(typeof(T), propertyInfo.PropertyType);
If you want to return a Func<T, object> then create a Func<T, object>, not a Func<T, (reflected property type)>, else the better solution is to use a Func<TOut, TIn> and create a totally generic function.
To keep current method signature you could do this:
var funcType = typeof(Func<,>).MakeGenericType(typeof(T), typeof(object));
var typeAs = Expression.TypeAs(property, typeof(object));
var lambda = Expression.Lambda(funcType, typeAs, alias);
and better way is to change your method to
public static Expression<Func<T, Tout>> PropertySelector<T, Tout>(this IEnumerable<T> collection, string propertyName)
{
if (string.IsNullOrWhiteSpace(propertyName))
{
throw new ArgumentException(nameof(propertyName));
}
var properties = typeof(T).GetProperties();
if (!properties.Any(p => p.Name == propertyName))
{
throw new ObjectNotFoundException($"Property: {propertyName} not found for type [{typeof(T).Name}]");
}
var propertyInfo = properties.Single(p => p.Name == propertyName);
var alias = Expression.Parameter(typeof(T), "_");
var property = Expression.Property(alias, propertyInfo);
var funcType = typeof(Func<,>).MakeGenericType(typeof(T), propertyInfo.PropertyType);
var lambda = Expression.Lambda(funcType, property, alias);
return (Expression<Func<T, Tout>>)lambda;
}
and call it with
var expression = people.PropertySelector<Person, string>("LastName");
I got the result I wanted. After comments from #Gusman, #IvanStoev and #PetSerAl I got it to function. I can move on with my exploring and learning again. Thank you very much. Final result was to template the Propertytype.
public static Expression<Func<T, TPropertyType>> PropertySelector<T, TPropertyType>(this IEnumerable<T> collection, string propertyName)
{
if (string.IsNullOrWhiteSpace(propertyName))
{
throw new ArgumentException(nameof(propertyName));
}
var properties = typeof(T).GetProperties();
if (!properties.Any(p => p.Name == propertyName))
{
throw new ObjectNotFoundException($"Property: {propertyName} not found for type [{typeof(T).Name}]");
}
var propertyInfo = properties.Single(p => p.Name == propertyName);
var alias = Expression.Parameter(typeof(T), "_");
var property = Expression.Property(alias, propertyInfo);
var funcType = typeof(Func<,>).MakeGenericType(typeof(T), typeof(TPropertyType));
var lambda = Expression.Lambda(funcType, property, alias);
return (Expression<Func<T, TPropertyType>>)lambda;
}
How can I convert a property name to Lambda expression in C#?
Like this: string prop = "Name"; to (p => p.Name)
public class Person{
public string Name{ get; set; }
}
Thanks!
Using expression trees you can generate the lambda expression.
using System.Linq.Expressions;
public static Expression<Func<T, object>> GetPropertySelector<T>(string propertyName)
{
var arg = Expression.Parameter(typeof(T), "x");
var property = Expression.Property(arg, propertyName);
//return the property as object
var conv = Expression.Convert(property, typeof(object));
var exp = Expression.Lambda<Func<T, object>>(conv, new ParameterExpression[] { arg });
return exp;
}
for Person you can call it like:
var exp = GetPropertySelector<Person>("Name");//exp: x=>x.Name
A lambda is just an anonymous function. You can store lambdas in delegates just like regular methods. I suggest you try making "Name" a property.
public string Name { get { return p.Name; } }
If you really want a lambda, use a delegate type such as Func.
public Func<string> Name = () => p.Name;
I want to write a method which creates mocks for any interface.
public T GetMock<T>(IDictionary<string, object> data) where T : class
I care only about property getters first. All getters should return values which are stored in the dictionary. Property name is a key in this dictionary. Following code illustrates intended usage:
public interface IFoo
{
string Property1 { get; }
int Property2 { get; }
DateTime Property3 { get; }
}
[Test]
public void TestY()
{
var data = new Dictionary<string, object>
{
{"Property1", "Hello"},
{"Property2", 5},
{"Property3", DateTime.Today}
};
var mock = GetMock<IFoo>(data);
Assert.AreEqual("Hello", mock.Property1);
Assert.AreEqual(5, mock.Property2);
Assert.AreEqual(DateTime.Today, mock.Property3);
}
The point is that I want to mock ANY interface. So my generic mock crreation looks like:
public T GetMock<T>(IDictionary<string, object> data) where T : class
{
var mock = new Mock<T>();
var type = typeof(T);
var properties = type.GetProperties();
foreach (var property in properties)
{
var attributeName = property.Name;
var parameter = Expression.Parameter(type);
var body = Expression.Property(parameter, attributeName);
var lambdaExpression = Expression.Lambda<Func<T, object>>(body, parameter);
Func<object> getter = () => data[attributeName];
mock.Setup(lambdaExpression).Returns(getter);
}
return mock.Object;
}
It should work but there is an issue with type conversion. The test fails with a message:
System.ArgumentException : Expression of type 'System.Int32' cannot be
used for return type 'System.Object'
I guess I am missing some conversion lambda. Any suggestions how to fix the problem?
Guess the only option is to use Reflection, because current version is 4.2, but still - there's no "Mock.Setup(Expression expr)" implementation, as stated Patrick.
So, here's my sample:
public static class ConfigFactory<T> where T : class {
static T cachedImplInstance;
public static T BuildConfigGroupWithReflection() {
if (cachedImplInstance == null) {
Type interfaceType = typeof(T);
MethodInfo setupGetMethodInfo = typeof(Mock<T>).GetMethod("SetupGet");
Mock<T> interfaceMock = new Mock<T>();
IDictionary<Type, MethodInfo> genericSetupGetMethodInfos = new Dictionary<Type, MethodInfo>();
IDictionary<Type, MethodInfo> specificReturnsMethodInfos = new Dictionary<Type, MethodInfo>();
if (setupGetMethodInfo != null)
foreach (PropertyInfo interfaceProperty in interfaceType.GetProperties()) {
string propertyName = interfaceProperty.Name;
Type propertyType = interfaceProperty.PropertyType;
ParameterExpression parameter = Expression.Parameter(interfaceType);
MemberExpression body = Expression.Property(parameter, propertyName);
var lambdaExpression = Expression.Lambda(body, parameter);
MethodInfo specificSetupGetMethodInfo =
genericSetupGetMethodInfos.ContainsKey(propertyType) ?
genericSetupGetMethodInfos[propertyType] :
genericSetupGetMethodInfos[propertyType] = setupGetMethodInfo.MakeGenericMethod(propertyType);
object setupResult = specificSetupGetMethodInfo.Invoke(interfaceMock, new[] { lambdaExpression });
MethodInfo returnsMethodInfo =
specificReturnsMethodInfos.ContainsKey(propertyType) ?
specificReturnsMethodInfos[propertyType] :
specificReturnsMethodInfos[propertyType] = setupResult.GetType().GetMethod("Returns", new[] { propertyType });
if (returnsMethodInfo != null)
returnsMethodInfo.Invoke(setupResult, new[] { Settings.Default[propertyName] });
}
cachedImplInstance = interfaceMock.Object;
}
return cachedImplInstance;
}
}
Notice line "returnsMethodInfo.Invoke(setupResult, new[] { Settings.Default[propertyName] });" - you may put your dictionnary here.
Say, we have interface:
public interface IConfig {
string StrVal { get; }
int IntVal { get; }
StringCollection StrsVal { get; }
string DbConnectionStr { get; }
string WebSvcUrl { get; }
}
Then, usage is as follows (assuming we have "Settings" of our project with corresponding Names/Types/Values):
IConfig cfg0 = ConfigFactory<IConfig>.BuildConfigGroupWithReflection();
This is a half answer, since I don't see any support in Moq for doing this. To get the correct Func, do the following:
// In your for loop from above...
var attributeName = property.Name;
var parameter = Expression.Parameter(type);
var body = Expression.Property(parameter, attributeName);
// Add this line to create the correct Func type
var func = typeof(Func<,>).MakeGenericType(typeof(T), property.PropertyType);
// Then use this Func to create the lambda
var lambdaExpression = Expression.Lambda(func, body, parameter);
The problem is that Setup doesn't have an overload that allows you to pass in a non-generic expression that represents a Func. In otherwords, this won't compile:
// Error: cannot convert from 'System.Linq.Expressions.LambdaExpression'
// to 'System.Linq.Expressions.Expression<System.Action<T>>'
mock.Setup(lambdaExpression);
So at this point you're stuck.
You could submit an issue (or pull request) to the Moq project, though I don't know if this application has a wide enough audience...
Code:
foreach (PropertyInfo prop in typeof(SomeObject).GetProperties())
{
if (Attribute.IsDefined(prop, typeof(SomeCustomAttribute)))
{
column.Expression(p => p.Name);
}
}
I want to add columns which have SomeCustomAttribute data-annotation assigned.
How to use prop.Name (name of the property) as an property instead of manual p.Name?
For example..
...
column.Expression(p => prop.Name);
...
column.Expression is
ITableColumn Expression<TProperty>(Expression<Func<TModel, TProperty>> expression)
Suppose you have this types defined:
interface ITableColumn { }
class SomeObject
{
public int MyProperty { get; set; }
}
class Column<TModel>
{
public ITableColumn Expression<TProperty>(Expression<Func<TModel, TProperty>> expression)
{
// just a stub
return null;
}
}
To invoke Column.Expression for single property, obtained via reflection, you have to build member expression first:
private static LambdaExpression MakeMemberExpression(PropertyInfo propertyInfo)
{
var instanceExpression = Expression.Parameter(propertyInfo.DeclaringType);
return Expression.Lambda(Expression.MakeMemberAccess(instanceExpression, propertyInfo), instanceExpression);
}
As long as you don't know TProperty statically, you don't need to build strongly typed lambda (Expression<Func<TModel, TProperty>>).
Now, let's invoke it:
var property = typeof(SomeObject)
.GetProperty("MyProperty");
var columnType = typeof(Column<>)
.MakeGenericType(typeof(SomeObject));
var expressionMethod = columnType
.GetMethod("Expression")
.MakeGenericMethod(property.PropertyType);
var expr = MakeMemberExpression(property);
expressionMethod.Invoke(new Column<SomeObject>(), new[] { expr });
Hope this helps.