110 lines
5.2 KiB
C#
110 lines
5.2 KiB
C#
using System.Linq.Expressions;
|
|
using System.Reflection;
|
|
|
|
namespace Modules.Library.Domain.EntityBuilders;
|
|
|
|
public abstract class Builder<T>
|
|
{
|
|
public abstract T Build();
|
|
|
|
protected static Func<T> CreateInstanceFunction()
|
|
{
|
|
//var ex = Expression.MemberInit(Expression.New(typeof(T)));
|
|
var type = typeof(T);
|
|
var ctor = type.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, []) ??
|
|
throw new Exception($"Private parameterless constructor is not found for type [{type.Name}]");
|
|
var ex = Expression.New(ctor);
|
|
var lambda = Expression.Lambda<Func<T>>(ex);
|
|
return lambda.Compile();
|
|
}
|
|
|
|
protected static Action<TBuilder, T> SetInstanceFieldsAction<TBuilder>()
|
|
{
|
|
var entityType = typeof(T);
|
|
var builderType = typeof(TBuilder);
|
|
var builder = Expression.Parameter(builderType, "builder");
|
|
var entity = Expression.Parameter(entityType, "entity");
|
|
|
|
var expressions = new List<Expression>();
|
|
|
|
var fields = builderType.GetFields(BindingFlags.NonPublic | BindingFlags.Instance);
|
|
|
|
foreach (var prop in entityType.GetProperties())
|
|
{
|
|
var field = builderType.GetField(BuilderFieldConventionName(prop.Name), BindingFlags.NonPublic | BindingFlags.Instance);
|
|
if (field != null)
|
|
{
|
|
var builderField = Expression.Field(builder, field);
|
|
Expression<Func<object, FieldInfo, object?>> fieldGetValueLambda = (obj, fieldInfo) => fieldInfo.GetValue(obj);
|
|
var fieldGetValueExpr = Expression.Invoke(fieldGetValueLambda, [builder, Expression.Constant(field)]);
|
|
if (IsReadonlyCollection(prop.PropertyType))
|
|
{
|
|
var entityField = entityType.GetField(field.Name, BindingFlags.Instance | BindingFlags.NonPublic);
|
|
if (entityField != null) expressions.Add(Expression.Assign(Expression.Field(entity, entityField), builderField));
|
|
}
|
|
else
|
|
{
|
|
//var field = fields.FirstOrDefault(q => q.Name == BuilderFieldConventionName(prop.Name));
|
|
var entityProp = Expression.Property(entity, prop);
|
|
|
|
Expression<Action<object, PropertyInfo, object>> propertySetValueExpr = (dst, property, value) => property.SetValue(dst, value);
|
|
|
|
if (IsNullable(builderField) && !IsNullable(entityProp))
|
|
{
|
|
var valueProperty = typeof(Nullable<>).GetProperty("Value")!;
|
|
|
|
Expression<Func<object, FieldInfo, object>> fieldGetValueFromNullableLambda = (obj, fieldInfo) => valueProperty.GetValue(fieldInfo.GetValue(obj))!;
|
|
var fieldGetValueFromNullableExpr = Expression.Invoke(fieldGetValueFromNullableLambda, [builder, Expression.Constant(field)]);
|
|
|
|
var setFromNullableExpr = Expression.Invoke(propertySetValueExpr, [entity, Expression.Constant(prop), fieldGetValueFromNullableExpr]);
|
|
expressions.Add(Expression.IfThen(Expression.IsTrue(IsNullExpression(builderField)), setFromNullableExpr));
|
|
}
|
|
else
|
|
{
|
|
expressions.Add(Expression.Invoke(propertySetValueExpr, [entity, Expression.Constant(prop), fieldGetValueExpr]));
|
|
}
|
|
|
|
//expressions.Add(cond);
|
|
}
|
|
}
|
|
}
|
|
|
|
expressions.Add(entity);
|
|
|
|
var lambda = Expression.Lambda<Action<TBuilder, T>>(Expression.Block(expressions), builder, entity);
|
|
return lambda.Compile();
|
|
}
|
|
|
|
private static Expression GetValue(MemberExpression member) => Expression.Convert(member, typeof(object));
|
|
|
|
private static Expression GetValueFromNullable(Expression objExpression, MemberExpression member)
|
|
{
|
|
var valueProperty = typeof(Nullable<>).GetProperty("Value")!;
|
|
var methodInfo = valueProperty.GetType().GetMethod("GetValue") ?? throw new Exception("Method \"GetValue\" is not found for nullable property");
|
|
return Expression.Call(objExpression, methodInfo, GetValue(member));
|
|
}
|
|
|
|
private static Expression IsNullExpression(MemberExpression member)
|
|
{
|
|
var info = member.Member;
|
|
return Expression.Equal(Expression.Convert(member, typeof(Nullable<>).MakeGenericType(MemberType(info))), Expression.Constant(null));
|
|
}
|
|
|
|
private static bool IsNullable(MemberExpression member) => MemberType(member.Member) == typeof(Nullable<>);
|
|
|
|
private static Type MemberType(MemberInfo info) => info.MemberType switch
|
|
{
|
|
MemberTypes.Event => ((EventInfo)info).EventHandlerType!,
|
|
MemberTypes.Field => ((FieldInfo)info).FieldType,
|
|
MemberTypes.Method => ((MethodInfo)info).ReturnType,
|
|
MemberTypes.Property => ((PropertyInfo)info).PropertyType,
|
|
_ => throw new NotImplementedException()
|
|
};
|
|
|
|
private static bool IsReadonlyCollection(Type collectionType) => collectionType.IsGenericType &&
|
|
collectionType.GetGenericTypeDefinition() == typeof(IReadOnlyCollection<>);
|
|
|
|
private static string BuilderFieldConventionName(string propertyName) =>
|
|
string.Concat('_', char.ToLower(propertyName[0]), propertyName[1..]);
|
|
|
|
} |