xvdiff
xvdiff

Reputation: 2229

Convert Func<T, TProperty> to Expression<Func<T, Property>>

I've got a generic repository implementation that allows to pass a selector in order to declare the entities primary key property:

public abstract class RepositoryBase<TEntity, TKey>
    where TEntity : class 
{

    private readonly Func<TEntity, TKey> _keySelector;

    protected Func<TEntity, TKey> KeySelector {
        get {
            return _keySelector;
        }
    }

    protected RepositoryBase(Func<TEntity, TKey> selector) {
        _keySelector = selector;
    }

}

... which can be used like this:

public class UserRepository : RepositoryBase<User, Guid>
{
    public UserRepository()
        : base((user) => user.Id)
    {

    }
}

I have now implemented a in memory repository to do some unit tests where I'd like to generate a new identity for each entity that is getting persisted. In case the entity does not have public accessor for the key, I've created a extension method to set properties using reflection.

public static void SetProperty<T, TProperty>(this T instance, Expression<Func<T, TProperty>> selector,
        TProperty newValue)
        where T : class
    {
        if (instance == null)
            throw new ArgumentNullException("instance");
        if (selector == null)
            throw new ArgumentNullException("selector");

        var propertyInfo = selector.GetMember() as PropertyInfo;
        if (propertyInfo == null)
            throw new InvalidOperationException();

        propertyInfo.SetValue(instance, newValue);
    }

My question is now: How can I use the KeySelector as an expression in order to set the primary key value? Is there a way to convert it? Or are there better ways to achieve what I'm trying?

Like so? Does that even make sense?:

protected override void AddItem(TEntity entity)
    {
        if (entity == null)
            throw new ArgumentNullException("entity");

        var id = default(TKey);
        if (GetPrimaryKey(entity).Equals(default(TKey)))
        {
            id = _identifierGenerator.Generate();
            entity.SetProperty(x => GetPrimaryKey(x), id); // <----
        }

        _items[id] = entity;
    }

Some methods used above:

Method 'GetPrimaryKey'

public TKey GetPrimaryKey(TEntity entity)
{
    if (entity == null)
        throw new ArgumentNullException("entity");

    return KeySelector(entity);
}

Method 'GetMember'

public static MemberInfo GetMember<T, TProperty>(this Expression<Func<T, TProperty>> expression)
    {
        var memberExp = RemoveUnary(expression.Body);

        return memberExp == null ? null : memberExp.Member;
    }

Method 'RemoveUnary'

    private static MemberExpression RemoveUnary(Expression toUnwrap)
    {
        var unwrap = toUnwrap as UnaryExpression;
        if (unwrap != null)
        {
            return unwrap.Operand as MemberExpression;
        }

        return toUnwrap as MemberExpression;
    }

Upvotes: 0

Views: 1083

Answers (1)

Jon Skeet
Jon Skeet

Reputation: 1503859

You can't, basically - not in any useful way. The solution seems simple though - change the type of your property instead:

private readonly Expression<Func<TEntity, TKey>> _keySelector;

protected Expression<Func<TEntity, TKey>> KeySelector {
    get {
        return _keySelector;
    }
}

protected RepositoryBase(Expression<Func<TEntity, TKey>> selector) {
    _keySelector = selector;
}

You can still use a lambda expression to initialize the property, and you can compile the expression tree to a delegate if you really need to.

Upvotes: 3

Related Questions