2011-04-30 10 views
8

Piense textos localizados, almacenados en esta tabla:LINQ - seleccionarlas si es que existen, si no caiga de nuevo a estos

Tabla Textos

  • TextID
  • Idioma
  • Valor

Ahora quiero seleccionar un texto para TextId 1. Si hay no es un texto para este TextId en "Danish", quiero volver a "English".

que podía hacer así:

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

... pero tengo que repetir el resto de la cláusula WHERE, lo que significa que estoy repitiendo (no es tan malo en este ejemplo, pero podría haber muchas más cláusulas).

¿Hay una manera más simple de hacer esto?

+0

Su bonito, pero podrías hacer una función que decida si tiene que ordenar el resultado ascendente o descendente por medio de x.Language, de modo que cuando hagas FirstOrDefault() tengas el idioma deseado si existe o el inglés (la consulta sería Donde (x.Language == "Danés" || x.Language == "Inglés")). – LueTm

+0

Hacer un pedido es una mala idea: confía en la coincidencia de que el danés está antes que el alfabeto en inglés ... ¿Qué sucede si quieres ofrecer en danés, inglés y francés? – RichardW1001

+0

Por eso dije "una función que decide si tiene que ordenar el resultado de forma ascendente o descendente". Realmente puede usar str.CompareTo – LueTm

Respuesta

8

me gustaría sugerir el uso de un patrón de filtro, donde escribes métodos de extensión contra IEnumerable. De esta manera la mayor parte de su lógica se encapsula en métodos que puede utilizar una y otra vez:

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

Su código se convierte entonces en:

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

La repetición se convierte en un no-tema.También me sugieren hacer usted mismo una clase estática para mantener los diversos valores de idioma para evitar el disco-codificación:

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

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

Su código se convierte entonces en:

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

Esto se puede combinar con el método DefaultIfEmpty que le ofrece:

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

Puede acabar poniendo esto en un método de extensión única para su reutilización:

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

ahora se puede llamar simplemente:

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

Tenga en cuenta que esto puede ser usado como el paso final de cualquier consulta así que algo como esto también funcionaría:

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

Como ya se ha señalado Si va a múltiples idiomas de copia de seguridad, hay mejores enfoques que consultar un idioma a la vez como en la pregunta original, pero este patrón de filtro funcionará limpiamente, es solo cuestión de definir los filtros correctos para su caso de uso y estrategia. .

+1

El problema potencial con este enfoque es la gran cantidad de viajes de ida y vuelta a la base de datos para recuperar un solo registro. Imagínese si tuviera 100s de idiomas y tuviera que hacer una consulta DB para cada uno para encontrar el primer respaldo disponible. –

+0

Esto no hará una gran cantidad de viajes de ida y vuelta, hará como máximo 2 - 1 para danés, 1 para inglés, solo si no hay resultado en danés.Si quisiera devolver resultados para varios idiomas, estructuraría la cláusula where de forma diferente, pero eso no es lo que esta pregunta está haciendo. La pregunta solo menciona un único retroceso. La pregunta, para mí, trata de evitar la repetición y la claridad del código. – RichardW1001

+0

Me gusta esto (también lo voté). Creo que C# emitirá el [System.Runtime.CompilerServices.Extension] por sí solo. Cheers – Berryl

0

Si su principal preocupación es evitar la repetición de su cláusula where entonces la forma más sencilla es la de dividir su cláusula where arriba, algo como esto:

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

Si usted puede garantizar que sólo tendrá como máximo una coincidencia entrada (que, para una tabla de localización, sería probablemente cierto) se puede simplificar aún más este (y envolver todo el asunto en una función para su reutilización más fácil):

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

¿No cree que el? el operador hará el trabajo aquí, siempre obtendrá un objeto devuelto de la consulta, nunca será nulo, pero no siempre tendrá resultados, de ahí el if (! texts.Any()) que verifica si hay hay algún resultado – RichardW1001

+0

¿Por qué la restricción para el segundo enfoque? Solo use First en lugar de Single (y FirstOrDefault en lugar de SingleOrDefault). – Yandros

4

Una solución es recuperar todos los textos de la identificación requerida y luego unirse en una asignación de preferencia de idioma:

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

Si sólo tiene dos idiomas, entonces esto se puede hacer aún más sencilla y evitar devolver cualquier filas innecesarias:

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

Si quieres apoyar una serie dinámica de idiomas, usted puede construir su expresión prioridad dinámicamente (usando System.Linq.Expressions). Esto se traduce en una sola llamada de base de datos que devolverá sólo el registro que desea:

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

+1 - Buena solución para múltiples retrocesos; -1 - no ayuda con la pregunta original de reducir la complejidad al agregar más y más cláusulas where. – RichardW1001

+0

@ RichardW1001: mi nuevo enfoque maneja con elegancia varios idiomas generando dinámicamente los condicionales anidados para cualquier cantidad de idiomas. –

0

Como dije en mi comentario anterior, no es exactamente bastante pero debería funcionar :)

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