Tobias J
Tobias J

Reputation: 22843

Allow caller to supply LINQ predicate without knowing value

We're using LINQ to EF and I'm trying to write a CachingRepository class that will simplify the pattern of caching retrieved entities in memory for the lifecycle of a single repository instance. The trick is that I want to be able to specify the property by which the entities are cached as a parameter to the constructor:

public class CachingRepository<TEntity, TKey> where TEntity : class
{
    private readonly Dictionary<TKey, TEntity> _cache
        = new Dictionary<TKey, TEntity>();

    private readonly IRepository<TEntity> _repository;

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

    public CachingRepository(IRepository<TEntity> repository, Expression<Func<TEntity, TKey>> entityKeySelector)
    {
        _repository = repository;
        _selector = entityKeySelector;
    }
}

(The existing IRepository implementation is pretty straightforward). So if someone wants to create a CachingRepository that caches per Employee.BadgeCode for example (where BadgeCode is a string), they could take an existing Employee repository and:

var cacheRepo = new CachingRepository(employeeRepo, (Employee) emp => emp.BadgeCode);

Here's the meat of the repository, where the cache has missed and I want to retrieve from the database:

    private TEntity GetFromStore(TKey key)
    {
        var entityType = typeof(TEntity);

        var paramName = _selector.Parameters[0].Name;

        var parameter = Expression.Parameter(entityType, paramName);
        var lambda = Expression.Lambda<Func<TEntity, bool>>(
            Expression.Equal(
                _selector.Body,
                Expression.Constant(key)
                )
            , parameter);

        return _repository.FirstOrDefault(lambda);
    }

However, in trying to run the above example I get an exception

The parameter 'emp' was not bound in the specified LINQ to Entities query expression.

I'm sure that I'm doing something wrong in simply passing _selector.Body to the Equal expression, but how should I do it? It seems like I need to have parameter "get into" my _selector expression but I'm not sure how to do that.

I realize that I could just pluck the property name from the selector in the constructor, and use something like Expression.Property(parameter, propertyName) instead, but it'd be nice to be able to pass more complex selector expressions besides simple property selectors. (Not to mention I'd like to understand why this isn't working).

Upvotes: 1

Views: 101

Answers (1)

Steve Wilkes
Steve Wilkes

Reputation: 7135

The errors occurs because _selector.Body is trying to access the 'emp' parameter, and that doesn't exist in the lambda you're creating. To fix it you should just be able to reuse _selector.Parameters[0] for the parameter you pass to Expression.Lambda instead of creating the new parameter ParameterExpression:

private TEntity GetFromStore(TKey key)
{
    var lambda = Expression.Lambda<Func<TEntity, bool>>(
        Expression.Equal(
            _selector.Body,
            Expression.Constant(key)
            )
        , _selector.Parameters[0]);

    return _repository.FirstOrDefault(lambda);
}

Upvotes: 1

Related Questions