Skip to content

Instantly share code, notes, and snippets.

@d1820
Last active March 19, 2024 13:02
Show Gist options
  • Save d1820/0b63f6b55d4fd4efaee14bf072a36dc3 to your computer and use it in GitHub Desktop.
Save d1820/0b63f6b55d4fd4efaee14bf072a36dc3 to your computer and use it in GitHub Desktop.
Parse object to create getter and setter expresssions
using System.Reflection;
namespace Entities
{
public class PropertyMap<T>
{
public Func<T, object> Getter { get; set; }
public PropertyInfo PropertyInfo { get; set; }
public Action<T, object> Setter { get; set; }
}
}
using System.Linq.Expressions;
using System.Reflection;
using Entities;
namespace Helpers
{
public class PropertyOptimizer<T>
{
public List<PropertyMap<T>> PropertyMaps = new List<PropertyMap<T>>();
private readonly Type _type = typeof(T);
private IEnumerable<PropertyInfo> PropertyInfos { get; set; }
public void Initialize()
{
PropertyInfos = _type.GetProperties();
foreach (PropertyInfo propertyInfo in PropertyInfos)
{
// Create a parameter expression for the instance of the Client class
ParameterExpression instance = Expression.Parameter(_type, "instance");
// Create a property access expression for the getter
MemberExpression propertyAccess = Expression.Property(instance, propertyInfo);
var castPropertyValue = Expression.Convert(propertyAccess, typeof(object));
var getterExpression = Expression.Lambda<Func<T, object>>(castPropertyValue, instance).Compile();
// Create a parameter expression for the value to set
ParameterExpression value = Expression.Parameter(typeof(object), "value");
// Create a property access expression for the setter
var valueCast = Expression.Convert(value, propertyInfo.PropertyType);
var setterExpression = Expression.Lambda<Action<T, object>>(
Expression.Assign(propertyAccess, valueCast), instance, value).Compile();
// Create a PropertyMap for this property
var propertyMap = new PropertyMap<T>
{
PropertyInfo = propertyInfo,
Getter = getterExpression,
Setter = setterExpression
};
PropertyMaps.Add(propertyMap);
}
}
}
}
public class Search
{
public string CreatedBy { get; set; }
public DateOnly? CreatedTimeUtc { get; set; }
public string Id { get; set; }
public DateOnly? LastUpdatedTimeUtc { get; set; }
public string ModifiedBy { get; set; }
}
using System.Linq.Expressions;
using Microsoft.Extensions.DependencyInjection;
//Allows creating a linq where clause from a class object dynamically based on the PropertyOptimizer
namespace Helpers
{
public class SearchHelper
{
private readonly IServiceProvider _serviceProvider;
public SearchHelper(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public IQueryable<T> ApplySearchFilters<T>(T searchObject, IQueryable<T> query) where T : class
{
//this is a required service get cause we want to handle the reflection at startup. If error register a PropertyOptimizer<T> in MappingProfile.
var optimizer = _serviceProvider.GetRequiredService<PropertyOptimizer<T>>();
foreach (var property in optimizer.PropertyMaps)
{
object value = property.Getter(searchObject);
if (value != null)
{
var parameter = Expression.Parameter(typeof(T), "x");
var propertyAccess = Expression.Property(parameter, property.PropertyInfo);
var equality = Expression.Equal(propertyAccess, Expression.Constant(value));
var lambda = Expression.Lambda<Func<T, bool>>(equality, parameter);
query = query.Where(lambda);
}
}
return query;
}
public Expression<Func<T, bool>> GetJoinedSearchFilters<T>(T searchObject) where T : class
{
//this is a required service get cause we want to handle the reflection at startup. If error register a PropertyOptimizer<T> in MappingProfile.
var optimizer = _serviceProvider.GetRequiredService<PropertyOptimizer<T>>();
var exps = new List<Expression<Func<T, bool>>>();
foreach (var property in optimizer.PropertyMaps)
{
object value = property.Getter(searchObject);
if (value != null)
{
var parameter = Expression.Parameter(typeof(T), "x");
var propertyAccess = Expression.Property(parameter, property.PropertyInfo);
var equality = Expression.Equal(propertyAccess, Expression.Constant(value));
exps.Add(Expression.Lambda<Func<T, bool>>(equality, parameter));
}
}
return CombineExpressionsWithAnd<T>(exps);
}
private static Expression<Func<T, bool>> CombineExpressionsWithAnd<T>(List<Expression<Func<T, bool>>> expressions)
{
if (expressions.Count == 0)
{
// Return a true expression if there are no expressions
return x => true;
}
Expression combinedExpression = expressions.First();
foreach (var expression in expressions.Skip(1))
{
combinedExpression = Expression.AndAlso(combinedExpression, expression.Body);
}
return Expression.Lambda<Func<T, bool>>(combinedExpression, expressions.First().Parameters);
}
}
}
services.AddSingleton<SearchHelper>();
services.AddSingleton(_ => new PropertyOptimizer<Search>());
using (var serviceScope = app.Services.CreateScope())
{
var serviceProvider = serviceScope.ServiceProvider;
//We bootstrap these here to get the property reflection out of the way on app startup.
serviceProvider.GetRequiredService<PropertyOptimizer<EEntities.Search>>().Initialize();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment