数据库
首页 > 数据库> > c#-Npgsql v3上的Dapper IPAddress / PhysicalAddress / Enum参数支持

c#-Npgsql v3上的Dapper IPAddress / PhysicalAddress / Enum参数支持

作者:互联网

Npgsql支持分别从类型为macaddr和inet的查询结果集中解析System.Net.NetworkInformation.PhysicalAddress和System.Net.IPAddress.例如,可以使用带有Dapper的Npgsql填充以下类:

-- 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以二进制形式发送数据,因此我认为这意味着inet和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);

我得到例外:

Problem with query: SELECT * FROM foo WHERE macaddress = :macAddress
System.NotSupportedException : The member macAddress of type System.Net.NetworkInformation.PhysicalAddress cannot be used as a parameter value

Dapper / Npgsql如何知道如何分别从inet和macaddr类型的列中解析IPAddress和PhysicalAddress,但是我无法将这些类型用作参数?在以前的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);

生成异常:

Problem with query: SELECT * FROM foo WHERE macaddress = :macAddress
Npgsql.NpgsqlException : 42883: operator does not exist: macaddr = text

我知道我可以将查询更改为“ SELECT * FROM foo WHERE macaddress =:macAddress :: macaddr”,但是我想知道是否有更清洁的方法来解决这个问题?有没有计划在不久的将来增加对这些类型的支持?

-开始编辑-

我只是意识到,同样的问题困扰着枚举类型.如果我有枚举参数,则可以从查询结果中解析它,但是无法将枚举传递给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: operator does not exist: bar = integer

如果相反,我将查询更改为:

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

然后我得到异常:

42846: cannot cast type integer to bar

我可以将枚举值作为参数传递的唯一方法是将它们作为文本传递并在查询中强制转换参数,如下所示:

// 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);

当然,有更好的方法可以解决此问题.任何人都可以阐明这是什么吗?

解决方法:

感谢Hambone和Shay的帮助,我找到了一种解决IPAddress和PhysicalAddress类型问题的方法.问题在于inet和macaddr是特定于Postgres的,而Dapper似乎与提供程序无关.因此,解决方案是添加一个自定义处理程序,该处理程序在将这些参数类型转发到Npgsql之前设置适当的NpgsqlDbType.定制处理程序可以编码为:

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);

现在,我可以通过Dapper发送PhysicalAddress和IPAddress参数,而无需对其进行字符串化.

但是,枚举提出了另一个挑战,因为Dapper 1.42不支持添加自定义枚举处理程序(请参阅Dapper问题#259/#286).更不幸的是,Dapper默认将枚举值作为整数发送给基础实现.因此,在使用Dapper 1.42(或更早版本)时,当前无法将枚举值发送到Npgsql而不将其转换为字符串.我已就此问题与Marc Gravell联系,并希望在不久的将来得到某种解决.在此之前,解决方案是:

1) Use Npgsql directly, bypassing Dapper
2) Send all enum values as text, and cast to the appropriate type in the query

我个人选择继续使用选项2.

开始编辑

浏览完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&gt ;,然后在单个代码路径中转换为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,而不会被转换为等效的数值(因此不会丢失关联的类型信息).整个过程似乎仍然令人费解,但是至少有一种方法可以使用Npgsql v3二进制格式通过Dapper传递枚举值参数.

标签:dapper,npgsql,c
来源: https://codeday.me/bug/20191028/1948727.html