I've been trying to make a generic function that will check if a value is null, otherwise it'll set the property on the entity to that value. Seems simple, thought I had it, but got a bit of a problem when the Type of the Property is not nullable (int) but the type of the value is obviously nullable (int?). Strings work fine, as they are nullable anyway.
Example:
UpdateEntityPropertyIfNotNull(entity, entity => entity.Id, request.Id);
So here, the entity Id is of type int but the request Id value is of type int? => generic function ends up just thinking they are both int?
This is my attempt at making the generic function:
private void UpdateEntityPropertyIfNotNull<T, TProperty>(T entity, Expression<Func<T, TProperty>> propertyFunc, TProperty valueToSet)
{
if (valueToSet == null)
{
return;
}
//if (propertyFunc.Body.NodeType != ExpressionType.MemberAccess)
//{
// throw new ArgumentException("This should be a member getter", nameof(propertyFunc));
//}
MemberExpression? member;
switch (propertyFunc.Body.NodeType)
{
case ExpressionType.Convert:
case ExpressionType.ConvertChecked:
var unary = propertyFunc.Body as UnaryExpression;
member = (unary?.Operand) as MemberExpression;
break;
default:
member = propertyFunc.Body as MemberExpression;
break;
}
var underlyingType = Nullable.GetUnderlyingType(typeof(TProperty)) ?? typeof(TProperty);
var model = propertyFunc.Parameters[0];
var value = Expression.Variable(underlyingType, nameof(model));
var assignment = Expression.Assign(member, value);
var lambdaExpression = Expression.Lambda<Action<T, TProperty>>(assignment, model, value).Compile();
lambdaExpression(entity, valueToSet);
}
Any advice would be great, I hate the idea of having to do the conditional checks manually...
CodePudding user response:
You need to group your constraints together, essentially creating a nullable value type constraint, and an other:
private void UpdateEntityPropertyIfNotNull<T, TProperty>(T entity, Expression<Func<T, TProperty>> propertyFunc, TProperty valueToSet)
{
if (!typeof(TProperty).IsValueType && valueToSet is null)
{
return;
}
UpdateEntityProperty(entity, propertyFunc, valueToSet);
}
private void UpdateEntityPropertyIfNotNull<T, TProperty>(T entity, Expression<Func<T, TProperty>> propertyFunc, TProperty? valueToSet)
where TProperty : struct
{
if (!valueToSet.HasValue)
{
return;
}
UpdateEntityProperty(entity, propertyFunc, valueToSet.Value);
}
private void UpdateEntityPropertyIfNotNull<T, TProperty>(T entity, Expression<Func<T, TProperty?>> propertyFunc, TProperty? valueToSet)
where TProperty : struct
{
if (!valueToSet.HasValue)
{
return;
}
UpdateEntityProperty(entity, propertyFunc, valueToSet.Value);
}
private void UpdateEntityProperty<T, TProperty>(T entity, Expression<Func<T, TProperty>> propertyFunc, TProperty valueToSet)
{
// snip
}
You get compile-time validity that your parameters are correctly handled, and it encompasses reference-types, value-types, and nullable value-types.
The last change is to update UpdateEntityProperty<T, TProperty>()
to either
var underlyingType = Nullable.GetUnderlyingType(typeof(TProperty)) ?? typeof(TProperty);
to
var underlyingType = typeof(TProperty);
or remove it and just use typeof()
directly:
var value = Expression.Variable(typeof(TProperty), nameof(model));