简体   繁体   English

npgsql:如何在一个以集合为参数的命令中使用 npgsql 选择多行(具有多个列值)?

[英]npgsql: How to select multiple rows (with multiple column values) with npgsql in one command with a collection as a parameter?

I have two tables defined below, supplier_balances and supplier_balance_items (btw there is a 1[supplier_balance]:N[supplier_balance_items] relationship between the two):我在下面定义了两个表, supplier_balancessupplier_balance_items (顺便说一句,两者之间存在1[supplier_balance]:N[supplier_balance_items]关系):

CREATE TABLE IF NOT EXISTS sch_brand_payment_data_lake_proxy.supplier_balances (
/* id is here for joining purposes with items table, instead of joining with the 4 columns used for sake
   of making sure a record is deemed as unique */
  id                             bigserial NOT NULL,
  accounting_document            text      NOT NULL,
  accounting_document_type       text      NOT NULL,
  company_code                   text      NOT NULL,
  document_date_year             int4      NOT NULL,
  accounting_doc_created_by_user text,
  accounting_clerk               text,
  assignment_reference           text,
  document_reference_id          text,
  original_reference_document    text,
  payment_terms                  text,
  supplier                       text,
  supplier_name                  text,
  document_date                  timestamp,
  posting_date                   timestamp,
  net_due_date                   timestamp,
  created_on                     timestamp default NULL,
  modified_on                    timestamp default NULL,
  pushed_on                      timestamp default NULL,
  is_modified bool GENERATED ALWAYS AS (modified_on IS NOT NULL AND modified_on > created_on) STORED,
  is_pushed   bool GENERATED ALWAYS AS (pushed_on   IS NOT NULL AND pushed_on > modified_on)  STORED,
  CONSTRAINT supplier_balances_pkey   PRIMARY KEY (id),
  /* accounting_document being the field of the composite unique index -> faster querying */
  CONSTRAINT supplier_balances_unique UNIQUE (
     accounting_document,
     accounting_document_type,
     company_code,
     document_date_year)
);
/* Creating other indexes for querying of those as well */
CREATE INDEX IF NOT EXISTS supplier_balances_accounting_document_type_idx
ON sch_brand_payment_data_lake_proxy.supplier_balances (accounting_document_type);
CREATE INDEX IF NOT EXISTS supplier_balances_company_code_idx
ON sch_brand_payment_data_lake_proxy.supplier_balances (company_code);
CREATE INDEX IF NOT EXISTS supplier_balances_document_date_year_idx
ON sch_brand_payment_data_lake_proxy.supplier_balances (document_date_year);

CREATE TABLE IF NOT EXISTS sch_brand_payment_data_lake_proxy.supplier_balance_items
(
    supplier_balance_id             bigserial NOT NULL,
    posting_view_item               text      NOT NULL,
    posting_key                     text,
    amount_in_company_code_currency numeric,
    amount_in_transaction_currency  numeric,
    cash_discount_1_percent         numeric,
    cash_discount_amount            numeric,
    clearing_accounting_document    text,
    document_item_text              text,
    gl_account                      text,
    is_cleared                      bool,
    clearing_date                   timestamp,
    due_calculation_base_date       timestamp,
    /* uniqueness is basically the posting_view_item for a given supplier balance */
    CONSTRAINT supplier_balance_items_pkey PRIMARY KEY (supplier_balance_id, posting_view_item),
    /* 1(supplier balance):N(supplier balance items) */
    CONSTRAINT supplier_balance_items_fkey FOREIGN KEY (supplier_balance_id)
               REFERENCES sch_brand_payment_data_lake_proxy.supplier_balances (id)
               ON DELETE CASCADE
               ON UPDATE CASCADE
);

Note: I'm just filling up the columns that can't be NULL for the sake of simplicity.注意:为了简单起见,我只是填写不能为NULL的列。

INSERT INTO 
sch_brand_payment_data_lake_proxy.supplier_balances 
(accounting_document, accounting_document_type, company_code, document_date_year)
VALUES 
('A', 'B', 'C', 0),
('A', 'B', 'C', 1),
('A', 'B', 'C', 2),
('A', 'B', 'C', 3),
('A', 'B', 'C', 4),
('A', 'B', 'C', 5)
RETURNING id;

Output:输出:

id ID
1 1
2 2
3 3
4 4
5 5
6 6
INSERT INTO 
sch_brand_payment_data_lake_proxy.supplier_balance_items 
(supplier_balance_id, posting_view_item)
VALUES 
(1, 'A'),
(1, 'B'),
(3, 'A'),
(3, 'B'),
(2, 'A'),
(1, 'C');
SELECT 
    accounting_document, 
    accounting_document_type, 
    company_code, 
    document_date_year
FROM sch_brand_payment_data_lake_proxy.supplier_balances;

Output:输出:

id ID accounting_document会计文件 accounting_document_type会计文件类型 company_code公司代码 document_date_year文档日期年份
1 1 A一个 B C C 0 0
2 2 A一个 B C C 1 1
3 3 A一个 B C C 2 2
4 4 A一个 B C C 3 3
5 5 A一个 B C C 4 4
6 6 A一个 B C C 5 5
SELECT 
    supplier_balance_id,
    posting_view_item
FROM sch_brand_payment_data_lake_proxy.supplier_balance_items;

Output:输出:

supplier_balance_id供应商余额ID posting_view_item posting_view_item
1 1 A一个
1 1 B
3 3 A一个
3 3 B
2 2 A一个
1 1 C C

Now if we want to select multiple values in a JOIN we can perform in a raw SQL:现在,如果我们想在 JOIN 中选择多个值,我们可以在原始 SQL 中执行:

SELECT 
    id,
    accounting_document, 
    accounting_document_type, 
    company_code, 
    document_date_year, 
    posting_view_item
FROM sch_brand_payment_data_lake_proxy.supplier_balances
LEFT OUTER JOIN sch_brand_payment_data_lake_proxy.supplier_balance_items
ON supplier_balances.id = supplier_balance_items.supplier_balance_id
WHERE (accounting_document, accounting_document_type, company_code, document_date_year)
IN  (('A', 'B', 'C', 1), ('A', 'B', 'C', 2))

Output:输出:

id ID accounting_document会计文件 accounting_document_type会计文件类型 company_code公司代码 document_date_year文档日期年份 posting_view_item posting_view_item
2 2 A一个 B C C 1 1 A一个
3 3 A一个 B C C 2 2 A一个

https://github.com/npgsql/npgsql/issues/1199 https://github.com/npgsql/npgsql/issues/1199

Now, when using npgsql in C#, reproducing the query just above is an easy feat:现在,当在 C# 中使用npgsql时,重现上面的查询是一件容易的事:

using System.Data;

using Npgsql;

var connectionStringBuilder = new NpgsqlConnectionStringBuilder
{
    Host     = "localhost",
    Port     = 5432,
    Username = "brand_payment_migration",
    Password = "secret",
    Database = "brand_payment"
};
using var connection = new NpgsqlConnection(connectionStringBuilder.ToString());
connection.Open();
using var command = connection.CreateCommand();
command.CommandText = 
"SELECT id, accounting_document, accounting_document_type, company_code, document_date_year, posting_view_item " +
"FROM sch_brand_payment_data_lake_proxy.supplier_balances " +
"LEFT OUTER JOIN sch_brand_payment_data_lake_proxy.supplier_balance_items " +
"ON supplier_balances.id = supplier_balance_items.supplier_balance_id " +
"WHERE (accounting_document, accounting_document_type, company_code, document_date_year) " +
"IN (('A', 'B', 'C', 1), ('A', 'B', 'C', 2));";

using var reader = command.ExecuteReader();
using var dataTable = new DataTable();
dataTable.Load(reader);
var cols = dataTable.Columns.Cast<DataColumn>().ToArray();
Console.WriteLine(string.Join(Environment.NewLine, cols.Select((x, i) => $"Col{i} = {x}")));
Console.WriteLine(string.Join("\t", cols.Select((_, i) => $"Col{i}")));
foreach (var dataRow in dataTable.Rows.Cast<DataRow>())
{
    Console.WriteLine(string.Join("\t", dataRow.ItemArray));
}

which, as expected outputs:其中,正如预期的输出:

Col0 = id
Col1 = accounting_document
Col2 = accounting_document_type
Col3 = company_code
Col4 = document_date_year
Col5 = posting_view_item
Col0    Col1    Col2    Col3    Col4    Col5
2       A       B       C       1       A
3       A       B       C       2       A
3       A       B       C       2       B

Now, what I would like to achieve is that, instead of passing a raw string for (('A', 'B', 'C', 1), ('A', 'B', 'C', 2));现在,我想要实现的是,而不是为(('A', 'B', 'C', 1), ('A', 'B', 'C', 2)); , I would love to use a NpgSqlParameter with a collection of value set (ie for each column)). ,我很想使用NpgSqlParameter与一组值集(即每列))。

So I've changed the C# snippet above and added the parameter所以我改变了上面的 C# 片段并添加了参数

// ...
"WHERE (accounting_document, accounting_document_type, company_code, document_date_year) " +
"IN @values;";
var parameter = command.CreateParameter();
parameter.ParameterName = "@values";
parameter.NpgsqlDbType = NpgsqlDbType.Array;
parameter.NpgsqlValue = new object[,]
{
    { "A", "B", "C", 1 }, 
    { "A", "B", "C", 2 }
};
// Note: the same kind of issue arises when using tuples, i.e.
// ( "A", "B", "C", 1 )
// ( "A", "B", "C", 2 )
command.Parameters.Add(parameter);
using var reader = command.ExecuteReader();
// ...

Then I'm getting this exception:然后我得到了这个例外:

Unhandled exception. System.ArgumentOutOfRangeException: Cannot set NpgsqlDbType to just Array, Binary-Or with the element type (e.g. Array of Box is NpgsqlDbType.Array | Npg
sqlDbType.Box). (Parameter 'value')
   at Npgsql.NpgsqlParameter.set_NpgsqlDbType(NpgsqlDbType value)
   at Program.<Main>$(String[] args) in C:\Users\natalie-perret\Desktop\Personal\playground\csharp\CSharpPlayground\Program.cs:line 25

Then I've tried to work around that error with:然后我尝试使用以下方法解决该错误:

parameter.NpgsqlDbType = NpgsqlDbType.Array | NpgsqlDbType.Unknown;

but then getting another exception:但随后又得到另一个例外:

Unhandled exception. System.ArgumentException: No array type could be found in the database for element .<unknown>
   at Npgsql.TypeMapping.ConnectorTypeMapper.ResolveByNpgsqlDbType(NpgsqlDbType npgsqlDbType)
   at Npgsql.NpgsqlParameter.ResolveHandler(ConnectorTypeMapper typeMapper)
   at Npgsql.NpgsqlParameterCollection.ValidateAndBind(ConnectorTypeMapper typeMapper)
   at Npgsql.NpgsqlCommand.ExecuteReader(CommandBehavior behavior, Boolean async, CancellationToken cancellationToken)
   at Npgsql.NpgsqlCommand.ExecuteReader(CommandBehavior behavior, Boolean async, CancellationToken cancellationToken)
   at Npgsql.NpgsqlCommand.ExecuteReader(CommandBehavior behavior)
   at Program.<Main>$(String[] args) in C:\Users\natalie-perret\Desktop\Personal\playground\csharp\CSharpPlayground\Program.cs:line 32

Seems that the type needs to be registered for some reason, actually if I don't specify the type:似乎由于某种原因需要注册类型,实际上如果我不指定类型:

Unhandled exception. System.NotSupportedException: The CLR type System.Object isn't natively supported by Npgsql or your PostgreSQL. To use it with a PostgreSQL composite
 you need to specify DataTypeName or to map it, please refer to the documentation.
   at Npgsql.TypeMapping.ConnectorTypeMapper.ResolveByClrType(Type type)
   at Npgsql.TypeMapping.ConnectorTypeMapper.ResolveByClrType(Type type)
   at Npgsql.NpgsqlParameter.ResolveHandler(ConnectorTypeMapper typeMapper)
   at Npgsql.NpgsqlParameter.Bind(ConnectorTypeMapper typeMapper)
   at Npgsql.NpgsqlParameterCollection.ValidateAndBind(ConnectorTypeMapper typeMapper)
   at Npgsql.NpgsqlCommand.ExecuteReader(CommandBehavior behavior, Boolean async, CancellationToken cancellationToken)
   at Npgsql.NpgsqlCommand.ExecuteReader(CommandBehavior behavior, Boolean async, CancellationToken cancellationToken)
   at Npgsql.NpgsqlCommand.ExecuteReader(CommandBehavior behavior)
   at Program.<Main>$(String[] args) in C:\Users\natalie-perret\Desktop\Personal\playground\csharp\CSharpPlayground\Program.cs:line 31

[EDIT] [编辑]

The temporary solution I've ended up with is to rely on the jsonb support and in particular the jsonb_to_recordset function (see the PostgreSQL documentation section about json functions ):我最终得到的临时解决方案是依赖 jsonb 支持,尤其是jsonb_to_recordset函数(请参阅PostgreSQL 文档部分关于 json 函数):

using System.Data;
using System.Text.Json;

using Npgsql;
using NpgsqlTypes;


var connectionStringBuilder = new NpgsqlConnectionStringBuilder
{
    Host     = "localhost",
    Port     = 5432,
    Username = "brand_payment_migration",
    Password = "secret",
    Database = "brand_payment"
};
using var connection = new NpgsqlConnection(connectionStringBuilder.ToString());
connection.Open();
using var command = connection.CreateCommand();
command.CommandText = 
"SELECT id, accounting_document, accounting_document_type, company_code, document_date_year, posting_view_item " +
"FROM sch_brand_payment_data_lake_proxy.supplier_balances " +
"LEFT OUTER JOIN sch_brand_payment_data_lake_proxy.supplier_balance_items " +
"ON supplier_balances.id = supplier_balance_items.supplier_balance_id " +
"WHERE (accounting_document, accounting_document_type, company_code, document_date_year) " +
"IN (SELECT * FROM jsonb_to_recordset(@values) " +
"AS params (accounting_document text, accounting_document_type text, company_code text, document_date_year integer));";
var parameter = command.CreateParameter();
parameter.ParameterName = "@values";
parameter.NpgsqlDbType = NpgsqlDbType.Jsonb;
parameter.NpgsqlValue = JsonSerializer.Serialize(new []
{
    new Params("A", "B", "C", 1), 
    new Params("A", "B", "C", 2)
});
command.Parameters.Add(parameter);
using var reader = command.ExecuteReader();
using var dataTable = new DataTable();
dataTable.Load(reader);
var cols = dataTable.Columns.Cast<DataColumn>().ToArray();
Console.WriteLine(string.Join(Environment.NewLine, cols.Select((x, i) => $"Col{i} = {x}")));
Console.WriteLine(string.Join("\t", cols.Select((_, i) => $"Col{i}")));
foreach (var dataRow in dataTable.Rows.Cast<DataRow>())
{
    Console.WriteLine(string.Join("\t", dataRow.ItemArray));
}


public Params(
    string accounting_document, 
    string accounting_document_type,
    string company_code,
    int document_date_year);

Output:输出:

Col0 = id
Col1 = accounting_document
Col2 = accounting_document_type
Col3 = company_code
Col4 = document_date_year
Col5 = posting_view_item
Col0    Col1    Col2    Col3    Col4    Col5
2       A       B       C       1       A
3       A       B       C       2       A
3       A       B       C       2       B

But this comes at the cost of adding an additional step of json serialization when passing the parameters.但这是以在传递参数时添加额外的 json 序列化步骤为代价的。 So other than that and building an insanely long string, I'm kinda puzzled at the fact there is no way to pass directly the actual values, without extra steps, to the NpgsqlParameter.NpgsqlValue property.因此,除了构建一个非常长的字符串之外,我有点困惑的是,没有办法直接将实际值传递给NpgsqlParameter.NpgsqlValue属性,而无需额外的步骤。

[Edit 2] [编辑 2]

Adding a DbFiddle添加一个DbFiddle

[Edit 3] [编辑 3]

The same jsonb "trick" can be used for feeding the data (albeit, same hiccups I've already mentioned above):可以使用相同的 jsonb“技巧”来提供数据(尽管我已经在上面提到了同样的问题):

INSERT INTO sch_brand_payment_data_lake_proxy.supplier_balances
    (accounting_document, accounting_document_type, company_code, document_date_year)
SELECT * FROM jsonb_to_recordset(
    '[{"accounting_document":"E","accounting_document_type":"B","company_code":"C","document_date_year":1},
      {"accounting_document":"E","accounting_document_type":"B","company_code":"C","document_date_year":2}]'::jsonb)
       AS params (accounting_document text, accounting_document_type text, company_code text, document_date_year integer)
RETURNING id;

[EDIT 4] Another way is to use jsonb_populate_recordset and pass the relevant NULL::table-full-name as the first parameter (which is gonna define the columns) and the relevant jsonb as the second parameter (akin to jsonb_to_recordset for the 1st parameter). [编辑 4] 另一种方法是使用jsonb_populate_recordset并将相关的NULL::table-full-name作为第一个参数(将定义列)和相关的jsonb作为第二个参数(类似于第一个参数的jsonb_to_recordset )。

Basically the 3 main ways of achieving what I wanted (updated the DbFiddle accordingly ):基本上实现我想要的 3 种主要方法(相应地更新了DbFiddle ):

总结一下

Note: things might get even easier with PostgreSQL 15 with the json_table feature .注意:使用带有json_table功能的 PostgreSQL 15 可能会变得更容易。

[EDIT 3] This article makes a pretty good job a summing things up: https://dev.to/forbeslindesay/postgres-unnest-cheat-sheet-for-bulk-operations-1obg [编辑 3] 这篇文章很好地总结了一些事情: https ://dev.to/forbeslindesay/postgres-unnest-cheat-sheet-for-bulk-operations-1obg

[EDIT 2] [编辑 2]

Following up the issue I've filed earlier today https://github.com/npgsql/npgsql/issues/4437#issuecomment-1113999994跟进我今天早些时候提出的问题https://github.com/npgsql/npgsql/issues/4437#issuecomment-1113999994

I've settled for a solution / workaround mentioned by @dhedey in another, somehow, related issue :我已经解决了@dhedey另一个以某种方式相关的问题中提到的解决方案/解决方法:

If it helps anyone else, I have found quite a neat workaround for these types of queries using the UNNEST command, which can take multiple array parameters and zip them together into columns, which can be joined with the table to filter to the relevant columns.如果它对其他人有帮助,我发现使用UNNEST命令对这些类型的查询有一个很好的解决方法,该命令可以采用多个数组参数并将它们一起压缩到列中,这些列可以与表连接以过滤到相关列。

The use of the join is also more performant than the ANY/IN pattern in some cases.在某些情况下,连接的使用也比 ANY/IN 模式更高效。

 SELECT * FROM table WHERE (itemauthor, itemtitle) = ANY (('bob', 'hello'), ('frank', 'hi')...)

Can be represented with:可以表示为:

 var authorsParameter = new NpgsqlParameter("@authors", NpgsqlDbType.Array | NpgsqlDbType.Varchar) { Value = authors.ToList() }; var titlesParameter = new NpgsqlParameter("@titles", NpgsqlDbType.Array | NpgsqlDbType.Varchar) { Value = titles.ToList() }; var results = dbContext.Set<MyRow>() .FromSqlInterpolated($@" SELECT t.* FROM UNNEST({authorsParameter}, {titlesParameter}) params (author, title) INNER JOIN table t ON t.author = params.author AND t.title = params.title ");

NB - the Varchar can be replaced by other types for parameters which are arrays of other types (eg Bigint) - check out the NpgsqlDbType enum for more details.注意 - Varchar 可以被其他类型的参数替换为其他类型的数组(例如 Bigint) - 查看NpgsqlDbType枚举以获取更多详细信息。

I've then rewritten some the code I posted originally and it seems that the unnest PostgreSQL function solution works like a charm.然后我重写了我最初发布的一些代码,似乎unnest PostgreSQL 函数解决方案就像一个魅力。 This is the answer I accept for the time being, it looks neater than the Json / JsonB which requires either further postgresql-json-specific mapping shenaniganeries or extract.这是我暂时接受的答案,它看起来比 Json / JsonB 更整洁,后者需要进一步的 postgresql-json 特定映射恶作剧或提取。

Though, I'm not yet exactly too sure about the performance implications:不过,我还不太确定性能影响:

  • unnest involves that you map the differenceunnest涉及您映射差异
  • jsonb_to_recordset requires an additional .NET Json serialization step, and in some instances, to explicitly map the output of jsonb_to_recordset to the relevant columns. jsonb_to_recordset需要额外的 .NET Json 序列化步骤,并且在某些情况下,需要将jsonb_to_recordset的输出显式映射到相关列。

Both do not come for free.两者都不是免费的。 But I like that unnest makes it explicitly for each column (ie each set / collections of values of a bigger .NET type (tuples, records, classs, structs, etc.)) you're passing to the NpgsqlParameter.NpgsqlValue property which DB type is gonna be used via the NpgsqlDbType enum但我喜欢unnest明确地为每一列(即每个集合/更大的 .NET 类型(元组、记录、类、结构等)的值的集合)传递给 DB 的NpgsqlParameter.NpgsqlValue属性类型将通过NpgsqlDbType枚举使用

using System.Data;

using Npgsql;
using NpgsqlTypes;


var connectionStringBuilder = new NpgsqlConnectionStringBuilder
{
    Host     = "localhost",
    Port     = 5432,
    Username = "brand_payment_migration",
    Password = "secret",
    Database = "brand_payment"
};
using var connection = new NpgsqlConnection(connectionStringBuilder.ToString());
connection.Open();

var selectStatement =
    "SELECT * FROM sch_brand_payment_data_lake_proxy.supplier_balances " +
    "WHERE (accounting_document, accounting_document_type, company_code, document_date_year) " +
    "IN (SELECT * FROM  unnest(" +
    "@accounting_document_texts, " +
    "@accounting_document_types, " +
    "@company_codes, " +
    "@document_date_years" +
    "))";

var insertStatement = 
    "INSERT INTO sch_brand_payment_data_lake_proxy.supplier_balances " +
    "(accounting_document, accounting_document_type, company_code, document_date_year) " + 
    "SELECT * FROM unnest(" +
    "@accounting_document_texts, " +
    "@accounting_document_types, " +
    "@company_codes, " +
    "@document_date_years" + 
    ") RETURNING id;";

var parameters = new (string Name, NpgsqlDbType DbType, object Value)[]
{
    ("@accounting_document_texts", NpgsqlDbType.Array | NpgsqlDbType.Text,    new[] {"G", "G", "G"}),
    ("@accounting_document_types", NpgsqlDbType.Array | NpgsqlDbType.Text,    new[] {"Y", "Y", "Y"}),
    ("@company_codes",             NpgsqlDbType.Array | NpgsqlDbType.Text,    new[] {"Z", "Z", "Z"}),
    ("@document_date_years",       NpgsqlDbType.Array | NpgsqlDbType.Integer, new[] {1, 2, 3})
};

connection.ExecuteNewCommandAndWriteResultToConsole(insertStatement, parameters);
connection.ExecuteNewCommandAndWriteResultToConsole(selectStatement, parameters);

public static class Extensions
{
    public static void AddParameter(this NpgsqlCommand command, string name, NpgsqlDbType dbType, object value)
    {
        var parameter = command.CreateParameter();
        parameter.ParameterName = name;
        parameter.NpgsqlDbType  = dbType;
        parameter.NpgsqlValue   = value;
        command.Parameters.Add(parameter);
    }

    public static NpgsqlCommand CreateCommand(this NpgsqlConnection connection, 
        string text, 
        IEnumerable<(string Name, NpgsqlDbType DbType, object Value)> parameters)
    {
        var command = connection.CreateCommand();
        command.CommandText = text;
        foreach (var (name, dbType, value) in parameters)
        {
            command.AddParameter(name, dbType, value);
        }

        return command;
    }
    public static void ExecuteAndWriteResultToConsole(this NpgsqlCommand command)
    {
        Console.WriteLine($"Executing command... {command.CommandText}");
        
        using var reader = command.ExecuteReader();
        using var dataTable = new DataTable();
        dataTable.Load(reader);
        var cols = dataTable.Columns.Cast<DataColumn>().ToArray();
        Console.WriteLine(string.Join(Environment.NewLine, cols.Select((x, i) => $"Col{i} = {x}")));
        Console.WriteLine(string.Join("\t", cols.Select((_, i) => $"Col{i}")));
        foreach (var dataRow in dataTable.Rows.Cast<DataRow>())
        {
            Console.WriteLine(string.Join("\t", dataRow.ItemArray));
        }
    }

    public static void ExecuteNewCommandAndWriteResultToConsole(this NpgsqlConnection connection, 
        string text,
        IEnumerable<(string Name, NpgsqlDbType DbType, object Value)> parameters)
    {
        using var command = connection.CreateCommand(text, parameters);
        command.ExecuteAndWriteResultToConsole();
    }
}

Output:输出:

Executing command... INSERT INTO sch_brand_payment_data_lake_proxy.supplier_balances (accounting_document, accounting_document_type, company_code, document_date_year) SEL
ECT * FROM unnest(@accounting_document_texts, @accounting_document_types, @company_codes, @document_date_years) RETURNING id;
Col0 = id
Col0
28
29
30
Executing command... SELECT * FROM sch_brand_payment_data_lake_proxy.supplier_balances WHERE (accounting_document, accounting_document_type, company_code, document_date_y
ear) IN (SELECT * FROM  unnest(@accounting_document_texts, @accounting_document_types, @company_codes, @document_date_years))
Col0 = id
Col1 = accounting_document
Col2 = accounting_document_type
Col3 = company_code
Col4 = document_date_year
Col5 = accounting_doc_created_by_user
Col6 = accounting_clerk
Col7 = assignment_reference
Col8 = document_reference_id
Col9 = original_reference_document
Col10 = payment_terms
Col11 = supplier
Col12 = supplier_name
Col13 = document_date
Col14 = posting_date
Col15 = net_due_date
Col16 = created_on
Col17 = modified_on
Col18 = pushed_on
Col19 = is_modified
Col20 = is_pushed
Col0    Col1    Col2    Col3    Col4    Col5    Col6    Col7    Col8    Col9    Col10   Col11   Col12   Col13   Col14   Col15   Col16   Col17   Col18   Col19   Col20
28      G       Y       Z       1                                                                                                                       False   False
29      G       Y       Z       2                                                                                                                       False   False
30      G       Y       Z       3                                                                                                                       False   False

[EDIT 1] [编辑 1]

Since @Charlieface pointed out that this isn't the appropriate answer, I figured it would be better to get the answer / information right from the npgsql maintainers / contributors.由于@Charlieface 指出这不是合适的答案,我认为最好从 npgsql 维护者/贡献者那里获得答案/信息。

Hence filing an issue on their GitHub repository: https://github.com/npgsql/npgsql/issues/4437因此在他们的 GitHub 存储库中提交了一个问题: https ://github.com/npgsql/npgsql/issues/4437


Original answer:原答案:

As of today, there is no way to pass among other things tuples or collections as composite "types" or via positional-slash-implicit "definitions", (which could be then used in a collection that would have been passed to the parameter value property), npgslq requires prior PostgreSQL type definitions (but still tuples and nested collections can't work out cause not regarded as safe enough by maintainers or at least one of them).到今天为止,除了其他东西之外,没有办法将元组或集合作为复合“类型”或通过位置斜杠隐式“定义”(然后可以在已经传递给参数值的集合中使用)属性),npgslq 需要先前的 PostgreSQL 类型定义(但元组和嵌套集合仍然无法解决,因为维护者或至少其中一个认为不够安全)。 https://github.com/npgsql/npgsql/issues/2154 https://github.com/npgsql/npgsql/issues/2154

As the exception says the corresponding composite is required in the database.正如例外所说,数据库中需要相应的组合。 This is because anonymous types are not mapped to records.这是因为匿名类型没有映射到记录。

So, you should create a type and a struct which must be mapped to the type.因此,您应该创建一个类型和一个必须映射到该类型的结构。

FYI, there is a similar issue #2097 to track mapping composites to value tuples.仅供参考,有一个类似的问题#2097来跟踪映射组合到值元组。

But this would require a few other related devs for npgsql like #2097 which has been dropped the author / main contributed deemed as too brittle in https://github.com/dotnet/efcore/issues/14661#issuecomment-462440199但这需要 npgsql 的其他一些相关开发人员,例如#2097 ,作者/主要贡献在https://github.com/dotnet/efcore/issues/14661#issuecomment-462440199中被认为过于脆弱

Note that after discussion in npgsql/npgsql#2097 we decided to drop this idea.请注意,在npgsql/npgsql#2097中讨论之后,我们决定放弃这个想法。 C# value tuples don't have names, so any mapping to PostgreSQL composites would rely on field definition ordering, which seems quite dangerous/brittle. C# 值元组没有名称,因此任何到 PostgreSQL 组合的映射都将依赖于字段定义排序,这似乎非常危险/脆弱。

I've finally decided to settle for the jsonb alternative, not a huge fan, but at least it allows to pass collections in a relatively secured way (as long as the serialization to pass the jsonb is under control).我终于决定接受 jsonb 替代方案,不是一个超级粉丝,但至少它允许以相对安全的方式传递集合(只要传递 jsonb 的序列化在控制之下)。

But the initial way I envisioned doing is not something that can be done as of today.但是我最初设想的做法不是今天可以做到的。


Also one thing I've learnt along the way investigating while writing that post:在写这篇文章的过程中,我还学到了一件事:

  • There is a pretty good Slack server dedicated to PostgreSQL: postgresteam.slack.com有一个非常好的专用于 PostgreSQL 的 Slack 服务器:postgresteam.slack.com
  • A pretty nice guide about how to properly format SQL when asking for PostgreSQL-related help (albeit opinionated with the author opinions): https://www.depesz.com/2010/05/28/what-mistakes-you-can-avoid-when-looking-for-help-on-irc/关于在寻求 PostgreSQL 相关帮助时如何正确格式化 SQL 的一个很好的指南(尽管对作者的意见有意见): https ://www.depesz.com/2010/05/28/what-mistakes-you-can- 在 irc 上寻找帮助时避免/
  • A paste bin automatically formatting SQL following the author preferences: https://paste.depesz.com根据作者偏好自动格式化 SQL 的粘贴箱: https ://paste.depesz.com

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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