MyBookmark/Modules.Library.Domain/EntityBuilders/Builder.cs
2024-09-23 03:00:50 +03:00

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..]);
}