Отключение дерева выражений предиката для нацеливания на другой тип

вступление

В приложении, над которым я сейчас работаю, есть два вида каждого бизнес-объекта: вид "ActiveRecord" и вид "DataContract". Так, например, будет:

namespace ActiveRecord {
    class Widget {
        public int Id { get; set; }
    }
}

namespace DataContract {
    class Widget {
        public int Id { get; set; }
    }
}

Слой доступа к базе данных заботится о переводе между семействами: вы можете сказать ему обновить DataContract.Widget и это волшебным образом создаст ActiveRecord.Widget с теми же значениями свойств и сохраните их.

Проблема возникла при попытке провести рефакторинг этого уровня доступа к базе данных.

Эта проблема

Я хочу добавить методы, подобные следующим, к слою доступа к базе данных:

// Widget is DataContract.Widget

interface IDbAccessLayer {
    IEnumerable<Widget> GetMany(Expression<Func<Widget, bool>> predicate);
}

Выше приведен простой метод get общего назначения с пользовательским предикатом. Единственный интересный момент заключается в том, что я передаю дерево выражений вместо лямбды, потому что внутри IDbAccessLayer Я запрашиваю IQueryable<ActiveRecord.Widget>; чтобы сделать это эффективно (подумайте LINQ to SQL) мне нужно передать дерево выражений, поэтому этот метод запрашивает именно это.

Загвоздка: параметр должен быть магическим образом преобразован из Expression<Func<DataContract.Widget, bool>> для Expression<Func<ActiveRecord.Widget, bool>>,

Попытка решения

Что я хотел бы сделать внутри GetMany является:

IEnumerable<DataContract.Widget> GetMany(
    Expression<Func<DataContract.Widget, bool>> predicate)
{
    var lambda = Expression.Lambda<Func<ActiveRecord.Widget, bool>>(
        predicate.Body,
        predicate.Parameters);

    // use lambda to query ActiveRecord.Widget and return some value
}

Это не будет работать, потому что в типичном сценарии, например, если:

predicate == w => w.Id == 0;

... дерево выражений содержит MemberAccessExpression экземпляр, который имеет свойство типа MemberInfo это описывает DataContract.Widget.Id, Это также ParameterExpression экземпляры как в дереве выражений, так и в его коллекции параметров (predicate.Parameters) которые описывают DataContract.Widget; все это приведет к ошибкам, так как запрашиваемое тело не содержит виджет такого типа, а скорее ActiveRecord.Widget,

Немного поиска, я нашел System.Linq.Expressions.ExpressionVisitor (его источник может быть найден здесь в контексте с практическими рекомендациями), который предлагает удобный способ изменить дерево выражений. В.NET 4 этот класс включен из коробки.

Вооружившись этим, я реализовал посетителя. Этот простой посетитель заботится только об изменении типов доступа к элементам и выражений параметров, но этого достаточно для работы с предикатом. w => w.Id == 0,

internal class Visitor : ExpressionVisitor
{
    private readonly Func<Type, Type> typeConverter;

    public Visitor(Func<Type, Type> typeConverter)
    {
        this.typeConverter = typeConverter;
    }

    protected override Expression VisitMember(MemberExpression node)
    {
        var dataContractType = node.Member.ReflectedType;
        var activeRecordType = this.typeConverter(dataContractType);

        var converted = Expression.MakeMemberAccess(
            base.Visit(node.Expression),
            activeRecordType.GetProperty(node.Member.Name));

        return converted;
    }

    protected override Expression VisitParameter(ParameterExpression node)
    {
        var dataContractType = node.Type;
        var activeRecordType = this.typeConverter(dataContractType);

        return Expression.Parameter(activeRecordType, node.Name);
    }
}

С этим посетителем GetMany будет выглядеть так:

IEnumerable<DataContract.Widget> GetMany(
    Expression<Func<DataContract.Widget, bool>> predicate)
{
    var visitor = new Visitor(...);
    var lambda = Expression.Lambda<Func<ActiveRecord.Widget, bool>>(
        visitor.Visit(predicate.Body),
        predicate.Parameters.Select(p => visitor.Visit(p));

    var widgets = ActiveRecord.Widget.Repository().Where(lambda);

    // This is just for reference, see below
    Expression<Func<ActiveRecord.Widget, bool>> referenceLambda = 
        w => w.Id == 0;

    // Here we 'd convert the widgets to instances of DataContract.Widget and
    // return them -- this has nothing to do with the question though.
}

Результаты

Хорошая новость в том, что lambda построен просто отлично. Плохая новость в том, что это не работает; это взрывает меня, когда я пытаюсь использовать это, и сообщения об исключениях действительно бесполезны вообще.

Я исследовал лямбду, которую производит мой код, и лямбда с жестким кодом с тем же выражением; они выглядят точно так же. Я провел часы в отладчике, пытаясь найти какую-то разницу, но я не могу.

Когда предикат w => w.Id == 0, lambda выглядит так же, как referenceLambda, Но последний работает, например, с IQueryable<T>.Where в то время как первый не делает; Я попробовал это в ближайшем окне отладчика.

Я должен также упомянуть, что когда предикат w => true все работает нормально. Поэтому я предполагаю, что я не выполняю достаточную работу для посетителя, но я не могу найти больше подсказок, которым нужно следовать.

Окончательное решение

После учета правильных ответов на проблему (два из них ниже; один короткий, другой с кодом) проблема была решена; Я поместил код вместе с несколькими важными примечаниями в отдельный ответ, чтобы этот длинный вопрос не стал еще длиннее.

Спасибо всем за ваши ответы и комментарии!

6 ответов

Решение

Кажется, вы генерируете выражение параметра дважды, в VisitMember() здесь:

var converted = Expression.MakeMemberAccess(
    base.Visit(node.Expression),
    activeRecordType.GetProperty(node.Member.Name));

... так как base.Visit() будет в конечном итоге в VisitParameter, я думаю, и в самом GetMany():

var lambda = Expression.Lambda<Func<ActiveRecord.Widget, bool>>(
    visitor.Visit(predicate.Body),
    predicate.Parameters.Select(p => visitor.Visit(p));

Если вы используете ParameterExpression в теле, это должен быть тот же экземпляр (а не только тот же тип и имя), что и для Lambda. Раньше у меня были проблемы с такого рода сценарием, хотя я думаю, что в результате я просто не смог создать выражение, он просто выбросил бы исключение. В любом случае вы можете попробовать повторно использовать экземпляр параметра, посмотрите, поможет ли он.

Оказалось, что сложная часть просто ParameterExpression экземпляры, которые существуют в дереве выражений новой лямбды, должны быть теми же экземплярами, которые передаются в IEnumerable<ParameterExpression> параметр Expression.Lambda,

Обратите внимание, что внутри TransformPredicateLambda я даю t => typeof(TNewTarget) в качестве функции "преобразователь типа"; Это связано с тем, что в данном конкретном случае мы можем предположить, что все параметры и доступы к членам будут относиться к этому конкретному типу. Для более сложных сценариев может потребоваться дополнительная логика.

Код:

internal class DbAccessLayer {
    private static Expression<Func<TNewTarget, bool>> 
    TransformPredicateLambda<TOldTarget, TNewTarget>(
    Expression<Func<TOldTarget, bool>> predicate)
    {
        var lambda = (LambdaExpression) predicate;
        if (lambda == null) {
            throw new NotSupportedException();
        }

        var mutator = new ExpressionTargetTypeMutator(t => typeof(TNewTarget));
        var explorer = new ExpressionTreeExplorer();
        var converted = mutator.Visit(predicate.Body);

        return Expression.Lambda<Func<TNewTarget, bool>>(
            converted,
            lambda.Name,
            lambda.TailCall,
            explorer.Explore(converted).OfType<ParameterExpression>());
    }


    private class ExpressionTargetTypeMutator : ExpressionVisitor
    {
        private readonly Func<Type, Type> typeConverter;

        public ExpressionTargetTypeMutator(Func<Type, Type> typeConverter)
        {
            this.typeConverter = typeConverter;
        }

        protected override Expression VisitMember(MemberExpression node)
        {
            var dataContractType = node.Member.ReflectedType;
            var activeRecordType = this.typeConverter(dataContractType);

            var converted = Expression.MakeMemberAccess(
                base.Visit(node.Expression), 
                activeRecordType.GetProperty(node.Member.Name));

            return converted;
        }

        protected override Expression VisitParameter(ParameterExpression node)
        {
            var dataContractType = node.Type;
            var activeRecordType = this.typeConverter(dataContractType);

            return Expression.Parameter(activeRecordType, node.Name);
        }
    }
}

/// <summary>
/// Utility class for the traversal of expression trees.
/// </summary>
public class ExpressionTreeExplorer
{
    private readonly Visitor visitor = new Visitor();

    /// <summary>
    /// Returns the enumerable collection of expressions that comprise
    /// the expression tree rooted at the specified node.
    /// </summary>
    /// <param name="node">The node.</param>
    /// <returns>
    /// The enumerable collection of expressions that comprise the expression tree.
    /// </returns>
    public IEnumerable<Expression> Explore(Expression node)
    {
        return this.visitor.Explore(node);
    }

    private class Visitor : ExpressionVisitor
    {
        private readonly List<Expression> expressions = new List<Expression>();

        protected override Expression VisitBinary(BinaryExpression node)
        {
            this.expressions.Add(node);
            return base.VisitBinary(node);
        }

        protected override Expression VisitBlock(BlockExpression node)
        {
            this.expressions.Add(node);
            return base.VisitBlock(node);
        }

        protected override Expression VisitConditional(ConditionalExpression node)
        {
            this.expressions.Add(node);
            return base.VisitConditional(node);
        }

        protected override Expression VisitConstant(ConstantExpression node)
        {
            this.expressions.Add(node);
            return base.VisitConstant(node);
        }

        protected override Expression VisitDebugInfo(DebugInfoExpression node)
        {
            this.expressions.Add(node);
            return base.VisitDebugInfo(node);
        }

        protected override Expression VisitDefault(DefaultExpression node)
        {
            this.expressions.Add(node);
            return base.VisitDefault(node);
        }

        protected override Expression VisitDynamic(DynamicExpression node)
        {
            this.expressions.Add(node);
            return base.VisitDynamic(node);
        }

        protected override Expression VisitExtension(Expression node)
        {
            this.expressions.Add(node);
            return base.VisitExtension(node);
        }

        protected override Expression VisitGoto(GotoExpression node)
        {
            this.expressions.Add(node);
            return base.VisitGoto(node);
        }

        protected override Expression VisitIndex(IndexExpression node)
        {
            this.expressions.Add(node);
            return base.VisitIndex(node);
        }

        protected override Expression VisitInvocation(InvocationExpression node)
        {
            this.expressions.Add(node);
            return base.VisitInvocation(node);
        }

        protected override Expression VisitLabel(LabelExpression node)
        {
            this.expressions.Add(node);
            return base.VisitLabel(node);
        }

        protected override Expression VisitLambda<T>(Expression<T> node)
        {
            this.expressions.Add(node);
            return base.VisitLambda(node);
        }

        protected override Expression VisitListInit(ListInitExpression node)
        {
            this.expressions.Add(node);
            return base.VisitListInit(node);
        }

        protected override Expression VisitLoop(LoopExpression node)
        {
            this.expressions.Add(node);
            return base.VisitLoop(node);
        }

        protected override Expression VisitMember(MemberExpression node)
        {
            this.expressions.Add(node);
            return base.VisitMember(node);
        }

        protected override Expression VisitMemberInit(MemberInitExpression node)
        {
            this.expressions.Add(node);
            return base.VisitMemberInit(node);
        }

        protected override Expression VisitMethodCall(MethodCallExpression node)
        {
            this.expressions.Add(node);
            return base.VisitMethodCall(node);
        }

        protected override Expression VisitNew(NewExpression node)
        {
            this.expressions.Add(node);
            return base.VisitNew(node);
        }

        protected override Expression VisitNewArray(NewArrayExpression node)
        {
            this.expressions.Add(node);
            return base.VisitNewArray(node);
        }

        protected override Expression VisitParameter(ParameterExpression node)
        {
            this.expressions.Add(node);
            return base.VisitParameter(node);
        }

        protected override Expression VisitRuntimeVariables(RuntimeVariablesExpression node)
        {
            this.expressions.Add(node);
            return base.VisitRuntimeVariables(node);
        }

        protected override Expression VisitSwitch(SwitchExpression node)
        {
            this.expressions.Add(node);
            return base.VisitSwitch(node);
        }

        protected override Expression VisitTry(TryExpression node)
        {
            this.expressions.Add(node);
            return base.VisitTry(node);
        }

        protected override Expression VisitTypeBinary(TypeBinaryExpression node)
        {
            this.expressions.Add(node);
            return base.VisitTypeBinary(node);
        }

        protected override Expression VisitUnary(UnaryExpression node)
        {
            this.expressions.Add(node);
            return base.VisitUnary(node);
        }

        public IEnumerable<Expression> Explore(Expression node)
        {
            this.expressions.Clear();
            this.Visit(node);
            return expressions.ToArray();
        }
    }
}

Я попробовал простую (не полную) реализацию для изменения выражения p => p.Id == 15 (код ниже). Существует один класс с именем "CrossMapping", который определяет соответствие между исходным и "новым" типами и членами типа.

Есть несколько названных методов Mutate_XY_Expression для каждого типа выражения, который создает новое видоизмененное выражение. Входные данные метода нуждаются в оригинальном выражении (MemberExpression originalExpression) как модель выражения, список или параметры выражения (IList<ParameterExpression> parameterExpressions) которые определяются параметрами в выражении "parent" и должны использоваться телом "parent" и объектом отображения (CrossMapping mapping), который определяет отображение между типами и членами.

Для полной реализации вам может понадобиться больше информации из выражения родителя, чем параметров. Но картина должна быть такой же.

Образец не реализует шаблон Visitor, как вы знаете - это потому, что простота. Но нет никаких препятствий для обращения к ним.

Надеюсь, это поможет.

Код (C# 4.0):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Linq.Expressions;

namespace ConsoleApplication1 {
    public class Product1 {
        public int Id { get; set; }
        public string Name { get; set; }
        public decimal Weight { get; set; }
    }

    public class Product2 {
        public int Id { get; set; }
        public string Name { get; set; }
        public decimal Weight { get; set; }
    }

    class Program {
        static void Main( string[] args ) {
            // list of products typed as Product1
            var lst1 = new List<Product1> {
                new Product1{ Id = 1, Name = "One" },
                new Product1{ Id = 15, Name = "Fifteen" },
                new Product1{ Id = 9, Name = "Nine" }
            };

            // the expression for filtering products
            // typed as Product1
            Expression<Func<Product1, bool>> q1;
            q1 = p => p.Id == 15;

            // list of products typed as Product2
            var lst2 = new List<Product2> {
                new Product2{ Id = 1, Name = "One" },
                new Product2{ Id = 15, Name = "Fifteen" },
                new Product2{ Id = 9, Name = "Nine" }
            };

            // type of Product1
            var tp1 = typeof( Product1 );
            // property info of "Id" property from type Product1
            var tp1Id = tp1.GetProperty( "Id", BindingFlags.Public | BindingFlags.Instance );
            // delegate type for predicating for Product1
            var tp1FuncBool = typeof( Func<,> ).MakeGenericType( tp1, typeof( bool ) );

            // type of Product2
            var tp2 = typeof( Product2 );
            // property info of "Id" property from type Product2
            var tp21Id = tp2.GetProperty( "Id", BindingFlags.Public | BindingFlags.Instance );
            // delegate type for predicating for Product2
            var tp2FuncBool = typeof( Func<,> ).MakeGenericType( tp2, typeof( bool ) );

            // mapping object for types and type members
            var cm1 = new CrossMapping {
                TypeMapping = {
                    // Product1 -> Product2
                    { tp1, tp2 },
                    // Func<Product1, bool> -> Func<Product2, bool>
                    { tp1FuncBool, tp2FuncBool }
                },
                MemberMapping = {
                    // Product1.Id -> Product2.Id
                    { tp1Id, tp21Id }
                }
            };

            // mutate express from Product1's "enviroment" to Product2's "enviroment"
            var cq1_2 = MutateExpression( q1, cm1 );

            // compile lambda to delegate
            var dlg1_2 = ((LambdaExpression)cq1_2).Compile();

            // executing delegate
            var rslt1_2 = lst2.Where( (Func<Product2, bool>)dlg1_2 ).ToList();

            return;
        }

        class CrossMapping {
            public IDictionary<Type, Type> TypeMapping { get; private set; }
            public IDictionary<MemberInfo, MemberInfo> MemberMapping { get; private set; }

            public CrossMapping() {
                this.TypeMapping = new Dictionary<Type, Type>();
                this.MemberMapping = new Dictionary<MemberInfo, MemberInfo>();
            }
        }
        static Expression MutateExpression( Expression originalExpression, CrossMapping mapping ) {
            var ret = MutateExpression(
                originalExpression: originalExpression,
                parameterExpressions: null,
                mapping: mapping
            );

            return ret;
        }
        static Expression MutateExpression( Expression originalExpression, IList<ParameterExpression> parameterExpressions, CrossMapping mapping ) {
            Expression ret;

            if ( null == originalExpression ) {
                ret = null;
            }
            else if ( originalExpression is LambdaExpression ) {
                ret = MutateLambdaExpression( (LambdaExpression)originalExpression, parameterExpressions, mapping );
            }
            else if ( originalExpression is BinaryExpression ) {
                ret = MutateBinaryExpression( (BinaryExpression)originalExpression, parameterExpressions, mapping );
            }
            else if ( originalExpression is ParameterExpression ) {
                ret = MutateParameterExpression( (ParameterExpression)originalExpression, parameterExpressions, mapping );
            }
            else if ( originalExpression is MemberExpression ) {
                ret = MutateMemberExpression( (MemberExpression)originalExpression, parameterExpressions, mapping );
            }
            else if ( originalExpression is ConstantExpression ) {
                ret = MutateConstantExpression( (ConstantExpression)originalExpression, parameterExpressions, mapping );
            }
            else {
                throw new NotImplementedException();
            }

            return ret;
        }

        static Type MutateType( Type originalType, IDictionary<Type, Type> typeMapping ) {
            if ( null == originalType ) { return null; }

            Type ret;
            typeMapping.TryGetValue( originalType, out ret );
            if ( null == ret ) { ret = originalType; }

            return ret;
        }
        static MemberInfo MutateMember( MemberInfo originalMember, IDictionary<MemberInfo, MemberInfo> memberMapping ) {
            if ( null == originalMember ) { return null; }

            MemberInfo ret;
            memberMapping.TryGetValue( originalMember, out ret );
            if ( null == ret ) { ret = originalMember; }

            return ret;
        }
        static LambdaExpression MutateLambdaExpression( LambdaExpression originalExpression, IList<ParameterExpression> parameterExpressions, CrossMapping mapping ) {
            if ( null == originalExpression ) { return null; }

            var newParameters = (from p in originalExpression.Parameters
                                 let np = MutateParameterExpression( p, parameterExpressions, mapping )
                                 select np).ToArray();

            var newBody = MutateExpression( originalExpression.Body, newParameters, mapping );

            var newType = MutateType( originalExpression.Type, mapping.TypeMapping );

            var ret = Expression.Lambda(
                delegateType: newType,
                body: newBody,
                name: originalExpression.Name,
                tailCall: originalExpression.TailCall,
                parameters: newParameters
            );

            return ret;
        }
        static BinaryExpression MutateBinaryExpression( BinaryExpression originalExpression, IList<ParameterExpression> parameterExpressions, CrossMapping mapping ) {
            if ( null == originalExpression ) { return null; }

            var newExprConversion = MutateExpression( originalExpression.Conversion, parameterExpressions, mapping );
            var newExprLambdaConversion = (LambdaExpression)newExprConversion;
            var newExprLeft = MutateExpression( originalExpression.Left, parameterExpressions, mapping );
            var newExprRigth = MutateExpression( originalExpression.Right, parameterExpressions, mapping );
            var newType = MutateType( originalExpression.Type, mapping.TypeMapping );
            var newMember = MutateMember( originalExpression.Method, mapping.MemberMapping);
            var newMethod = (MethodInfo)newMember;

            var ret = Expression.MakeBinary(
                binaryType: originalExpression.NodeType,
                left: newExprLeft,
                right: newExprRigth,
                liftToNull: originalExpression.IsLiftedToNull,
                method: newMethod,
                conversion: newExprLambdaConversion
            );

            return ret;
        }
        static ParameterExpression MutateParameterExpression( ParameterExpression originalExpresion, IList<ParameterExpression> parameterExpressions, CrossMapping mapping ) {
            if ( null == originalExpresion ) { return null; }

            ParameterExpression ret = null;
            if ( null != parameterExpressions ) {
                ret = (from p in parameterExpressions
                       where p.Name == originalExpresion.Name
                       select p).FirstOrDefault();
            }

            if ( null == ret ) {
                var newType = MutateType( originalExpresion.Type, mapping.TypeMapping );

                ret = Expression.Parameter( newType, originalExpresion.Name );
            }

            return ret;
        }
        static MemberExpression MutateMemberExpression( MemberExpression originalExpression, IList<ParameterExpression> parameterExpressions, CrossMapping mapping ) {
            if ( null == originalExpression ) { return null; }

            var newExpression = MutateExpression( originalExpression.Expression, parameterExpressions, mapping );
            var newMember = MutateMember( originalExpression.Member, mapping.MemberMapping );

            var ret = Expression.MakeMemberAccess(
                expression: newExpression,
                member: newMember
            );

            return ret;
        }
        static ConstantExpression MutateConstantExpression( ConstantExpression originalExpression, IList<ParameterExpression> parameterExpressions, CrossMapping mapping ) {
            if ( null == originalExpression ) { return null; }

            var newType = MutateType( originalExpression.Type, mapping.TypeMapping );
            var newValue = originalExpression.Value;

            var ret = Expression.Constant(
                value: newValue,
                type: newType
            );

            return ret;
        }
    }
}

Приведенный выше собственный ответ Джона великолепен, поэтому я расширил его для обработки вызовов методов, константных выражений и т. Д., Чтобы теперь он работал и для таких выражений, как:

x => x.SubObjects
      .AsQueryable()
      .SelectMany(y => y.GrandChildObjects)
      .Any(z => z.Value == 3)

Я также покончил с ExpressionTreeExplorer так как единственное, что нам нужно, это ParameterExpressions.

Вот код (Обновление: очистить кеш после завершения конвертации)

public class ExpressionTargetTypeMutator : ExpressionVisitor
{
    private readonly Func<Type, Type> typeConverter;
    private readonly Dictionary<Expression, Expression> _convertedExpressions 
        = new Dictionary<Expression, Expression>();

    public ExpressionTargetTypeMutator(Func<Type, Type> typeConverter)
    {
        this.typeConverter = typeConverter;
    }

    // Clear the ParameterExpression cache between calls to Visit.
    // Not thread safe, but you can probably fix it easily.
    public override Expression Visit(Expression node)
    {
        bool outermostCall = false;
        if (false == _isVisiting)
        {
            this._isVisiting = true;
            outermostCall = true;
        }
        try
        {
            return base.Visit(node);
        }
        finally
        {
            if (outermostCall)
            {
                this._isVisiting = false;
                _convertedExpressions.Clear();
            }
        }
    }


    protected override Expression VisitMember(MemberExpression node)
    {
        var sourceType = node.Member.ReflectedType;
        var targetType = this.typeConverter(sourceType);

        var converted = Expression.MakeMemberAccess(
            base.Visit(node.Expression),
            targetType.GetProperty(node.Member.Name));

        return converted;
    }

    protected override Expression VisitParameter(ParameterExpression node)
    {
        Expression converted;
        if (false == _convertedExpressions.TryGetValue(node, out converted))
        {
            var sourceType = node.Type;
            var targetType = this.typeConverter(sourceType);
            converted = Expression.Parameter(targetType, node.Name);
            _convertedExpressions.Add(node, converted);
        }
        return converted;
    }

    protected override Expression VisitMethodCall(MethodCallExpression node)
    {
        if (node.Method.IsGenericMethod)
        {
            var convertedTypeArguments = node.Method.GetGenericArguments()
                                                    .Select(this.typeConverter)
                                                    .ToArray();
            var genericMethodDefinition = node.Method.GetGenericMethodDefinition();
            var newMethod = genericMethodDefinition.MakeGenericMethod(convertedTypeArguments);
            return Expression.Call(newMethod, node.Arguments.Select(this.Visit));
        }
        return base.VisitMethodCall(node);
    }

    protected override Expression VisitConstant(ConstantExpression node)
    {
        var valueExpression = node.Value as Expression;
        if (null != valueExpression)
        {
            return Expression.Constant(this.Visit(valueExpression));
        }
        return base.VisitConstant(node);
    }

    protected override Expression VisitLambda<T>(Expression<T> node)
    {
        return Expression.Lambda(this.Visit(node.Body), node.Name, node.TailCall, node.Parameters.Select(x => (ParameterExpression)this.VisitParameter(x)));
    }
}

Разве ExecuteTypedList не выполняет то, что вы хотите сделать? SubSonic заполнит ваши DTO /POCO. Из блога Роба Коннери:

ExecuteTypedList<> пытается сопоставить имена возвращаемых столбцов с именами свойств переданного типа. В этом примере они точно совпадают - и это не совсем реальный мир. Вы можете обойти это путем наложения псевдонимов столбцов - так же, как вы бы использовали псевдоним SQL-вызова:

return Northwind.DB.Select("ProductID as 'ID'", "ProductName as 'Name'", "UnitPrice as 'Price'")
            .From<Northwind.Product>().ExecuteTypedList<Product>();

Вот ссылка на Rob's Writing Decoupled, Тестируемый код с SubSonic 2.1

Я думаю, что Linq-To-Sql выдаст желаемый SQL, если вы сделаете ваши запросы правильно. В этом случае, используя IQueryable и отложенное выполнение вы можете избежать возврата всех ActiveRecord.Widget записей.

IEnumerable<DataContract.Widget> GetMany( 
    Func<DataContract.Widget, bool> predicate) 
{ 
    // get Widgets
    IQueryable<DataContract.Widget> qry = dc.Widgets.Select(w => TODO: CONVERT_TO_DataContract.Widget);

    return qry.Where(predicate);
}
Другие вопросы по тегам