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

发布于 2024-11-04 03:33:25 字数 485 浏览 0 评论 0原文

想想本地化的文本,存储在此表中:

表文本

  • TextId
  • 语言

现在我想为 TextId 1 选择一个文本。如果“丹麦语”中没有此 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子句的其余部分,这意味着我正在重复自己(在这个例子中还不错,但可能有更多子句)。

有没有更简单的方法来做到这一点?

Think localized texts, stored in this table:

Table Texts

  • TextId
  • Language
  • Value

Now I want to select a text for TextId 1. If there is not a text for this TextId in "Danish", I want to fall back to "English".

I could do like this:

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");
}

...But i have to repeat the rest of the Where clause, which means I am repeating myself (not so bad in this example, but could be many more clauses).

Is there a simpler way to do this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

居里长安 2024-11-11 03:33:25

我建议使用过滤器模式,在其中针对 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);

然后将其放入单个扩展中来完成重用方法:

[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);

正如人们所指出的,如果您使用多种备份语言,有更好的选择方法而不是像原来那样一次检查一种语言问题,但是这个过滤器模式可以干净地工作,这只是为您的用例和策略定义正确的过滤器的问题。

I'd suggest using a Filter pattern, where you write Extension methods against IEnumerable. That way the bulk of your logic is encapsulated into methods you can use again and again:

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);
        }
    }

Your code then becomes:

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

The repetition then becomes a non-issue. I'd also suggest making yourself a static class to hold the various language values to avoid the hard-coding:

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

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

Your code then becomes:

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

You can combine this with the DefaultIfEmpty method which gives you:

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

Then finish off by putting this into a single Extension method for re-use:

[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);
}

You can now simply call:

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

Note that this can be used as the final step of any query so something like this would also work:

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

As people have pointed out, if you go to multiple backup languages, there are better approaches than checking one language at a time like in the original question, but this Filter pattern will work cleanly, it's just a matter of defining the right filters for your use case and strategy.

中二柚 2024-11-11 03:33:25

一种解决方案是检索所需 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);
}

One solution is to retrieve all the texts of the required id and then join on a language preference mapping:

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();

If you only have two languages then this can be done even more simply and avoid returning any unnecessary rows:

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();

If you want to support a dynamic number of languages, you can build your priority expression dynamically (using System.Linq.Expressions). This results in a single database call that will return only the one record you want:

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);
}
无需解释 2024-11-11 03:33:25

如果您主要关心的是避免重复您的 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")

If your primary concern is avoiding repeating your where clause then the simplest way is to split your where clause up, something like this:

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")

If you can guarantee that you will only have at most one matching entry (which, for a localization table, would probably be true) you can simplify this even further (and wrap the whole thing in a function for easier reuse):

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")
明月松间行 2024-11-11 03:33:25

就像我在上面的评论中所说的那样,它并不完全漂亮,但它应该可以工作:)

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();
    }
}

Like I said in my comment above, its not exactly pretty but it should work :)

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();
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文