2015-08-19 1 views
3

Npgsql поддерживает разбор System.Net.NetworkInformation.PhysicalAddress и System.Net.IPAddress из результата запроса наборов типа MacAddr и инет соответственно. Например, следующий класс может быть заполнен с использованием Npgsql с Dapper:Щеголеватый IPAddress/PhysicalAddress/ENUM Параметр Поддержка над Npgsql v3

-- Postgres CREATE TABLE command 
CREATE TABLE foo (
    ipaddress inet, 
    macaddress macaddr 
); 
// C# class for type "foo" 
public class foo 
{ 
    public IPAddress ipaddress { get; set; } 
    public PhysicalAddress macaddress { get; set; } 
} 

// Code that loads all data from table "foo" 
IDbConnection connection = new NpgsqlConnection(connectionString); 
var foos = connection.Query<foo>("SELECT * FROM foo"); 

Поскольку Npgsql v3.0.1 посылает данные в двоичной форме, я полагаю, это означает, что есть некоторое бинарное представление для типов инет и macaddr. Тем не менее, когда я запускаю следующий код, используя одни и те же объявления выше ...

// Code that tries to load a specific row from "foo" 
var query = "SELECT * FROM foo WHERE macaddress = :macAddress"; 
var queryParams = new DynamicParameters(); 
queryParams.Add("macAddress", PhysicalAddress.Parse("FF-FF-FF-FF-FF-FF")); 
IDbConnection connection = new NpgsqlConnection(connectionString); 
var foos = connection.Query<foo>(query, queryParams); 

я получаю исключение:

Проблема с запросом: SELECT * FROM обув WHERE MacAddress =: MACADDRESS
системы .NotSupportedException: член MACADDRESS типа System.Net.NetworkInformation.PhysicalAddress не может быть использован в качестве значения параметра

Как это, что Чудной/Npgsql знает, как разобрать IPAddress и PhysicalAddress из столбца типа inet и macaddr, соответственно, я не могу использовать эти типы в качестве параметров? В предыдущих версиях Npgsql, я просто послал ToString() результат в качестве значения параметра, но в Npgsql v3.0.1 следующий код ...

// Code that tries to load a specific row from "foo" 
// The only change from above is the "ToString()" method called on PhysicalAddress 
var query = "SELECT * FROM foo WHERE macaddress = :macAddress"; 
var queryParams = new DynamicParameters(); 
queryParams.Add("macAddress", PhysicalAddress.Parse("FF-FF-FF-FF-FF-FF").ToString()); 
IDbConnection connection = new NpgsqlConnection(connectionString); 
var foos = connection.Query<foo>(query, queryParams); 

Генерирует исключение:

Проблема с запросом: SELECT * FROM обув WHERE MacAddress =: MACADDRESS
Npgsql.NpgsqlException: 42883: оператор не существует: MacAddr = текст

Я знаю, что я мог бы изменить запрос, чтобы быть «SELECT * FROM foo WHERE macaddress =: macAddress :: macaddr "вместо этого, но мне интересно, есть ли более чистый способ сделать это? Есть ли какой-нибудь план для поддержки поддержки этих типов в ближайшем будущем?

- НАЧАТЬ EDIT -

Я просто понял, что та же самая проблема преследующих перечисленных типов. Если у меня есть параметр перечисления, я могу проанализировать его из результата запроса, но я не могу передать перечисление в Postgres.Например:

CREATE TYPE bar AS ENUM (
    val1, 
    val2 
); 

CREATE TABLE log (
    mybar bar 
); 
public enum bar 
{ 
    val1, 
    val2 
} 

public class log 
{ 
    public bar mybar { get; set; } 
} 

// Code that loads all data from table "log" 
NpgsqlConnection.RegisterEnumGlobally<bar>(); 
IDbConnection connection = new NpgsqlConnection(connectionString); 
var logs = connection.Query<log>("SELECT * FROM log"); 

// Code that attempts to get rows from log with a specific enum 
var query = "SELECT * FROM log WHERE mybar = :barParam"; 
var queryParams = new DynamicParameters(); 
queryParams.Add("barParam", bar.val1); 
// The following throws an exception 
logs = connection.Query<log>(query, queryParams); 

В выше, все работает до последней строки, которая бросает следующее исключение:

42883: оператор не существует: бар = целое число

Если вместо этого я схожу на запрос:

SELECT * FROM log WHERE mybar = :barParam::bar 

Тогда я получаю исключение:

42846: не может бросить тип целое запретить

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

// Code that successfully performs the query 
var query = "SELECT * FROM log WHERE mybar = :barParam::bar"; 
var queryParams = new DynamicParameters(); 
queryParams.Add("barParam", bar.val1.ToString()); 
logs = connection.Query<log>(query, queryParams); 

Конечно есть лучший способ пойти об этом. Может ли кто-нибудь пролить свет на то, что это?

+0

можете ли вы пометить вопрос как ответ? –

+0

Я отметил это как ответил вчера. Разве это не появляется как таковое? –

+0

Все хорошо, спасибо! –

ответ

3

Благодаря помощи Хамбоне и Шей, я выяснил, как это решить для IPAddress и PhysicalAddress типов. Проблема в том, что inet и macaddr являются специфичными для Postgres, а Dapper оказывается агностиком провайдера. Поэтому решение состоит в том, чтобы добавить пользовательский обработчик, который устанавливает соответствующий NpgsqlDbType перед пересылкой этих типов параметров в Npgsql. Пользовательский обработчик может быть закодирован как:

using System; 
using System.Data; 
using Dapper; 
using Npgsql; 
using NpgsqlTypes; 

namespace MyNamespace 
{ 
    internal class PassThroughHandler<T> : SqlMapper.TypeHandler<T> 
    { 

     #region Fields 

     /// <summary>Npgsql database type being handled</summary> 
     private readonly NpgsqlDbType _dbType; 

     #endregion 

     #region Constructors 

     /// <summary>Constructor</summary> 
     /// <param name="dbType">Npgsql database type being handled</param> 
     public PassThroughHandler(NpgsqlDbType dbType) 
     { 
      _dbType = dbType; 
     } 

     #endregion 

     #region Methods 

     public override void SetValue(IDbDataParameter parameter, T value) 
     { 
      parameter.Value = value; 
      parameter.DbType = DbType.Object; 
      var npgsqlParam = parameter as NpgsqlParameter; 
      if (npgsqlParam != null) 
      { 
       npgsqlParam.NpgsqlDbType = _dbType; 
      } 
     } 

     public override T Parse(object value) 
     { 
      if (value == null || value == DBNull.Value) 
      { 
       return default(T); 
      } 
      if (!(value is T)) 
      { 
       throw new ArgumentException(string.Format(
        "Unable to convert {0} to {1}", 
        value.GetType().FullName, typeof(T).FullName), "value"); 
      } 
      var result = (T)value; 
      return result; 
     } 

     #endregion 

    } 
} 

Затем в статическом конструкторе моего уровня доступа к данным (DAL) класс, я просто добавить строки:

var ipAddressHandler = 
    new PassThroughHandler<IPAddress>(NpgsqlDbType.Inet); 
var macAddressHandler = 
    new PassThroughHandler<PhysicalAddress>(NpgsqlDbType.MacAddr); 
SqlMapper.AddTypeHandler(ipAddressHandler); 
SqlMapper.AddTypeHandler(macAddressHandler); 

Теперь я могу отправить PhysicalAddress и IPAddress через Dapper, без необходимости их изгибать.

Решения, однако, представляют собой еще одну проблему, поскольку Dapper 1.42 не поддерживает добавление пользовательских обработчиков перечислений (см. Вопросы Dapper #259/#286). Еще более неудачным является то, что Dapper по умолчанию отправляет перечисленные значения как целые числа в базовую реализацию. Поэтому в настоящее время невозможно отправить перечисленные значения в Npgsql без преобразования их в строки при использовании Dapper 1.42 (или ранее). Я связался с Марком Гравелем по этому вопросу и надеюсь получить какое-то разрешение в ближайшем будущем. До этого времени, разрешение не является:

1) Использование Npgsql напрямую, минуя щеголеватый
2) Отправить все значения перечислений в виде текста, а приведение к соответствующему типу в запросе

I лично решил продолжить с вариантом № 2.


НАЧАТЬ EDIT

После просмотра исходного кода Dapper, я понял, что есть третий вариант, чтобы сделать эту работу. Хотя создать индивидуальный обработчик для каждого перечисленного типа невозможно, можно перечислить перечислимое значение в объекте SqlMapper.ICustomQueryParameter. Поскольку код нужно только передать перечисленное значение Npgsql, реализация проста:

using System; 
using System.Data; 
using Dapper; 

namespace MyNamespace 
{ 
    internal class EnumParameter : SqlMapper.ICustomQueryParameter 
    { 

     #region Fields 

     /// <summary>Enumerated parameter value</summary> 
     private readonly Enum _val; 

     #endregion 

     #region Constructors 

     /// <summary>Constructor</summary> 
     /// <param name="val">Enumerated parameter value</param> 
     public EnumParameter(Enum val) 
     { 
      _val = val; 
     } 

     #endregion 

     #region Methods 

     public void AddParameter(IDbCommand command, string name) 
     { 
      var param = command.CreateParameter(); 
      param.ParameterName = name; 
      param.DbType = DbType.Object; 
      param.Value = _val; 
      command.Parameters.Add(param); 
     } 

     #endregion 

    } 
} 

Мой код был уже настроен таким образом, что каждый параметр добавляется к Dictionary<string, object>, затем преобразуется в DynamicParameters объект в одном код.Из-за этого, я был в состоянии добавить следующую проверку в петлю, которая преобразует от одного к другому:

var queryParams = new DynamicParameters(); 
foreach (var kvp in paramDict) 
{ 
    var enumParam = kvp.Value as Enum; 
    if (enumParam == null) 
    { 
     queryParams.Add(kvp.key, kvp.Value); 
    } 
    else 
    { 
     queryParams.Add(kvp.key, new EnumParameter(enumParam)); 
    } 
} 

Делая это, перечисляемые значения передаются Npgsql без преобразования их числовой эквивалент (и таким образом, без потери информации о соответствующем типе). Весь этот процесс по-прежнему кажется невероятно запутанным, но, по крайней мере, существует способ передачи параметров счисления через Dapper с использованием двоичных форм Npgsql v3.

0

Должен признаться, я не знаком с классом DynamicParameters ... используя собственные библиотеки NpgSql, однако, я думаю, вы можете выполнить основную задачу, указанную выше.

Это явное определение параметров:

List<foo> foos = new List<AdLookup.foo>(); 

NpgsqlConnection conn = new NpgsqlConnection(ConnectionString); 
conn.Open(); 

NpgsqlCommand cmd = new NpgsqlCommand(
    "select * from foo where macaddress = :macAddress", conn); 
cmd.Parameters.Add("macAddress", NpgsqlTypes.NpgsqlDbType.MacAddr); 
cmd.Parameters[0].Value = PhysicalAddress.Parse("FF-FF-FF-FF-FF-FF"); 

NpgsqlDataReader reader = cmd.ExecuteReader(); 

while (reader.Read()) 
{ 
    foo f = new foo(); 
    f.ipaddress = (IPAddress)reader.GetValue(0); 
    f.macaddress = (PhysicalAddress)reader.GetValue(1); 
    foos.Add(f); 
} 

reader.Close(); 
conn.Close(); 

Когда вы создали свой параметр, вы использовали значение параметра в качестве своего второго аргумента. Опять же, я не знаком с DynamicParameter класса, но в классе NpgSqlParameter, вы бы сделать это с помощью метода AddWithValue:

cmd.Parameters.AddWithValue("macAddress", PhysicalAddress.Parse("FF-FF-FF-FF-FF-FF")); 

Это может заменить cmd.Parameters.Add и cmd.Parameters[0] строки выше.

Опять же, я понятия не имею, помогает ли это или нет ... но я хотел бы обратиться к одному из способов отправки параметра в запрос.

Если DynamicParameters поддерживает AddWithValue, ваше решение может быть таким же простым.

+0

DynamicParameters не предлагает такой метод, но я узнал, что могу передать NpgsqlParameter в Npgsql, создав свой собственный пользовательский обработчик в Dapper. Это позволяет мне перехватывать и изменять параметр, переданный в Npgsql, таким образом, как вы описали. –

1

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

В двух словах Npgsql может преобразовывать экземпляры PhysicalAddress в двоичное представление PostgreSQL для macaddr и наоборот. В отличие от предыдущих версий, он больше не будет воспринимать текстовые представления прозрачно, вам решать их и предоставлять экземпляр PhysicalAddress.

var query = "SELECT * FROM foo WHERE macaddress = :macAddress"; 
var queryParams = new DynamicParameters(); 
queryParams.Add("macAddress", PhysicalAddress.Parse("FF-FF-FF-FF-FF-FF")); 
IDbConnection connection = new NpgsqlConnection(connectionString); 
var foos = connection.Query<foo>(query, queryParams); 

Проблема здесь, вероятно, в том, что Dapper не знает тип PhysicalAddress. Отъезд this issue у нас был с 3.0.0, где включен обработчик типа Dapper для jsonb, вам придется сделать то же самое с PhysicalAddress.

// Code that tries to load a specific row from "foo" 
// The only change from above is the "ToString()" method called on PhysicalAddress 
var query = "SELECT * FROM foo WHERE macaddress = :macAddress"; 
var queryParams = new DynamicParameters(); 
queryParams.Add("macAddress", PhysicalAddress.Parse("FF-FF-FF-FF-FF-FF").ToString()); 
IDbConnection connection = new NpgsqlConnection(connectionString); 
var foos = connection.Query<foo>(query, queryParams); 

Проблема здесь состоит в том, что вы предоставляете строку, где ожидается PhysicalAddress, поэтому PostgreSQL жалуется вы сравниваете тип MacAddr к типу текста.

Что касается перечислений, Npgsql 3.0.0 включает в себя поддержку записи и чтения перечислений напрямую, не проходя строчное представление. Однако вам нужно заранее сообщить Npgsql о вашем типе перечисления, предварительно позвонив NpgsqlConnection.RegisterEnumGlobally («pg_enum_type_name»). К сожалению, мне еще не удалось документировать новую поддержку enum, это произойдет в ближайшее время.

+0

Хорошо, так звучит так, что проблема «IPAddress» и «PhysicalAddress» вызвана тем, что он прошел через Dapper. Добавление 'SqlMapper.TypeHandler ' для каждого из этих типов, который просто устанавливает значение «NpgsqlDbType» в * Inet * и * MacAddr *, соответственно, разрешил эту проблему. Тем не менее, я все еще не могу использовать перечисленные типы в качестве параметров запроса в v3.0.1, когда я реализую обработчик типа Dapper, который устанавливает NpgsqlDbType в * Enum *. У вас есть тест, который иллюстрирует передачу перечисленных параметров в запрос Npgsql? –

+0

Итак, я посмотрел ваши тесты [здесь] (https://github.com/npgsql/npgsql/blob/develop/test/Npgsql.Tests/Types/EnumTests.cs), которые, как представляется, включают ошибку времени компиляции с нет свойства 'NpgsqlParameter.SpecificType'. Когда я устанавливаю 'parameter.EnumType = typeof (MyEnumType)' и 'parameter.NpgsqlDbType = NpgsqlDbType.Enum', я все еще получаю исключение» * 42804: column «SomeColumn» имеет тип «MyEnumType», но выражение имеет тип целое число *». –

+0

Я не могу добавить обработчик Dapper для перечисляемого типа, [номер # 259] (https://github.com/StackExchange/dapper-dot-net/issues/259). Поэтому мой предыдущий комментарий может быть неправильным, поскольку установка свойств * NpgsqlDbType * и * EnumType * в параметре может действительно работать. Тем не менее, ** нет способа сделать это через Dapper **, поскольку обработчики перечисления полностью игнорируются. На данный момент единственным решением, которое я мог бы придумать, является просто передать значения enum в базу данных в виде текста и передать их соответствующему типу в запросе. –

 Смежные вопросы

  • Нет связанных вопросов^_^