简体   繁体   中英

How do I pass JSON as a primitive PostgreSQL type to a function using Dapper?

I have a PostgreSQL function that takes in a parameter of type json . Using Dapper, how do I execute a call that passes the object to the PostgreSQL function such that PostgreSQL recognizes the type as json instead of as text ?

Example PostgreSQL Function that takes in json type

CREATE OR REPLACE FUNCTION testfuncthattakesinjson(heroes json)
    RETURNS SETOF characters 
    LANGUAGE 'sql'

    STABLE
    ROWS 100
AS $BODY$

    SELECT  c.*
    FROM    characters c
    JOIN    json_array_elements(heroes) j
    ON      c.first_name = j->>'first_name'
    AND     c.last_name = j->>'last_name';

$BODY$;

Broken Example C# Integration Test

[Test]
public void Query_CallFunctionThatTakesInJsonParameter_FunctionUsesJsonType()
{
    using (var conn = new NpgsqlConnection(Db.GetConnectionStringToDatabase()))
    {
        var funcName = "testfuncthattakesinjson";
        var expect = CharacterTestData.Where(character => character.id <= 3);
        var jsonObj = JArray.FromObject(CharacterTestData
            .Where(character => character.id <= 3)
            .Select(character => new Hero(character))
            .ToList());

        SqlMapper.AddTypeHandler(new JArrayTypeHandler());

        // Act 
        var results = conn.Query<Character>(funcName, new
        {
            heroes = jsonObj
        }, commandType: CommandType.StoredProcedure);

        // Assert
        CollectionAssert.AreEquivalent(expect, results);
    }
}

Supporting JArrayTypeHandler

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

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

In this current iteration, I've added a SqlMapper.TypeHandler . (At the moment, I'm only concerned with passing the JArray to PostgreSQL, hence the NotImplmentedException for Parse .)

With this example, I get the following exception:

System.NotSupportedException : 'The CLR array type Newtonsoft.Json.Linq.JArray isn't supported by Npgsql or your PostgreSQL. If you wish to map it to an PostgreSQL composite type array you need to register it before usage, please refer to the documentation.'

In past iterations, I've also tried things like using a List<Hero> type handler and letting that type handler deal with the Json conversion.

I've also tried adding the Npgsql.Json.NET Nuget package extension for Npgsql and call conn.TypeMapper.UseJsonNet() in my test method, but that didn't seem to have any effect.

And if I do anything to serialize the object to a JSON string, then I get a different error (below), which makes sense.

Npgsql.PostgresException : '42883: function testfuncthattakesinjson(heroes => text) does not exist'

So, it is possible to use Dapper to pass a JSON object as a PostgreSQL primitive to a function?

You can use Dapper's ICustomQueryParameter interface.

public class JsonParameter : ICustomQueryParameter
{
    private readonly string _value;

    public JsonParameter(string value)
    {
        _value = value;
    }

    public void AddParameter(IDbCommand command, string name)
    {
        var parameter = new NpgsqlParameter(name, NpgsqlDbType.Json);
        parameter.Value = _value;

        command.Parameters.Add(parameter);
    }
}

Then your Dapper call becomes:

var results = conn.Query<Character>(funcName, new
    {
        heroes = new JsonParameter(jsonObj.ToString())
    }, commandType: CommandType.StoredProcedure);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM