LINQ with group_concat - 在一个字段中获取链接的表结果

Posted

技术标签:

【中文标题】LINQ with group_concat - 在一个字段中获取链接的表结果【英文标题】:LINQ with group_concat - get linked tabe results in one field 【发布时间】:2020-09-23 00:24:13 【问题描述】:

早上好,

我有以下需要转移到 LINQ 的 SQLite 查询:

"SELECT hostname, macaddress, uuid, group_concat(collection.collection_name) AS collection, daterequested, daterealized, requester, action_name AS action, done_name as done, comment FROM computerimport 
                    LEFT OUTER JOIN action ON action.action_id = computerimport.action_id
                    LEFT OUTER JOIN done ON done.done_id = computerimport.done_id
                    LEFT OUTER JOIN computercollection ON computerimport.computer_id = computercollection.computer_id
                    LEFT OUTER JOIN collection ON computercollection.collection_id = collection.collection_id
                    GROUP BY computerimport.computer_id
                    ORDER BY daterequested;"

如您所见,我有一些 LEFT OUTER JOIN,并且还使用 group_concat 将一张表中的所有内容放在一个字段中,这样当一个设备存在于计算机集合中两次时,我只能得到一条记录。 我的 LINQ 查询如下所示;

await (from ci in _context.ComputerImports
                      join re in _context.Results on ci.result_id equals re.result_id
                      join ac in _context.Actions on ci.action_id equals ac.action_id
                      join cc in _context.ComputerCollections on ci.computer_id equals cc.computer_id into gjcc
                         from subcc in gjcc.DefaultIfEmpty()
                      join co in _context.Collections on subcc.collection_id equals co.collection_id into gjco
                         from subco in gjco.DefaultIfEmpty()
                      select new OverviewViewModel
                                
                                    computer_id = ci.computer_id,
                                    hostname = ci.hostname, 
                                    uuid = ci.uuid,
                                    collection_id = subcc.collection_id,
                                    collection_name = subco.collection_displayname,
                                    daterequested = ci.daterequested,
                                    daterealized = ci.daterealized,
                                    action_id = ci.action_id,
                                    action_name = ac.action_name,
                                    result_id = ci.result_id, 
                                    result_name = re.result_name,
                                    comment = ci.comment
                                ).OrderByDescending(e => e.daterequested).ToListAsync();

但我也尝试了使用字符串连接的 group by:

await (from t in (from ci in _context.ComputerImports
                      join re in _context.Results on ci.result_id equals re.result_id
                      join ac in _context.Actions on ci.action_id equals ac.action_id
                      join cc in _context.ComputerCollections on ci.computer_id equals cc.computer_id into gjcc
                         from subcc in gjcc.DefaultIfEmpty()
                      join co in _context.Collections on subcc.collection_id equals co.collection_id into gjco
                         from subco in gjco.DefaultIfEmpty()
                      select new ci.computer_id, ci.hostname, ci.uuid, subcc.collection_id, subco.collection_displayname, ci.daterequested, ci.daterealized, ci.action_id, ac.action_name, ci.result_id, re.result_name, ci.comment)
                    group t by new  t.computer_id, t.hostname, t.uuid, t.daterequested, t.daterealized, t.action_id, t.action_name, t.result_id, t.result_name, t.comment  into dg
                         select new OverviewViewModel
                                
                                    computer_id = dg.Key.computer_id,
                                    hostname = dg.Key.hostname, 
                                    uuid = dg.Key.uuid,
                                    collection_id = dg.Key.result_id,
                                    collection_name = String.Join(",", dg.Select(d => d.collection_displayname).Distinct()),
                                    daterequested = dg.Key.daterequested,
                                    daterealized = dg.Key.daterealized,
                                    action_id = dg.Key.action_id,
                                    action_name = dg.Key.action_name,
                                    result_id = dg.Key.result_id, 
                                    result_name = dg.Key.result_name,
                                    comment = dg.Key.comment
                                ).OrderByDescending(e => e.daterequested).ToListAsync();

到目前为止还没有运气。有没有办法让这个查询更容易?目前,与 SQL 语言相比,它感觉不是很容易和可读。


更新: 在带有 MSSQL 的 Powershell 中,这似乎也很容易:

$query = "SELECT hostname, uuid, STRING_AGG (TRIM(collection.collection_displayname), ' - ') as collection, daterequested, daterealized, requester, action_name AS action, result_name as result, comment 
                    FROM computerimport 
                    LEFT OUTER JOIN action ON action.action_id = computerimport.action_id
                    LEFT OUTER JOIN result ON result.result_id = computerimport.result_id
                    LEFT OUTER JOIN computercollection ON computerimport.computer_id = computercollection.computer_id
                    LEFT OUTER JOIN collection ON computercollection.collection_id = collection.collection_id
                    GROUP BY computerimport.computer_id, computerimport.hostname, computerimport.uuid, computerimport.daterequested, computerimport.requester, computerimport.daterealized, action_name, result_name, computerimport.comment
                    ORDER BY daterequested"

更新:

我接受了我的工作查询并尝试从中创建一个新查询:

var result = ovm.GroupBy(cc => new cc.computer_id, cc.hostname, cc.uuid, cc.daterequested, cc.daterealized, cc.action_id, cc.action_name, cc.result_id, cc.result_name, cc.comment)
                            .Select(dd => new  omputer_id = dd.Key.computer_id,
                                    hostname = dd.Key.hostname, 
                                    uuid = dd.Key.uuid,
                                    collection_name = String.Join(" - ", dd.Select(d => d.collection_name).Distinct()),
                                    daterequested = dd.Key.daterequested,
                                    daterealized = dd.Key.daterealized,
                                    action_id = dd.Key.action_id,
                                    action_name = dd.Key.action_name,
                                    result_id = dd.Key.result_id, 
                                    result_name = dd.Key.result_name,
                                    comment = dd.Key.comment);

不幸的结果是: Select(d => d.collection_name)' 无法翻译。以可翻译的形式重写查询,或通过插入对 AsEnumerable()、AsAsyncEnumerable()、ToList() 或 ToListAsync() 的调用显式切换到客户端评估。请参阅https://go.microsoft.com/fwlink/?linkid=2101038 了解更多信息。”


更新: 按照建议,我正在尝试使用此处建议的 SQL 语句:How to execute SqlQuery with Entity Framework Core 2.1?

所以我用我需要的字段创建了一个新类,将它添加到模型构建器中:

public DbSet<OverviewQuery> OverviewQueries  get; set; 

modelBuilder.Entity<OverviewQuery>().ToView(nameof(OverviewQueries)).HasNoKey();

但是当我尝试运行连接表时总是会收到错误:

InvalidOperationException: The underlying reader doesn't have as many fields as expected.

当我直接运行它时,我没有问题:

           string sqlquery = @"SELECT computerimport.action_id FROM computerimport LEFT OUTER JOIN action ON action.action_id = computerimport.action_id" ; 
_context.OverviewQueries.FromSqlRaw(sqlquery).ToList();

更新:

好的,模型必须具有您想要传递的完全相同的值,不能有更多:

string sqlquery = "SELECT computerimport.*, action.action_name, STRING_AGG(TRIM(collection.collection_name), ' - ') AS collection_name, STRING_AGG(TRIM(collection.collection_displayname), ' - ') AS collection_displayname, result.result_name " +
                            "FROM computerimport " + 
                            "LEFT OUTER JOIN action ON action.action_id = computerimport.action_id " +
                            "LEFT OUTER JOIN result ON result.result_id = computerimport.result_id " +
                            "LEFT OUTER JOIN computercollection ON computerimport.computer_id = computercollection.computer_id " +
                            "LEFT OUTER JOIN collection ON computercollection.collection_id = collection.collection_id " +
                            "GROUP BY computerimport.computer_id, computerimport.hostname, computerimport.uuid, computerimport.daterequested, computerimport.requester, computerimport.daterealized, computerimport.action_id, action_name, computerimport.result_id, result_name, computerimport.comment";


            var ovmraw = await _context.OverviewQueries.FromSqlRaw(sqlquery).ToListAsync();

概览查询

公共 int computer_id 获取;设置;

public string hostname get; set;
public string uuid get; set;
public Nullable<DateTime> daterequested get; set;  
public Nullable<DateTime> daterealized get; set;
public string requester get; set;
public int action_id get; set;
public int result_id get; set;
public string comment get; set;

// Values from other models
public string action_name get; set;
//public int? collection_id  get; set; 
public string collection_name get; set;
public string collection_displayname get; set;
public string result_name get; set;

【问题讨论】:

LINQ 不是 SQL 的替代品。不要这样使用它。它是一种在 ORM 之上使用的语言。从关系而不是 LINQ 创建连接是 ORM 的工作。而且由于 LINQ 不是 SQL,它没有特定于供应商的 SQL 扩展,例如 mysql 的 GROUP_CONCAT 或 SQL Server 的 STRING_AGG 您为 Powershell 编写的不是 Powershell,它只是一个 SQL 查询。但是 LINQ 不是 SQL。 ORM 通常用于报告您发布的查询。 MinMax 等聚合 LINQ 函数只能让你走这么远。创建视图并将您的实体映射到该视图会更好,而且通常更快。 我明白了。创建视图是什么意思?每个表都有一个模型,我有一个视图,我想在其中显示来自该模型的组合信息,除了连接问题外,它也可以工作。我现在尝试了 FromSqlRaw 但这只能让我触摸一张桌子。 【参考方案1】:

正如 Panagiotis Kanavos 所说,我不得不/我切换到 SQL 并让查询正常工作。你也必须小心你使用的模型。它必须具有与您的查询一样的确切属性。否则它将失败并显示错误消息:

InvalidOperationException: The underlying reader doesn't have as many fields as expected.

【讨论】:

以上是关于LINQ with group_concat - 在一个字段中获取链接的表结果的主要内容,如果未能解决你的问题,请参考以下文章

mysql self join with group_concat 并且没有重复

java Linq GroupBy with Having

LINQ-Entities Group By With Range 变量查询

使用 LINQ 将多行连接成单行(CSV 属性)

SQL Where in to Linq with DataTable

csharp 来自http://stackoverflow.com/questions/2714639/weighted-average-with-linq