#c# #.net #linq-to-sql #dynamic-linq
#c# #.net #linq-to-sql #динамический-linq
Вопрос:
Я начну с фрагмента кода:
var objectType = typeof(Department); // Department is entity class from linqdatacontext
using (var dataContext = new DataModel.ModelDataContext())
{
var entity = Expression.Parameter(objectType, "model");
var keyValue = Expression.Property(entity, "Id");
var pkValue = Expression.Constant(reader.Value);
var cond = Expression.Equal(keyValue, pkValue);
var table = dataContext.GetTable(objectType);
... // and here i don't how to proceed
}
Я даже не уверен, правильно ли я строю это выражение. Однако, проще говоря, мне нужно динамически вызывать SingleOrDefault()
эту таблицу, чтобы найти объект по первичному ключу. В каждом примере, который я нашел, используется общий вариант GetTable<>()
, но я не могу использовать это очевидно. Я, вероятно, что-то упускаю из виду…
Комментарии:
1. Почему бы просто не сделать этот код сам по себе универсальным?
2. Ну, я этого не хочу, потому что у меня там около 30 объектов, которые мне нужно получить с помощью этого. Это было бы довольно грязно и утомительно.
3. Но если сам код является универсальным, то вам все равно нужен только один метод, если я чего-то не упускаю. (В этом случае
dynamic
, возможно, это решение, но я все еще не понимаю, почему здесь).4. Я мало что знаю о
dynamic
ключевом слове. Где вы видите какой-либо универсальный код? У меня там только классический тип и значение первичного ключа, не более того.5. Ах, так что в «реальной» версии параметр
objectType
находится в другом месте кода. Да, это остановит работу дженериков для вас, если вызывающий код не имеет соответствующего экземпляра или способен жестко запрограммировать тип. Посмотрите на ответ @SteveDanner тогда использованиеdynamic
в Where показывает, как вызывать свойства без знания типа во время компиляции. Это будет полезно позже, хотя я думаю, что ваша проблема немного отличается.
Ответ №1:
Всякий раз, когда я создаю деревья выражений, мне нравится начинать с примера того, что я создаю:
() => dataContext.GetTable<TEntity>().SingleOrDefault(entity => entity.Id == 1);
Исходя из этого, мы можем легко проанализировать целевое выражение. Вы на полпути к этому; вам просто нужно включить вызов GetTable
метода в дерево выражений, а затем создать внешнее лямбда-выражение для вызова всего этого:
using(var dataContext = new DataModel.ModelDataContext())
{
var getTableCall = Expression.Call(
Expression.Constant(dataContext),
"GetTable",
new[] { entityType });
var entity = Expression.Parameter(entityType, "entity");
var idCheck = Expression.Equal(
Expression.Property(entity, "Id"),
Expression.Constant(reader.Value));
var idCheckLambda = Expression.Lambda(idCheck, entity);
var singleOrDefaultCall = Expression.Call(
typeof(Queryable),
"SingleOrDefault",
new[] { entityType },
getTableCall,
Expression.Quote(idCheckLambda));
var singleOrDefaultLambda = Expression.Lambda<Func<object>>(
Expression.Convert(singleOrDefaultCall, typeof(object)));
var singleOrDefaultFunction = singleOrDefaultLambda.Compile();
return singleOrDefaultFunction();
}
Мы должны преобразовать SingleOrDefault
вызов, чтобы иметь возвращаемый тип объекта, чтобы он мог служить телом Func<object>
функции.
(Непроверенный)
Редактировать: параметризация контекста и значения данных
Теперь мы создаем эту функцию:
(dataContext, value) => dataContext.GetTable<TEntity>().SingleOrDefault(entity => entity.Id == value);
Вы бы изменили константы на параметры и добавили эти параметры в скомпилированную вами функцию:
var dataContextParameter = Expression.Parameter(typeof(ModelDataContext), "dataContext");
var valueParameter = Expression.Parameter(typeof(object), "value");
var getTableCall = Expression.Call(
dataContextParameter,
"GetTable",
new[] { entityType });
var entity = Expression.Parameter(entityType, "entity");
var idCheck = Expression.Equal(
Expression.Property(entity, "Id"),
valueParameter);
var idCheckLambda = Expression.Lambda(idCheck, entity);
var singleOrDefaultCall = Expression.Call(
typeof(Queryable),
"SingleOrDefault",
new[] { entityType },
getTableCall,
Expression.Quote(idCheckLambda));
var singleOrDefaultLambda =
Expression.Lambda<Func<ModelDataContext, object, object>>(
Expression.Convert(singleOrDefaultCall, typeof(object)),
dataContextParameter,
valueParameter);
var singleOrDefaultFunction = singleOrDefaultLambda.Compile();
// Usage
using(var dataContext = new DataModel.ModelDataContext())
{
return singleOrDefaultFunction(dataContext, reader.Value);
}
Комментарии:
1. Вау, это выглядит отвратительно 🙂 Я обязательно попробую это позже, сейчас мне нужно выполнить некоторые поручения. Я дам вам знать, если бы это сработало. Спасибо.
2. Хорошо, все работает нормально, большое вам спасибо. Теперь я думаю о некоторой оптимизации, потому что для этого будет много вызовов, и компиляция этого каждый раз может пойти не так. И есть также проблема с тем, что
dataContext
он удаляется в конце использования, поэтому его нельзя использовать как константу. Я пытаюсь что-тоExpression.Variable
сделать, но не уверен, как правильно это использовать. Было бы здорово иметь предварительно скомпилированную функцию, в которую можно поместить параметры с DataContext и Value, а затем выполнить ее для получения результата.3. Ты гений 🙂 Спасибо, это именно то, что мне нужно. Хотя это плохо работает,
Expression.Equal
потомуId
что свойство — это типInt32
, и я передаюobject
его. Но это не имеет большого значения, я могу с этим справиться, в данном случае это достаточно хорошо.
Ответ №2:
Если вы используете .NET 4, вы можете попробовать преобразовать возвращаемые объекты в as dynamic
, чтобы затем можно было запрашивать следующим образом.
using (var dataContext = new DataModel.ModelDataContext())
{
var entity = Expression.Parameter(objectType, "model");
var keyValue = Expression.Property(entity, "Id");
var pkValue = Expression.Constant(reader.Value);
var cond = Expression.Equal(keyValue, pkValue);
var table = dataContext.GetTable(objectType);
var result = table.Where(ent => ((dynamic)ent).SomeField == "SomeValue");
}
Комментарии:
1. Да, я использую .NET 4. Это было бы неплохо, но проблема начинается с табличной переменной. Нет метода Where, потому что он не типизирован или по какой-то другой причине.
Ответ №3:
Я все еще не совсем уверен в отношении всей вашей проблемы (и я подозреваю, что ответ about dynamic
решит часть того, что тоже возникнет). Тем не менее, просто для ответа:
В каждом примере, который я нашел, используется общий вариант GetTable<>() , но я не могу использовать это, очевидно
Для любого T
, Table<T>
реализует (среди других интерфейсов) ITable<T>
и ITable
. Первый типизирован в общем виде, второй — нет.
Форма GetTable<T>()
возвращает такой Table<T>
. Однако форма GetTable(Type t)
возвращает ITable
. Поскольку ITable
наследуется от IQueryable
вас, вы можете запросить его. Если вам нужно что-то сделать с этим запросом, который обычно требует знания типа (например, сравнения по заданному свойству), то dynamic
, согласно предыдущему ответу, данному Стивом Даннером, это позволяет сделать.
Комментарии:
1. Хорошо, но я все еще, вероятно, что-то упускаю в этом. Как я могу запросить
IQueryable
, не зная там типа? Когда я использую универсальныйGetTable<T>
, я получаю возможные методы, такие как Where и Select . Но с не-общимGetTable(Type t)
нет ничего. Я не знаю, как я мог бы использоватьdynamic
там (в основном потому, что это ключевое слово все еще неизвестно для меня )
Ответ №4:
Лично я бы сделал это, используя отражение и библиотеку динамических запросов LINQ.
Вы можете получить список всех ключей для таблицы с dataContext.Mapping.GetMetaType(objectType).IdentityMembers
помощью, а затем получить доступ к данным с помощью чего-то вроде dataContext.GetTable(objectType).Where(key.Name "==@0", id)
.
Очевидно, я пропустил несколько шагов — если у вас несколько ключей, вам нужно будет создать более полный предикат с циклом .IdentityMembers
, и если у вас всегда есть только один ключ, вы можете использовать .Сначала() на нем. Я тоже его не тестировал, но он должен быть довольно близким. Вероятно, всего будет 6-7 строк кода — я могу написать его (и протестировать), если вам это нужно.
Редактировать: библиотека динамических запросов LINQ может быть загружена из Microsoft по адресу http://msdn.microsoft.com/en-us/vcsharp/bb894665.aspx — просто включите DynamicLinq.cs в свой проект, и все в порядке.
Комментарии:
1. Все та же проблема. Не универсальная версия
GetTable
does не имеетWhere
метода.2. @FredyC: Я забыл, что установил отдельную библиотеку динамических запросов LINQ. Упс. Добавлена ссылка на него.