在Postgres中插入带有自动递增字段的JSON列?

时间:2017-04-19 16:09:08

标签: c# json postgresql npgsql

首先,如果我使用了错误的术语,请原谅我,我对非常全新的PostgreSQL和NoSQL风格的数据存储。继续:

我正在使用npgsql将.NET项目连接到PostgreSQL数据库。我们将它用作NoSQL文档存储,我们的表由id列中的主键data列和jsonb对象组成,没有别的。

data列中,我们还想要一种生成id字段的方法,该字段在每个插入时自动递增。这个id不一定需要匹配主键,但这是我正在工作的假设。

我已经能够使用以下脚本在pgadmin中完成这项工作:

drop table public.testjson;
drop sequence public.testjson_id_seq;

CREATE SEQUENCE public.testjson_id_seq
  INCREMENT 1
  MINVALUE 1
  MAXVALUE 9223372036854775807
  START 1
  CACHE 1;
ALTER TABLE public.testjson_id_seq
  OWNER TO login;

CREATE TABLE public.testjson
(
  id bigint NOT NULL DEFAULT nextval('testjson_id_seq'::regclass),
  data jsonb,
  CONSTRAINT testjson_pkey PRIMARY KEY (id)
);
ALTER TABLE public.testjson
  OWNER TO login;


-- Insert data using currval --
INSERT INTO testjson (data) VALUES( to_jsonb('{"jsonid": '||currval('testjson_id_seq'::regclass)::bigint||', moreStuff: "all the stuff!"}'));
INSERT INTO testjson (data) VALUES( to_jsonb('{"jsonid": '||currval('testjson_id_seq'::regclass)::bigint||', moreStuff: "all the stuff!"}'));
INSERT INTO testjson (data) VALUES( to_jsonb('{"jsonid": '||currval('testjson_id_seq'::regclass)::bigint||', moreStuff: "all the stuff!"}'));
INSERT INTO testjson (data) VALUES( to_jsonb('{"jsonid": '||currval('testjson_id_seq'::regclass)::bigint||', moreStuff: "all the stuff!"}'));
select * from testjson

-- Output -- 

id | data
---------------------------------------------------------
1  | "{\"jsonid\": 1, moreStuff: \"all the stuff!\"}"
2  | "{\"jsonid\": 2, moreStuff: \"all the stuff!\"}"
3  | "{\"jsonid\": 3, moreStuff: \"all the stuff!\"}"
4  | "{\"jsonid\": 4, moreStuff: \"all the stuff!\"}"

到目前为止,这么好。我想我将重构这一点,以便jsonid字段有自己的序列并使用nextval而不是currval(以防止竞争条件导致重复),但问题不在于此。我在代码方面无法复制这个问题。环顾其他questions让我相信这可行:

var crcmd = new NpgsqlCommand("INSERT INTO "+_schema+"."+table+" ("+JsonColumn+") VALUES (:json) RETURNING id;", _conn);

var jsonData = getSerializedJsonData(thing, primaryKey);
crcmd.Parameters.AddWithValue("json", NpgsqlDbType.Jsonb, jsonData);

returnId = (long) crcmd.ExecuteScalar();

...

private string getSerializedJsonData<T>(T thing, string primaryKey, string tableSeq)
{
    var jsonThing = JsonConvert.SerializeObject(thing);

    var bracketIndex = jsonThing.IndexOf('{');
    var thingPrefix = jsonThing.Substring(0, bracketIndex + 1);
    var thingData = jsonThing.Substring(bracketIndex + 1);
    var pkEntry = "\"" + primaryKey +"\": currval('" + tableSeq + '::regclass)::bigint, ";
    jsonThing = thingPrefix + pkEntry + thingData;
    return jsonThing;
}

但是当我尝试测试它时,抛出以下异常:

Npgsql.PostgresException: 22P02: invalid input syntax for type json; Token "currval" is invalid

我出错的任何想法?

1 个答案:

答案 0 :(得分:0)

我明白了。原来我需要解决两件事。

第一个是转换为jsonb。我之前没有注意到,但是如果你查看我的postgres输出,data列将被保存为字符串而不是jsonb对象。将to_jsonb('...')替换为('...')::jsonb即可轻松解决此问题。

第二项任务是让它在C#中运行。为了做到这一点,我不得不改变创建NpgsqlCommand的方式:为了访问currval值,我必须将命令文本作为原始文本传递,而不是参数化正如我原本试图做的那样。所以最终的工作代码看起来更像是这样:

var jsonData = getSerializedJsonData(thing, primaryKey);
var crcmd = new NpgsqlCommand("INSERT INTO "+_schema+"."+table+" ("+JsonColumn+") VALUES (('+jsonData+')::jsonb) RETURNING id;", _conn);

returnId = (long) crcmd.ExecuteScalar();

...

private string getSerializedJsonData<T>(T thing, string primaryKey, string tableSeq)
{
    var jsonThing = JsonConvert.SerializeObject(thing);

    var bracketIndex = jsonThing.IndexOf('{');
    var thingPrefix = jsonThing.Substring(0, bracketIndex + 1);
    var thingData = jsonThing.Substring(bracketIndex + 1);
    var pkEntry = "\"" + primaryKey +"\": currval('" + tableSeq + '::regclass)::bigint, ";
    jsonThing = thingPrefix + pkEntry + thingData;
    return jsonThing;
}

-- Output (truncated) -- 
id | data
--------------------------------------------------------
1  | { IsActive: true, "DisplayName": "configUser", "UserLoginId": 1", "PasswordHash":... 
2  | { IsActive: true, "DisplayName": "WDCGVBNESSAULSAZLIVR", "UserLoginId": 2", "Pass...
3  | { IsActive: false, "DisplayName": "UJMFANMOSHPNDQSEUEGL", "UserLoginId": 3", "Pas...
4  | { IsActive: true, "DisplayName": "SOQDFTZVHPHXIXDVQJTS", "UserLoginId": 4", "Pass...
5  | { IsActive: true, "DisplayName": "WFPUMCRBRPDHSQALMKPW", "UserLoginId": 5", "Pass...
6  | { IsActive: true, "DisplayName": "HVBEGQSSFJWCJYCCLMXI", "UserLoginId": 6", "Pass...