Last active
March 19, 2024 13:02
-
-
Save d1820/0b63f6b55d4fd4efaee14bf072a36dc3 to your computer and use it in GitHub Desktop.
Parse object to create getter and setter expresssions
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; } | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; } | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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