Convert FuncT, T, bool> to an ExpressionFuncT, T, bool>> in LINQ-to-SQL.

c# expression-trees lambda linq-to-sql

Question

LINQ-to-SQL has been a PITA for me. We're using it to communicate to the database, and then send entities via WCF to a Silverlight app. Everything was working fine, until it came time to start editing (CUD) the entities, and their related data.

I was finally able to devise two for loops that allowed the CUD. I tried to refactor them, and I was so close, until I learned that I can't always do Lambda with L2S.

public static void CudOperation<T>(this DataContext ctx, IEnumerable<T> oldCollection, IEnumerable<T> newCollection, Func<T, T, bool> predicate)
    where T : class
{
    foreach (var old in oldCollection)
    {
        if (!newCollection.Any(o => predicate(old, o)))
        {
            ctx.GetTable<T>().DeleteAllOnSubmit(ctx.GetTable<T>().Where(o => predicate(old, o)));
        }
    }

    foreach (var newItem in newCollection)
    {
        var existingItem = oldCollection.SingleOrDefault(o => predicate(o, newItem));
        if (existingItem != null)
        {
            ctx.GetTable<T>().Attach(newItem, existingItem);
        }
        else
        {
            ctx.GetTable<T>().InsertOnSubmit(newItem);
        }
    }
}

Called by:

ctx.CudOperation<MyEntity>(myVar.MyEntities, newHeader.MyEntities,
    (x, y) => x.PkID == y.PkID && x.Fk1ID == y.Fk1ID && x.Fk2ID == y.FK2ID);

This almost worked. However, my Func needs to be an Expression>, and that's where I'm stuck.

Is there anyone who can tell me if this is possible? We have to be in .NET 3.5 due to SharePoint 2010.

1
6
2/17/2012 12:38:15 AM

Accepted Answer

Just change the parameter from:

 Func<T, T, bool> predicate

To:

 Expression<Func<T, T, bool>> predicate

The Expression is generated by the compiler.

Now, the issue is how to use this.

In your case, you need both a Func and an Expression, since you're using it in Enumerable LINQ queries (func based) as well as the SQL LINQ queries (expression based).

In:

.Where(o => predicate(old, o))

The old parameter is fixed. So we could change the parameter to:

Func<T, Expression<Func<T, bool>>> predicate

This means we can supply one argument (the 'fixed' one) and get back an expression.

foreach (var old in oldCollection)
{
    var condition = predicate(old);
    // ...
    {
        ctx.GetTable<T>().DeleteAllOnSubmit(ctx.GetTable<T>().Where(condition));
    }
}

We also need to use this in Any. To get a Func from an Expression we can call Compile():

foreach (var old in oldCollection)
{
    var condition = predicate(old);
    if (!newCollection.Any(condition.Compile()))
    {
        ctx.GetTable<T>().DeleteAllOnSubmit(ctx.GetTable<T>().Where(condition));
    }
}

You can do the same thing with the next part.

There are two issues:

  1. The performance might be impacted by using Compile() lots. I'm not sure how much effect it would actually have, but I'd profile it to check.
  2. The usage is now a little weird, since this is a curried lambda. Instead of passing (x,y) => ... you will be passing x => y => .... I'm not sure if this is a big deal for you.

There might be a better way to do this :)

Here's an alternate method, which should be a bit faster, since the Expression only has to be compiled once. Create a rewriter that will 'apply' one argument, like this:

class PartialApplier : ExpressionVisitor
{
    private readonly ConstantExpression value;
    private readonly ParameterExpression replace;

    private PartialApplier(ParameterExpression replace, object value)
    {
        this.replace = replace;
        this.value = Expression.Constant(value, value.GetType());
    }

    public override Expression Visit(Expression node)
    {
        var parameter = node as ParameterExpression;
        if (parameter != null && parameter.Equals(replace))
        {
            return value;
        }
        else return base.Visit(node);
    }

    public static Expression<Func<T2,TResult>> PartialApply<T,T2,TResult>(Expression<Func<T,T2,TResult>> expression, T value)
    {
        var result = new PartialApplier(expression.Parameters.First(), value).Visit(expression.Body);

        return (Expression<Func<T2,TResult>>)Expression.Lambda(result, expression.Parameters.Skip(1));
    }
}

Then use it like this:

public static void CudOperation<T>(this DataContext ctx,
    IEnumerable<T> oldCollection,
    IEnumerable<T> newCollection,
    Expression<Func<T, T, bool>> predicate)
    where T : class
{

    var compiled = predicate.Compile();

    foreach (var old in oldCollection)
    {
        if (!newCollection.Any(o => compiled(o, old)))
        {
            var applied = PartialApplier.PartialApply(predicate, old);
            ctx.GetTable<T>().DeleteAllOnSubmit(ctx.GetTable<T>().Where(applied));
        }
    }

    foreach (var newItem in newCollection)
    {
        var existingItem = oldCollection.SingleOrDefault(o => compiled(o, newItem));
        if (existingItem != null)
        {
            ctx.GetTable<T>().Attach(newItem, existingItem);
        }
        else
        {
            ctx.GetTable<T>().InsertOnSubmit(newItem);
        }
    }
}
11
2/17/2012 1:59:17 AM


Related Questions





Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow