Dapper可以将用户定义的复合类型传递给PostgreSQL函数吗?

时间:2019-05-17 17:06:33

标签: c# postgresql dapper

我试图弄清楚如何使用Dapper将用户定义的复合类型传递给PostgreSQL函数。我知道这在SQL Server中是可能的,并且我已经有了使用Dapper + SQL Server的工作示例,但是,我在如何使用PostgreSQL做同样的事情上很短。

从我读过的一些书中,我不确定Dapper + PostgreSQL是否甚至可以实现,但我知道它确实适用于普通的Npgsql(并且我有一个实际的例子好)。

那么,如何使用Dapper调用一个采用用户定义的复合类型的PostgreSQL函数?

用户定义的复合类型示例

CREATE TYPE hero AS (
    first_name text,
    last_name text
);

采用用户定义的复合类型的PostgreSQL函数示例

CREATE OR REPLACE FUNCTION testfuncthattakesinudt(our_hero hero)
    RETURNS SETOF characters 
    LANGUAGE 'sql'

    STABLE
    ROWS 1000
AS $BODY$

    SELECT  *
    FROM    characters
    WHERE   first_name = COALESCE(our_hero.first_name, '')
    AND     last_name = COALESCE(our_hero.last_name, '');

$BODY$;

C#理论示例

[Test]
public void UsingDapper_Query_CallFunctionThatTakesInUserDefinedCompositeType_FunctionUsesUserDefinedCompositeType()
{
    // Arrange
    using (var conn = new NpgsqlConnection(_getConnectionStringToDatabase()))
    {
        var funcName = "testfuncthattakesinudt";
        var expect = CharacterTestData.First();

        // Act
        var result = conn.Query<Character>(funcName,
            new
            {
                our_hero = new
                {
                    first_name = CharacterTestData.First().first_name,
                    last_name = CharacterTestData.First().last_name
                }
            },
            commandType: CommandType.StoredProcedure
        );

        // Assert
        Assert.AreEqual(expect, result);
    }
}

我知道使用简单的Npgsql,有必要创建类似于以下内容的参数:

var udtCompositeParameter = new NpgsqlParameter
{
    ParameterName = "our_hero",
    Value = new
    {
        first_name = CharacterTestData.First().first_name,
        last_name = CharacterTestData.First().last_name
    },
    DataTypeName = "hero"
};

但是使用Dapper时,我还没有找到设置DataTypeName或类似方法的方法。我尝试了多种方法来为Dapper设置参数形状(例如,使用类似DynamicParameter并指定dbType: DbType.Object的参数),但是无论如何,我总是会遇到与复合类型有关的类似错误。我也看过Dapper source,但是从我的观察来看,它只是针对PostgreSQL特定的测试,而那些似乎与我要进行的测试一致的测试是针对SQL Server量身定制的。 >

3 个答案:

答案 0 :(得分:0)

对于其他可能会偶然发现此问题的人来说,这对我很有用,尽管我对此并不十分满意,所以我愿意寻求更好的解决方案。

工作集成测试

[Test]
public void Query_CallFunctionThatTakesInUserDefinedType_FunctionUsesUserDefinedType()
{
    // Arrange
    using (var conn = new NpgsqlConnection(Db.GetConnectionStringToDatabase()))
    {
        var funcName = "testfuncthattakesinudt";
        var expect = CharacterTestData.First();

        SqlMapper.AddTypeHandler(new HeroTypeHandler());
        conn.Open();
        conn.ReloadTypes();
        conn.TypeMapper.MapComposite<Hero>("hero");

        // Act
        var result = conn.Query<Character>(funcName,
            new
            {
                our_hero = new Hero
                {
                    first_name = CharacterTestData.First().first_name,
                    last_name = CharacterTestData.First().last_name
                }
            },
            commandType: CommandType.StoredProcedure
        ).FirstOrDefault();

        // Assert
        Assert.AreEqual(expect, result);
    }
}

支持HeroTypeHandler

internal class HeroTypeHandler : SqlMapper.TypeHandler<Hero>
{
    public override Hero Parse(object value)
    {
        throw new NotImplementedException();
    }

    public override void SetValue(IDbDataParameter parameter, Hero value)
    {
        parameter.Value = value;
    }
}

修复似乎分为两个部分:

  1. 有必要添加一个HeroTypeHandler并通过SqlMapper.AddTypeHandler对其进行映射。
  2. 我需要通过Hero将类型hero映射到我的PostgreSQL复合类型conn.TypeMapper.MapComposite。但是,(到目前为止)我的直觉是,这仅仅是我在进行自己的集成测试,因为在实际的应用程序中,在应用程序开始时全局注册所有复合类型可能是(?)的理想选择。 (或者如果有很多原因,可能不是出于性能原因?)

尽管我不喜欢这种解决方案,但是我的HeroTypeHandler并没有真正提供任何实际价值(无双关语)。通过简单地将value分配给parameter.Value即可正常工作,我猜这是Dapper为该调用所做的,但是显然没有。 (?)如果我有很多复合类型,我宁愿不需要这样做。

请注意,由于我只关心将这种类型的发送给 PostgreSQL函数,因此我认为没有必要实现Parse方法,因此{{1} }。 YMMV

此外,由于自从我发布原始问题以来进行了一些重构,因此还存在一些其他小的差异。但是,它们与上面详述的整体修复程序无关。

答案 1 :(得分:0)

代替SqlMapper.TypeHandler<Hero>ICustomQueryParameter

您可以使用

Dapper.SqlMapper.AddTypeMap(typeof(Hero), DbType.Object);

答案 2 :(得分:0)

我从应用程序中的一个简单解决方案中脱颖而出:

在我的DbContext或应用程序的开始中设置UDT映射。

NpgsqlConnection.GlobalTypeMapper.MapComposite<MyContactParameter>("udt_my_contact");

MyContactParameter类:

public class MyContactParameter
{
    public Guid id { get; set; }

    public string name { get; set; }

    public string company_name  { get; set; }

    public bool is_something { get; set; }
}

使用Dapper调用该数据库过程,并传递该UDT类型的数组

await dapperConnection.ExecuteAsync(procedureName, new
          {
              param_first = "string anything",
              param_udt_list = new List<MyContactParameter>{ 
                  new MyContactParameter { 
                      id = Guid.NewId(),
                      name = "any name",
                      company_name = "any company name",
                      is_something = true
                  },
                  new MyContactParameter { 
                      id = Guid.NewId(),
                      name = "any name 2",
                      company_name = "any company name 2",
                      is_something = false
                  }
              }
          }, 
          commandType: CommandType.StoredProcedure
      );