将 T-SQL string_agg 转换为 LINQ C#

Posted

技术标签:

【中文标题】将 T-SQL string_agg 转换为 LINQ C#【英文标题】:Converting T-SQL string_agg to LINQ C# 【发布时间】:2020-01-31 15:41:37 【问题描述】:

我正在尝试将我的 T-SQL 查询转换为 LINQ。

我的查询:

SELECT l.Id, s.SystemSerial, v.LicenseVersion, l.CreatedDate, STRING_AGG (sf.[Name], ',') as Features
FROM [system] AS s
LEFT OUTER JOIN SoftwareLicense AS l ON l.SystemId = s.id
LEFT OUTER JOIN SoftwareVersion as v ON l.SoftwareVersionId = v.Id
LEFT OUTER JOIN FeatureLicense as fl ON fl.SoftwareLicenseId = l.Id AND fl.IsActive = 1
LEFT OUTER JOIN SoftwareFeature as sf ON sf.Id = fl.SoftwareFeatureId
GROUP BY l.id, s.SystemSerial, v.LicenseVersion, l.CreatedDate

上面的查询返回以下内容:

267     DELL-H99DHM2        1.0     2019-05-06T13:19:59.3081543     Advanced,Internal
270     DESKTOP-SLL5NLC     1.0     2019-05-06T19:22:19.5161704     Standard,Video
271     DESKTOP-T67FIK1     1.0     2019-05-06T19:30:50.6251582     Advanced,Internal,Video
272     DESKTOP-T67FIK1     1.1     2019-05-07T11:30:50.2351512     Advanced

我的原始 LINQ 查询(在我添加 STRING_AGG 之前)如下所示:

var allSystemsAndLicenses = (from s in _context.Systems
                            join sl in _context.SoftwareLicenses on s.Id equals sl.SystemId into sll
                            from sl2 in sll.DefaultIfEmpty()
                            join sv in _context.SoftwareVersions on sl2.SoftwareVersionId equals sv.Id into svv
                            from sv2 in svv.DefaultIfEmpty()
                            join fl in _context.FeatureLicenses on sl2.Id equals fl.SoftwareLicenseId into fll
                            from fl2 in fll.DefaultIfEmpty().Where(a => a.IsActive)
                            join sf in _context.SoftwareFeatures on fl2.SoftwareFeatureId equals sf.Id into sff
                            from sf2 in sff.DefaultIfEmpty()
                            select new SystemLicenseResult
                            
                                LicenseId = sl2.Id,
                                SerialNumber = s.SystemSerial,
                                LicenseVersion = sv2.LicenseVersion + " (" + sv2.Software.Name + ")",
                                LicenseExpiryDate = sl2.CreatedDate,
                                CreatedDate = sl2.CreatedDate
                            );

我试图弄清楚如何在我的 C# 代码中将 STRING_AGG (sf.[Name], ',') as Features 表示为 LINQ。我有一种感觉,我需要使用 linq 的 GroupBy 功能还是在选择中进行某种选择?

感谢任何帮助。

【问题讨论】:

可能有一种方法可以做到这一点,但就我个人而言,我会将功能作为对象模型中的列表返回,并在实际需要时处理制作逗号分隔的列表(这可能是视图模型问题,而不是数据问题)。 @Neil 你猜猜它在性能方面的表现如何?此查询当前返回约 1000 行。要将其缩减到我实际拥有的约 200 个系统,我需要在构建新模型时遍历从数据库中获得的结果集? 【参考方案1】:

我想我明白了!我的代码如下:

var allSystemsAndLicenses = (from s in _context.Systems
    join sl in _context.SoftwareLicenses on s.Id equals sl.SystemId into sll
    from sl2 in sll.DefaultIfEmpty()
    join sv in _context.SoftwareVersions on sl2.SoftwareVersionId equals sv.Id into svv
    from sv2 in svv.DefaultIfEmpty()
    join fl in _context.FeatureLicenses on sl2.Id equals fl.SoftwareLicenseId into fll
    from fl2 in fll.DefaultIfEmpty().Where(a => a.IsActive)
    join sf in _context.SoftwareFeatures on fl2.SoftwareFeatureId equals sf.Id into sff
    from sf2 in sff.DefaultIfEmpty()
    select new SystemLicenseResult
    
        LicenseId = sl2.Id,
        SerialNumber = s.SystemSerial,
        LicenseVersion = sv2.LicenseVersion + " (" + sv2.Software.Name + ")",
        LicenseExpiryDate = sl2.CreatedDate,
        LicenseFeatures = sf2.Name,
        CreatedDate = sl2.CreatedDate
    );

// I have some predicates defined that I am not putting here, but they do exist.
var filteredResults = allSystemsAndLicenses.Where(predicates);

var groupedResult = filteredResults.GroupBy(a => a.LicenseId);
var result = groupedResult.ToList()
    // Because the ToList(), this select projection is not done in the DB
    .Select(eg => new SystemLicenseResult
        
            LicenseId = eg.Key,
            SerialNumber = eg.First().SerialNumber,
            LicenseFeatures = string.Join(",", eg.Select(i => i.LicenseFeatures))
        )

这似乎返回与 T-SQL 语句相同的结果视图!

【讨论】:

【参考方案2】:

请注意,这实际上并没有转换为 SQL STRING_AGG 语法。 EF 拉取整个列表,然后将这些字符串聚合到内存中。

这意味着,如果您的分组项目中有大量其他字段,并且有许多需要连接的子字符串,那么所有“分组”字段将针对每个要连接的字符串重复。 IE。您的SerialNumberLicenseVersion 等将根据LicenseFeature 重复(检查生成的 sql)。

这可能意味着大量额外的“重复”数据正在您的数据库服务器和 EF 客户端之间移动。

如果这可能是一个问题(场景包括几个分组字段,每个字段都有很多子字符串),请考虑将查询拆分为 2 个单独的查询并在内存中手动连接。

在每个分组行只有几个字符串的情况下,只有 1 次往返会更快。

【讨论】:

以上是关于将 T-SQL string_agg 转换为 LINQ C#的主要内容,如果未能解决你的问题,请参考以下文章

T-SQL STRING_AGG 问题不知道是写得不好还是不工作

支持greenplum string_agg 转换成hivesql

将 T-SQL 转换为 MySQL

将 T-SQL 交叉应用转换为 Oracle

将 T-SQL 查询转换为 Postgres

T-SQL -- 将逗号分隔的列转换为多列