Как создать анонимный объект с именами свойств, определяемыми динамически?
Учитывая массив значений, я хотел бы создать анонимный объект со свойствами, основанными на этих значениях. Имена свойств были бы просто "pN"
, где N
- это индекс значения в массиве.
Например, данный
object[] values = { 123, "foo" };
Я хотел бы создать анонимный объект
new { p0 = 123, p1 = "foo" };
Единственный способ, которым я могу это сделать, - использовать цепочку switch
или if
до разумного количества параметров для поддержки, но мне было интересно, был ли более элегантный способ сделать это:
object[] parameterValues = new object[] { 123, "foo" };
dynamic values = null;
switch (parameterValues.Length)
{
case 1:
values = new { p0 = parameterValues[0] };
break;
case 2:
values = new { p0 = parameterValues[0], p1 = parameterValues[1] };
break;
// etc. up to a reasonable # of parameters
}
Фон
У меня есть существующий набор методов, которые выполняют sql-инструкции в базе данных. Обычно эти методы принимают string
для оператора sql и params object[]
для параметров, если они есть. Понимание заключается в том, что если запрос использует параметры, они будут называться @p0, @p1, @p2, etc.
.
Пример:
public int ExecuteNonQuery(string commandText, CommandType commandType, params object[] parameterValues) { .... }
который будет вызываться следующим образом:
db.ExecuteNonQuery("insert into MyTable(Col1, Col2) values (@p0, @p1)", CommandType.Text, 123, "foo");
Теперь я хотел бы использовать Dapper в этом классе, чтобы обернуть и выставить метод Dapper Query<T>
, и сделать это таким образом, чтобы это соответствовало существующие методы, например что-то вроде:
public IEnumerable<T> ExecuteQuery<T>(string commandText, CommandType commandType, params object[] parameterValues) { .... }
но метод Dapper Query<T>
принимает значения параметров в анонимном объекте:
var dog = connection.Query<Dog>("select Age = @Age, Id = @Id", new { Age = (int?)null, Id = guid });
что приводит к моему вопросу о создании анонимного объекта для передачи параметров Dapper.
Добавление кода с использованием класса DynamicParameter
по запросу @Paolo Tedesco.
string sql = "select * from Account where Id = @p0 and username = @p1";
dynamic values = new DynamicParameter(123, "test");
var accounts = SqlMapper.Query<Account>(connection, sql, values);
выдает исключение в строке 581 файла Dapper SqlMapper.cs:
using (var reader = cmd.ExecuteReader())
а исключение - SqlException
:
Должен объявить скалярную переменную "@p0".
и проверка свойства cmd.Parameters
не отображает параметры, настроенные для команды.
Ответы
Ответ 1
Вы злоупотребляете Dapper, вам никогда не нужно это делать, вместо этого используйте IDynamicParameters
или используйте конкретный чрезвычайно гибкий класс DynamicParameters
.
В частности:
string sql = "select * from Account where Id = @id and username = @name";
var values = new DynamicParameters();
values.Add("id", 1);
values.Add("name", "bob");
var accounts = SqlMapper.Query<Account>(connection, sql, values);
DynamicParameters
может принимать анонимный класс в конструкторе. Вы можете выполнить DynamicParameters
с помощью метода AddDynamicParams
.
Кроме того, не существует строгой зависимости от анонных типов. Dapper позволит использовать конкретные типы в качестве параметров, например:
class Stuff
{
public int Thing { get; set; }
}
...
cnn.Execute("select @Thing", new Stuff{Thing = 1});
У Кевина был аналогичный вопрос: Ищете быстрый и легкий способ объединить все свойства в POCO - DynamicParameters
отлично работает здесь, без необходимости волшебный обруч.
Ответ 2
Не совсем анонимный объект, но как насчет реализации DynamicObject, который возвращает значения для p1... pn на основе значений в массив? Будет ли это работать с Dapper?
Пример:
using System;
using System.Dynamic;
using System.Text.RegularExpressions;
class DynamicParameter : DynamicObject {
object[] _p;
public DynamicParameter(params object[] p) {
_p = p;
}
public override bool TryGetMember(GetMemberBinder binder, out object result) {
Match m = Regex.Match(binder.Name, @"^p(\d+)$");
if (m.Success) {
int index = int.Parse(m.Groups[1].Value);
if (index < _p.Length) {
result = _p[index];
return true;
}
}
return base.TryGetMember(binder, out result);
}
}
class Program {
static void Main(string[] args) {
dynamic d1 = new DynamicParameter(123, "test");
Console.WriteLine(d1.p0);
Console.WriteLine(d1.p1);
}
}
Ответ 3
Вы не можете динамически создавать анонимные объекты. Но Dapper должен работать с динамическим объектом. Для создания динамических объектов можно использовать Clay. Это позволяет вам писать код типа
var person = New.Person();
person["FirstName"] = "Louis";
// person.FirstName now returns "Louis"