Files
netcore-winforms-demos/OutlookInspiredApp/DevExpress.OutlookInspiredApp/Common/Utils/ExpressionHelper.cs
2018-12-21 10:31:41 +03:00

146 lines
8.7 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
namespace DevExpress.DevAV.Common.Utils {
/// <summary>
/// Provides methods to perform operations with lambda expression trees.
/// </summary>
public class ExpressionHelper {
class ValueHolder<T> {
public readonly T value;
public ValueHolder(T value) {
this.value = value;
}
}
static readonly Dictionary<Type, object> TraitsCache = new Dictionary<Type, object>();
/// <summary>
/// Builds a lambda expression that compares an entity property value with a given constant value.
/// </summary>
/// <typeparam name="TPropertyOwner">An owner type of the property.</typeparam>
/// <typeparam name="TProperty">A primary key property type.</typeparam>
/// <param name="getPropertyExpression">A lambda expression that returns the property value for a given entity.</param>
/// <param name="constant">A constant value to compare with entity property value.</param>
public static Expression<Func<TPropertyOwner, bool>> GetValueEqualsExpression<TPropertyOwner, TProperty>(Expression<Func<TPropertyOwner, TProperty>> getPropertyExpression, TProperty constant) {
Expression equalExpression = Expression.Equal(getPropertyExpression.Body, Expression.Convert(Expression.Field(Expression.Constant(new ValueHolder<TProperty>(constant)), "value"), getPropertyExpression.Body.Type));
return Expression.Lambda<Func<TPropertyOwner, bool>>(equalExpression, getPropertyExpression.Parameters.Single());
}
/// <summary>
/// Returns an instance of the EntityTraits class that encapsulates operations to obtain and set the primary key value of a given entity.
/// </summary>
/// <typeparam name="TOwner">A type used as a key to cache compiled lambda expressions.</typeparam>
/// <typeparam name="TPropertyOwner">An owner type of the primary key property.</typeparam>
/// <typeparam name="TProperty">A primary key property type.</typeparam>
/// <param name="owner">An instance of the TOwner type which type is used as a key to cache compiled lambda expressions.</param>
/// <param name="getPropertyExpression">A lambda expression that returns the primary key value for a given entity.</param>
public static EntityTraits<TPropertyOwner, TProperty> GetEntityTraits<TOwner, TPropertyOwner, TProperty>(TOwner owner, Expression<Func<TPropertyOwner, TProperty>> getPropertyExpression) {
object traits = null;
if(!TraitsCache.TryGetValue(owner.GetType(), out traits)) {
traits = new EntityTraits<TPropertyOwner, TProperty>(getPropertyExpression.Compile(), GetSetValueActionExpression(getPropertyExpression).Compile(), GetHasValueFunctionExpression(getPropertyExpression).Compile());
TraitsCache[owner.GetType()] = traits;
}
return (EntityTraits<TPropertyOwner, TProperty>)traits;
}
/// <summary>
/// Determines whether the given entity satisfies the condition represented by a lambda expression.
/// </summary>
/// <typeparam name="TEntity">A type of the given object.</typeparam>
/// <param name="entity">An object to test.</param>
/// <param name="predicate">A function that determines whether the given object satisfies the condition.</param>
public static bool IsFitEntity<TEntity>(TEntity entity, Expression<Func<TEntity, bool>> predicate) where TEntity : class {
return predicate == null || (new TEntity[] { entity }.AsQueryable().Any(predicate));
}
/// <summary>
/// Converts a property reference represented as a lambda expression to a property name.
/// </summary>
/// <param name="expression">A lambda expression that returns the property value.</param>
public static string GetPropertyName(LambdaExpression expression) {
Expression body = expression.Body;
if(body is UnaryExpression) {
body = ((UnaryExpression)body).Operand;
}
var memberExpression = ValidateMemberExpression((MemberExpression)body);
return memberExpression.Member.Name;
}
static MemberExpression ValidateMemberExpression(MemberExpression memberExpression) {
if(IsNullableValueExpression(memberExpression))
memberExpression = (MemberExpression)memberExpression.Expression;
return memberExpression;
}
static bool IsNullableValueExpression(MemberExpression memberExpression) {
var propertyInfo = (PropertyInfo)memberExpression.Member;
return propertyInfo.PropertyType.IsValueType && propertyInfo.ReflectedType == typeof(Nullable<>).MakeGenericType(propertyInfo.PropertyType) && propertyInfo.Name == "Value";
}
static Expression<Action<TPropertyOwner, TProperty>> GetSetValueActionExpression<TPropertyOwner, TProperty>(Expression<Func<TPropertyOwner, TProperty>> getPropertyExpression) {
MemberExpression body = ValidateMemberExpression((MemberExpression)getPropertyExpression.Body);
ParameterExpression thisParameter = getPropertyExpression.Parameters.Single();
ParameterExpression propertyValueParameter = Expression.Parameter(typeof(TProperty), "propertyValue");
Expression keyValueExpression = propertyValueParameter;
if(IsNullableValueExpression((MemberExpression)getPropertyExpression.Body)) {
var constructor = typeof(Nullable<>).MakeGenericType(typeof(TProperty)).GetConstructor(new Type[] { typeof(TProperty) });
keyValueExpression = Expression.New(constructor, keyValueExpression);
}
BinaryExpression assignPropertyValueExpression = Expression.Assign(body, keyValueExpression);
return Expression.Lambda<Action<TPropertyOwner, TProperty>>(assignPropertyValueExpression, thisParameter, propertyValueParameter);
}
static Expression<Func<TPropertyOwner, bool>> GetHasValueFunctionExpression<TPropertyOwner, TProperty>(Expression<Func<TPropertyOwner, TProperty>> getPropertyExpression) {
MemberExpression memberExpression = (MemberExpression)getPropertyExpression.Body;
if(IsNullableValueExpression(memberExpression)) {
Expression equalExpression = Expression.NotEqual(memberExpression.Expression, Expression.Constant(null));
return Expression.Lambda<Func<TPropertyOwner, bool>>(equalExpression, getPropertyExpression.Parameters.Single());
}
return x => true;
}
}
/// <summary>
/// Incapsulates operations to obtain and set the primary key value of a given entity.
/// </summary>
/// <typeparam name="TEntity">An owner type of the primary key property.</typeparam>
/// <typeparam name="TPrimaryKey">A primary key property type.</typeparam>
public class EntityTraits<TEntity, TPrimaryKey> {
/// <summary>
/// Initializes a new instance of EntityTraits class.
/// </summary>
/// <param name="getPrimaryKeyFunction">A function that returns the primary key value of a given entity.</param>
/// <param name="setPrimaryKeyAction">An action that assigns the primary key value to a given entity.</param>
/// <param name="hasPrimaryKeyFunction">A function that determines whether given the entity has a primary key assigned.</param>
public EntityTraits(Func<TEntity, TPrimaryKey> getPrimaryKeyFunction, Action<TEntity, TPrimaryKey> setPrimaryKeyAction, Func<TEntity, bool> hasPrimaryKeyFunction) {
this.GetPrimaryKey = getPrimaryKeyFunction;
this.SetPrimaryKey = setPrimaryKeyAction;
this.HasPrimaryKey = hasPrimaryKeyFunction;
}
/// <summary>
/// The function that returns the primary key value of a given entity.
/// </summary>
public Func<TEntity, TPrimaryKey> GetPrimaryKey { get; private set; }
/// <summary>
/// The action that assigns the primary key value to a given entity.
/// </summary>
public Action<TEntity, TPrimaryKey> SetPrimaryKey { get; private set; }
/// <summary>
/// A function that determines whether the given entity has a primary key assigned (the primary key is not null). Always returns true if the primary key is a non-nullable value type.
/// </summary>
/// <returns></returns>
public Func<TEntity, bool> HasPrimaryKey { get; private set; }
}
}