Может ли Dapper передавать определенный пользователем составной тип в функцию PostgreSQL? - PullRequest
0 голосов
/ 17 мая 2019

Я пытаюсь выяснить, как использовать Dapper для передачи пользовательского составного типа в функцию PostgreSQL.Я знаю, что это возможно с SQL Server, и у меня уже есть рабочие примеры, использующие Dapper + SQL Server, однако я кратко расскажу, как сделать то же самое с PostgreSQL.

Из некоторых вещей, которые я прочитал, я не уверен, возможно ли это даже с Dapper + PostgreSQL, но я знаю, что он работает с простым Npgsql (и у меня есть рабочий примероб этом также).

Итак, как вызвать функцию PostgreSQL, которая принимает пользовательский составной тип, используя Dapper?

Пример пользовательского составного типа

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..

1 Ответ

0 голосов
/ 24 мая 2019

Для всех, кто мог бы наткнуться на этот вопрос, это сработало для меня, хотя я не очень доволен этим, поэтому я открыт для лучших решений этой проблемы.

Рабочий интеграционный тест

[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 с моим составным типом PostgreSQL hero через conn.TypeMapper.MapComposite.Тем не менее, мое внутреннее чувство (до сих пор) заключается в том, что я сражаюсь со своими собственными интеграционными тестами, поскольку в реальном приложении, вероятно, (?) Было бы идеально (?) Регистрировать все составные типы глобально в начале приложения.(Или, может быть, не из-за соображений производительности, если их много?)

Что мне не нравится в этом решении, так это то, что мои HeroTypeHandler действительно не дают никакой реальной ценности(не каламбур).Просто назначив value на parameter.Value, все получилось, и я предположил бы, что это то, что Даппер сделал бы для вызова, но, очевидно, нет.(?) Я бы предпочел не делать этого для большого количества составных типов, если у меня их было много.

Обратите внимание, так как меня интересует только отправка этого типа в функция PostgreSQL, я не счел необходимым реализовывать метод Parse, следовательно, NotImplementedException.YMMV

Кроме того, из-за некоторого рефакторинга, так как я опубликовал исходный вопрос, есть некоторые другие незначительные различия.Однако они не были связаны с общим исправлением, описанным выше.

...