How to get type property names using lambda expression and anonymous type?

I am trying to use expression trees and anonymous types to achieve the following results.

Say I have this class:

class Person { public string FirstName {get;set;} public string MiddleName {get;set;} public string LastName {get;set;} public DateTime DateOfBirth {get;set;} } 

Now I want to be able to call the following:

 string[] names = Foo<Person>(x=> new { x.LastName, x.DateOfBirth }); 

I want the names to contain 2 elements: "LastName" and "DateOfBirth".

I am trying to extend PetaPoco using a safe compilation method instead of writing a sql string so that I can specify a list of properties / columns that I want to include in SQL, and not to select everything. I have quite large objects, and there are cases when I do not want to select all columns for performance reasons.

+4
source share
5 answers

I am lazy, so this code only processes public properties. But this should be a good base for you.

 public static string[] Foo<T>(Expression<Func<T, object>> func) { var properties = func.Body.Type.GetProperties(); return typeof(T).GetProperties() .Where(p => properties.Any(x => p.Name == x.Name)) .Select(p => { var attr = (ColumnAttribute) p.GetCustomAttributes(typeof(ColumnAttribute), true).FirstOrDefault(); return (attr != null ? attr.Name : p.Name); }).ToArray(); } 
+1
source

Try this for size:

 public static string[] Foo<T, TResult>(Expression<Func<T, TResult>> func) { return typeof(TResult).GetProperties().Select(pi => pi.Name).ToArray(); } 

When you return an anonymous type from your lamda, you can iterate over all the properties of that anonymous type and use the assumed property names. However, when using this, the syntax will be more similar:

 Foo((Person x) => new { x.LastName, x.DateOfBirth }); 

This is because the second general argument is anonymous.

+3
source

The answers given here work when either a single property is selected, or when several properties are selected. None of them work for both. Lukazoid's answer only works for a few properties, the rest is for a single property, starting with writing my answer.

The code below is considered as a case, i.e. You can use it to select one property and several properties. Please note that I have not added any checks, so feel free to add your own.

 string[] Foo<T>(Expression<Func<Person, T>> func) { if (func.Body is NewExpression) { // expression selects multiple properties, // OR, single property but as an anonymous object // extract property names right from the expression itself return (func.Body as NewExpression).Members.Select(m => m.Name).ToArray(); // Or, simply using reflection, as shown by Lukazoid // return typeof(T).GetProperties().Select(p => p.Name).ToArray(); } else { // expression selects only a single property of Person, // and not as an anonymous object. return new string[] { (func.Body as MemberExpression).Member.Name }; } } 

Or more concisely, using the ternary operator, all this becomes the following:

 string[] Foo<T>(Expression<Func<Person, T>> func) { return (func.Body as NewExpression) != null ? typeof(T).GetProperties().Select(p => p.Name).ToArray() : new string[] { (func.Body as MemberExpression).Member.Name }; } 

Download LinkPad File: LinkPad
Watch it online: Repl.it

Please feel free to point out what I may have missed.

+2
source

The code page is a thousand words, so here's how Microsoft does it in Prism :

 ///<summary> /// Provides support for extracting property information based on a property expression. ///</summary> public static class PropertySupport { /// <summary> /// Extracts the property name from a property expression. /// </summary> /// <typeparam name="T">The object type containing the property specified in the expression.</typeparam> /// <param name="propertyExpression">The property expression (eg p => p.PropertyName)</param> /// <returns>The name of the property.</returns> /// <exception cref="ArgumentNullException">Thrown if the <paramref name="propertyExpression"/> is null.</exception> /// <exception cref="ArgumentException">Thrown when the expression is:<br/> /// Not a <see cref="MemberExpression"/><br/> /// The <see cref="MemberExpression"/> does not represent a property.<br/> /// Or, the property is static. /// </exception> public static string ExtractPropertyName<T>(Expression<Func<T>> propertyExpression) { if (propertyExpression == null) { throw new ArgumentNullException("propertyExpression"); } var memberExpression = propertyExpression.Body as MemberExpression; if (memberExpression == null) { throw new ArgumentException(Resources.PropertySupport_NotMemberAccessExpression_Exception, "propertyExpression"); } var property = memberExpression.Member as PropertyInfo; if (property == null) { throw new ArgumentException(Resources.PropertySupport_ExpressionNotProperty_Exception, "propertyExpression"); } var getMethod = property.GetGetMethod(true); if (getMethod.IsStatic) { throw new ArgumentException(Resources.PropertySupport_StaticExpression_Exception, "propertyExpression"); } return memberExpression.Member.Name; } } 

If you want to account for attributes, it will be a little more complicated, but the general idea of ​​accepting Expression<Func<T>> and inferring the name of the target will be the same.

Update: As is, the method will accept only one parameter; I just presented it as a guide. Of course, the idea can be generalized:

 public static string[] ExtractPropertyNames<T>( Expression<Func<T, object>> propertyExpression) 

This method will take an expression that takes T and returns an anonymous type, which you can then flip. You can replace the second parameter of type object , but in fact it does not mean anything, because the only thing you want to do is reflect the type.

0
source

I think you need to parse the code to build Html.LabelFor(LabelExtensions.LabelFor<TModel,TValue> from System.Web.Mvc ).

For example, see ExpressionHelper.GetExpressionText

As for replacing the member name with the attribute value, you will have to use old-fashioned reflection.

-1
source

Source: https://habr.com/ru/post/1404011/


All Articles