LINQ-to-SQL: преобразовать Func <T, T, bool> в выражение <Func <T, T, bool >> - PullRequest
6 голосов
/ 17 февраля 2012

LINQ-to-SQL был для меня PITA. Мы используем его для связи с базой данных, а затем отправляем сущности через WCF в приложение Silverlight. Все работало нормально, пока не пришло время начать редактирование (CUD) объектов и связанных с ними данных.

Я наконец-то смог придумать две петли для CUD. Я пытался их реорганизовать, и я был так близок, пока не узнал, что не всегда могу сделать лямбду с 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);
        }
    }
}

Вызывается:

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

Это почти сработало. Однако мой Func должен быть Expression>, и вот где я застрял.

Есть кто-нибудь, кто может сказать мне, если это возможно? Мы должны быть в .NET 3.5 из-за SharePoint 2010.

1 Ответ

11 голосов
/ 17 февраля 2012

Просто измените параметр с:

 Func<T, T, bool> predicate

На:

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

Выражение генерируется компилятором.

Теперь вопрос заключается в том, какиспользуйте это.

В вашем случае вам понадобятся и Func , и , и Expression, поскольку вы также используете его в Enumerable LINQ-запросах (на основе func).как запросы SQL LINQ (на основе выражений).

In:

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

Параметр old является фиксированным.Таким образом, мы можем изменить параметр на:

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

Это означает, что мы можем предоставить один аргумент («фиксированный») и получить обратно выражение.

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

Нам также нужноиспользуйте это в Any.Чтобы получить Func из выражения, мы можем позвонить Compile():

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

Вы можете сделать то же самое со следующей частью.

Есть две проблемы:

  1. На производительность может повлиять использование Compile() лотов.Я не уверен, какой эффект это будет на самом деле, но я бы профилировал это, чтобы проверить.
  2. Использование теперь немного странно, так как это карри лямбда.Вместо прохождения (x,y) => ... вы будете пропускать x => y => ....Я не уверен, что для вас это важно.

Возможно, есть лучший способ сделать это:)

Вот альтернативный метод , что должно быть немного быстрее, поскольку выражение должно быть скомпилировано только один раз.Создайте переписчик, который будет «применять» один аргумент, например:

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));
    }
}

Затем используйте его так:

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);
        }
    }
}
...