Home > OS >  Update Entity Property if Not Null Using Expressions
Update Entity Property if Not Null Using Expressions

Time:02-10

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));
  • Related