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

Inserting JSON Column with auto-incrementing field in Postgres?

首先,如果我使用了错误的术语,请原谅我,我 非常 是 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

有什么地方出错了吗?

我明白了。结果我需要解决两件事。

第一个是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...