Отключение дерева выражений предиката для нацеливания на другой тип
вступление
В приложении, над которым я сейчас работаю, есть два вида каждого бизнес-объекта: вид "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);
}