【问题标题】:Can this query about finding missing keys be improved? (either SQL or LINQ)这个关于查找丢失键的查询可以改进吗? (SQL 或 LINQ)
【发布时间】:2016-02-13 07:19:24
【问题描述】:

我正在开发一个 ASP.NET MVC 网站,并且正在寻找一种方法来改进这个例程。它可以在 LINQ 级别或 SQL Server 级别进行改进。我希望我们最多可以在一个查询调用中完成。

以下是涉及的表格和一些示例数据:

我们没有限制每个Key 必须有每个LanguageId 值,实际上业务逻辑不允许这样的限制。但是,在应用程序级别,我们想警告管理员密钥缺少一个/某些语言值。所以我有这个类和查询:

public class LocalizationKeyWithMissingCodes
{
    public string Key { get; set; }
    public IEnumerable<string> MissingCodes { get; set; }
}

此方法获取密钥列表,以及任何缺失的代码(例如,如果我们有 en + jp + ch 语言代码,并且密钥只有 en + ch 的值,则列表将包含 jp):

    public IEnumerable<LocalizationKeyWithMissingCodes> GetAllKeysWithMissingCodes()
    {
        var languageList = Utils.ResolveDependency<ILanguageRepository>().GetActive();
        var languageIdList = languageList.Select(q => q.Id);
        var languageIdDictionary = languageList.ToDictionary(q => q.Id);

        var keyList = this.GetActive()
            .Select(q => q.Key)
            .Distinct();

        var result = new List<LocalizationKeyWithMissingCodes>();
        foreach (var key in keyList)
        {
            // Get missing codes
            var existingCodes = this.Get(q => q.Active && q.Key == key)
                .Select(q => q.LanguageId);

            // ToList to make sure it is processed at application
            var missingLangId = languageList.Where(q => !existingCodes.Contains(q.Id))
                .ToList();
            result.Add(new LocalizationKeyWithMissingCodes()
            {
                Key = key,
                MissingCodes = missingLangId
                    .Select(q => languageIdDictionary[q.Id].Code),
            });
        }

        result = result.OrderByDescending(q => q.MissingCodes.Count() > 0)
            .ThenBy(q => q.Key)
            .ToList();

        return result;
    }

我认为我当前的解决方案不好,因为它对每个键进行查询调用。有没有办法改进它,或者让它更快,或者在一个查询调用中打包?

编辑:这是答案的最终查询:

    public IQueryable<LocalizationKeyWithMissingCodes> GetAllKeysWithMissingCodes()
    {
        var languageList = Utils.ResolveDependency<ILanguageRepository>().GetActive();
        var localizationList = this.GetActive();

        return localizationList
            .GroupBy(q => q.Key, (key, items) => new LocalizationKeyWithMissingCodes()
            {
                Key = key,
                MissingCodes = languageList
                    .GroupJoin(
                        items,
                        lang => lang.Id,
                        loc => loc.LanguageId,
                        (lang, loc) => loc.Any() ? null : lang)
                    .Where(q => q != null)
                    .Select(q => q.Code)
            }).OrderByDescending(q => q.MissingCodes.Count() > 0) // Show the missing keys on the top
            .ThenBy(q => q.Key);
    }

【问题讨论】:

    标签: c# sql sql-server linq


    【解决方案1】:

    另一种可能性,使用 LINQ:

    public IEnumerable<LocalizationKeyWithMissingCodes> GetAllKeysWithMissingCodes(
        List<Language> languages,
        List<Localization> localizations)
    {
        return localizations
            .GroupBy(x => x.Key, (key, items) => new LocalizationKeyWithMissingCodes
            {
                Key = key,
                MissingCodes = languages
                    .GroupJoin( // check if there is one or more match for each language
                        items,
                        x => x.Id,
                        y => y.LanguageId,
                        (x, ys) => ys.Any() ? null : x)
                    .Where(x => x != null) // eliminate all languages with a match
                    .Select(x => x.Code) // grab the code
            })
            .Where(x => x.MissingCodes.Any()); // eliminate all complete keys 
    }
    

    【讨论】:

    • 这太棒了!我怎么也想不到。
    【解决方案2】:

    以下是用于识别缺少“完整”语言分配的键的 SQL 逻辑:

    SELECT
        all.[Key],
        all.LanguageId
    FROM
        (
        SELECT
            loc.[Key],
            lang.LanguageId
        FROM
            Language lang
        FULL OUTER JOIN
            Localization loc
            ON (1 = 1)
        WHERE
            lang.Active = 1
        ) all
    LEFT JOIN
        Localization loc
        ON (loc.[Key] = all.[Key])
        AND (loc.LanguageId = all.LanguageId)
    WHERE
        loc.[Key] IS NULL;
    

    查看所有键(而不是过滤):

    SELECT
        all.[Key],
        all.LanguageId,
        CASE WHEN loc.[Key] IS NULL THEN 1 ELSE 0 END AS Flagged
    FROM
        (
        SELECT
            loc.[Key],
            lang.LanguageId
        FROM
            Language lang
        FULL OUTER JOIN
            Localization loc
            ON (1 = 1)
        WHERE
            lang.Active = 1
        ) all
    LEFT JOIN
        Localization loc
        ON (loc.[Key] = all.[Key])
        AND (loc.LanguageId = all.LanguageId);
    

    【讨论】:

    • 获取密钥确实如此(有一个错字,它是lang.Id),但我不知道为什么我在同一个KeyLanguageId 上得到重复记录(我认为 Active = 0 记录的问题),虽然我将您的条件添加到WHERE lang.Active = 1 AND loc.Active = 1
    • 当然,我不知道您的表格逻辑的来龙去脉,但总的来说,上述内容应该可行。它适用于您的编辑吗?
    • 是的,删除重复的,它似乎工作,我会检查并尝试将其转换为 LINQ 查询并告诉你结果。另外,我的要求是返回每个键,无论它们是否缺少值。
    • 查看我的编辑 - 我根据您的评论添加了不同的版本。
    • 我仍然收到大量重复记录,但不知道如何消除它们。基本上,我在每个表中都有 Active 字段,将它们标记为已删除 (0) 或者是真实的业务记录 (1)。我尝试使用 LINQ Except 语句,它似乎运行良好。我不知道它的 SQL 等价物。
    【解决方案3】:

    您的代码似乎在进行大量的数据库查询和具体化..

    就 LINQ 而言,单个查询看起来像这样......

    我们采用语言和本地化表的笛卡尔积来获得 (key, code) 的所有组合,然后减去关系中存在的 (key, code) 元组。这给了我们不存在的(键,代码)组合。

    var result = context.Languages.Join(context.Localizations, lang => true, 
    loc => true, (lang, loc) => new { Key = loc.Key, Code = lang.Code })
    .Except(context.Languages.Join(context.Localizations, lang => lang.Id, 
    loc => loc.LanguageId, (lang, loc) => new { Key = loc.Key, Code = lang.Code }))
    .GroupBy(r => r.Key).Select(r => new LocalizationKeyWithMissingCodes 
    { 
     Key = r.Key, 
     MissingCodes = r.Select(kc => kc.Code).ToList()
    })
    .ToList()
    .OrderByDescending(lkmc => lkmc.MissingCodes.Count())
    .ThenBy(lkmc => lkmc.Key).ToList();
    

    附言我在旅途中输入了这个 LINQ 查询,所以如果它有语法问题,请告诉我.. 查询的要点是我们采用笛卡尔积并减去匹配的行。

    【讨论】:

    • 这是我第一次看到 LINQ Include。它是什么?我不能通过,因为l =&gt; l.Localization不存在,只有l.Localizations,等等下一个Select语句,我无法选择Key,因为l.Localizations是一个集合。
    • 我应该只使用第二个连接作为Except 的参数吗?
    • 是的..我更新了答案,只加入第二个结果集的语言 id..
    • @DatVM Include 是基本的 LINQ - 如果你错过了,最好去阅读其他“隐藏”功能的文档。它指示映射器预加载相关对象。选择发票包含发票行意味着读取这些行不会导致发出额外的 SQL 语句。这就是为什么它是基本的 - 没有它,您在遍历导航属性时会得到大量的小 SQL。这对于性能来说是如此基本和核心,以至于任何不了解它的人都应该考虑一下他真正了解了多少关于 LINQ 的知识。
    • @TomTom,仅作记录,Include 是 EntityFramework(或 LINQ to Entities)的基本功能,而不是 LINQ to Objects。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多