LINQ - 如果它们存在则选择它们,否则回退到这些

时间:2011-04-30 15:50:33

标签: c# linq

考虑存储在此表中的本地化文本:

表格文字

  • TEXTID
  • 语言

现在我想为TextId 1选择一个文本。如果“Danish”中没有这个TextId的文本,我想回到“英语”。

我可以这样做:

var texts = MyDb.Texts.Where(x=>x.TextId == 1 & x.Language == "Danish");

if (!texts.Any()){
    texts = MyDb.Texts.Where(x=>x.TextId == 1 & x.Language == "English");
}

...但我必须重复Where子句的其余部分,这意味着我在重复自己(在这个例子中不是那么糟糕,但可能还有更多的条款)。

有更简单的方法吗?

4 个答案:

答案 0 :(得分:9)

我建议使用Filter模式,在其中编写针对IEnumerable的扩展方法。这样,您的大部分逻辑都被封装到您可以反复使用的方法中:

public static class TextExtensions
    {
        [System.Runtime.CompilerServices.Extension]
        public static IEnumerable<Text> ByTextId(this IEnumerable<Text> qry, int textId)
        {
            return qry.Where(t => t.TextId == textId);
        }

        [System.Runtime.CompilerServices.Extension]            
        public static IEnumerable<Text> ByLanguage(this IEnumerable<Text> qry, string language)
        {
            return qry.Where(t => t.Language == language);
        }
    }

然后您的代码变为:

var texts = MyDB.Texts.ByTextId(1).ByLanguage("Danish");

然后重复变成无问题。我还建议让自己成为一个静态类来保存各种语言值以避免硬编码:

public static class LanguageValues
{
    public static string English
    {
        get
        {
            return "English";
        }
    }

    public static string Danish
    {
        get
        {
            return "Danish";
        }
    }
}

然后您的代码变为:

var texts = MyDB.Texts.ByTextId(1).ByLanguage(LanguageValues.Danish);

您可以将它与DefaultIfEmpty方法结合使用,该方法为您提供:

var texts = MyDB.Texts.DefaultIfEmpty(MyDB.Texts.ByTextId(1).ByLanguage(LanguageValues.English).Single()).ByTextId(1).ByLanguage(LanguageValues.Danish);

然后将其放入单个Extension方法中以便重复使用:

[System.Runtime.CompilerServices.Extension]
public static IEnumerable<Text> GetValueOrDefault(this IEnumerable<Text> qry, int textId, string language)
{
    return qry.DefaultIfEmpty(qry.ByTextId(textId).ByLanguage(LanguageValues.English).Single()).ByTextId(textId).ByLanguage(language);
}

您现在可以简单地致电:

var text = MyDB.Texts.GetValueOrDefault(1, LanguageValues.Danish);

请注意,这可以用作任何查询的最后一步,所以这样的事情也可以起作用:

var text = MyDB.Texts.Where(<some funky clause>).Where(<some other funky clause>).GetValueOrDefault(1,LanguageValues.Danish);

正如人们所指出的那样,如果你使用多种备份语言,有比在原始问题中一次检查一种语言更好的方法,但这种过滤模式将干净利落地工作,这只是定义权利的问题。过滤器用于您的用例和策略。

答案 1 :(得分:4)

一种解决方案是检索所需id的所有文本,然后加入语言首选项映射:

var languages = new[]
                    {
                        new {Language = "Danish", Priority = 1},
                        new {Language = "English", Priority = 2}
                    };
var id = 1;
var text = (from t in db.Texts.Where(t => t.TextId == id).AsEnumerable()
            join l in languages on t.Language equals l.Language
            orderby l.Priority
            select t).FirstOrDefault();

如果您只有两种语言,那么这可以更简单地完成,并避免返回任何不必要的行:

var id = 1;
var text = (from t in db.Texts
            let priority = t.Language == "Danish" ? 1 : 2
            where t.TextId == id
            orderby priority
            select t).FirstOrDefault();

如果要支持动态数量的语言,可以动态构建优先级表达式(使用System.Linq.Expressions)。这导致单个数据库调用只返回您想要的一个记录:

var id = 1;
var text = db.Texts.Where(t => t.TextId == id).OrderBy(CreatePriorityExpression()).FirstOrDefault();

private static Expression<Func<Text, int>> CreatePriorityExpression()
{
    var languages = new[]
                        {
                            new {Language = "Danish", Priority = 1},
                            new {Language = "English", Priority = 2}
                        };

    // Creates an expression of nested if-else statements & translates to a SQL CASE 
    var param = Expression.Parameter(typeof(Text));
    var lang = Expression.PropertyOrField(param, "Language");
    Expression ex = Expression.Constant(languages.Last().Priority);
    foreach (var l in languages.Reverse().Skip(1))
        ex = Expression.Condition(Expression.Equal(lang, Expression.Constant(l.Language)), Expression.Constant(l.Priority), ex);
    return Expression.Lambda<Func<Text, int>>(ex, param);
}

答案 2 :(得分:0)

如果你主要关心的是避免重复你的where子句,那么最简单的方法是将你的where子句分开,如下所示:

var alltexts = MyDb.Texts.Where(x => x.TextId == 1);
var text = alltexts.Any(x => x.Language == "Danish")
         ? alltexts.Where(x => x.Language == "Danish")
         : alltexts.Where(x => x.Language == "English")

如果您可以保证最多只有一个匹配的条目(对于本地化表,可能是真的),您可以进一步简化(并将整个事物包装在一个函数中以便于重用):

public Text GetLocalizedText(Func<Text, bool> predicate, string language )
{
    var temp = MyDb.Texts.Where(predicate);
    return temp.SingleOrDefault(x => x.Language == language)
        ?? temp.Single(x => x.Language == "English");
}

var caption = GetLocalizedText(x => x.TextId == 1, "Danish")

答案 3 :(得分:0)

就像我在上面的评论中所说的那样,它不完全漂亮,但应该有效:)

public string GetText(string lang, string fallback)
{
    // lang precedes fallback
    if (lang.CompareTo(fallback) < 1)
    {
        return MyDb.Texts.Where(x => x.Language == lang || x.Language == fallback).OrderBy(x => x.Language).FirstOrDefault();
    }
    else
    {
        return MyDb.Texts.Where(x => x.Language == lang || x.Language == fallback).OrderByDescending(x => x.Language).FirstOrDefault();
    }
}