views:

138

answers:

3

In SubSonic 3.04's SimpleRepository, I cannot seem to perform a Contains operation within a lambda expression. Here's a trivial example:

SimpleRepository repo = new SimpleRepository("ConnectionString");

List<int> userIds = new List<int>();
userIds.Add(1);
userIds.Add(3);

List<User> users = repo.Find<User>(x => userIds.Contains(x.Id)).ToList();

I get the error message:

variable 'x' of type 'User' referenced from scope '', but it is not defined

Am I missing something here, or does SubSonic not support Contains in lambda expressions? If not, how would this be done?

A: 

Subsonic probably isn't able to convert the userIds.Contains because it is unable to translate that list into something it can execute on a SQL database. You'll probably have to resort into explictely defining an or condition:

repo.Find<User>(x => x.Id == 1 || x.Id == 3).ToList();
Thomas
+4  A: 

Since neither of these seem to work...

x => guids.Contains(x.Guid)
x => guids.Any(y => y == x.Guid)

... we write a custom lambda expression builder that generates:

x => x.Id == {id1} OR x.Id == {id2} OR x.Id == {id3}

This is a trivial scenario, but demonstrates how GetContainsId<User>(ids, repo) will find all Users with an Id that matches something in the supplied list.

public List<T> GetContainsId<T>(List<int> ids, SimpleRepository repo)
    where T : Record, new() // `Record` is a base class with property Id
{
    ParameterExpression x = Expression.Parameter(typeof(T), "x");
    LambdaExpression expr;
    if (ids.Count == 0)
    {
        expr = Expression.Lambda(LambdaExpression.Constant(false), x);
    }
    else
    {
        expr = Expression.Lambda(BuildEqual(x, ids.ToArray()), x);
    }

    return repo.Find<T>((Expression<Func<T,bool>>)expr).ToList();
}

private BinaryExpression BuildEqual(ParameterExpression x, int id)
{
    MemberExpression left = Expression.Property(x, "Id");
    ConstantExpression right = Expression.Constant(id);
    return Expression.Equal(left, right);
}

private BinaryExpression BuildEqual(ParameterExpression x, int[] ids, int pos = 0)
{
    int id = ids[pos];
    pos++;

    if (pos == ids.Length)
    {
        return BuildEqual(x, id);
    }

    return Expression.OrElse(BuildEqual(x, ids, pos), BuildEqual(x, id));
}
Anton
Just realized that there's a small bug in the order of operations for building the Or expression. Fixed in the example.
Anton
A: 

I'm pretty sure this will work if you use an IEnumerable instead of a List. So the following should work:

SimpleRepository repo = new SimpleRepository("ConnectionString");

IEnumerable<int> userIds = new List<int>();
userIds.Add(1);
userIds.Add(3);

List<User> users = repo.Find<User>(x => userIds.Contains(x.Id)).ToList();
Adam
Doesn't work for me, unless there's something else wrong with my code. Are you sure? (Also, how's Subsonic 3.1 coming along?)
Anton
hmmm, try doing userIds.Contains(x.Id.ToString())) - 3.1's on hold at the moment I'll be posting an update to the mailing list this weekend.
Adam