none
Entity Framework filtrar traducciones desde bbdd RRS feed

  • Pregunta

  • Hola. Estoy modificando un proyecto NET Core, que no tiene repositorio genérico, y tira de una base de datos en SQL Server y otra en PostgreSQL.

    En las dos ddbb hay muchas tablas y cada tabla tiene su propia tabla de traducciones (relacionada con una tabla maestra de idiomas) para localizar los términos en la base de datos.

    Campos de la Tabla de idiomas:

    [Id] [nvarchar](2) NOT NULL,
    [LanguageKey] [nvarchar](2) NOT NULL,
    [ISO639-2T] [nvarchar](3) NULL,
    [ISO639-2B] [nvarchar](20) NULL
    en postgre es similar.

    Tabla ejemplo traducción:

    [Id] [int] IDENTITY(1,1) NOT NULL,
    [FkCCAA] [int] NOT NULL,  -- FK to related table
    [FkTranslation] [nvarchar](2) NOT NULL, -- FK to language table
    [Name] [nvarchar](300) NOT NULL

    Tanto para las búsquedas como para recuperar los nombres, debo intentar mirar primero por el idioma que me pasan, si no se encuentra por uno por defecto que también me pasan y si no el término que se encuentre en algún idioma.

    Por ahora hago algo de este estilo, pero me parece poco óptimo:

    result = await _context.TableX.Where(x => (x.TableXTranslations.Where(t => t.FkTranslation == lang && !string.IsNullOrWhiteSpace(t.Name)).FirstOrDefault().Name ?? x.TableXTranslations.Where(t => t.FkTranslation == defLang && !string.IsNullOrWhiteSpace(t.Name)).FirstOrDefault().Name ?? x.TableXTranslations.Where(t => !string.IsNullOrWhiteSpace(t.Name)).FirstOrDefault().Name).ToLower().Contains(name.ToLower()))
                                        .Select(x => new ItemDto
                                        {
                                            Id = x.Id.ToString(),
                                            Name = x.TableX.Where(t => t.FkTranslation == lang && !string.IsNullOrWhiteSpace(t.Name)).FirstOrDefault().Name ?? x.TableX.Where(t => t.FkTranslation == defLang && !string.IsNullOrWhiteSpace(t.Name)).FirstOrDefault().Name ?? x.TableX.Where(t => !string.IsNullOrWhiteSpace(t.Name)).FirstOrDefault().Name
                                        }).AsNoTracking().AsQueryable().ToListAsync();

    Además según la documentación de NET Core 3 esta consulta debería traducirse por JOINS pero en lugar de eso se traduce por subconsultas, aún menos eficientes.

    Estoy intentando montar un consulta genérica que pueda usar desde el Where y el Select de cada consulta de cada tabla, que son bastantes, pero no puedo usar repositorios genéricos, y a ser posible no usar tampoco procedimientos almacenados

    ¿¿A alguien se le ocurre cómo podría hacerlo??

    Gracias de antemano. Saludos, Héctor.

     
    lunes, 27 de enero de 2020 16:13

Todas las respuestas

  • hola

    >>Además según la documentación de NET Core 3 esta consulta debería traducirse por JOINS pero en lugar de eso se traduce por subconsultas, aún menos eficientes.

    donde ves los joins ?

    alli veo where dentro de otros where, eso no son ningun join, son subconsultas

    la verdad es que trate pero no entendi para nada esa consultas que estas queriendo armar, deberias poder partirla en al menos dos consulta separadas y puede que ayude si usas la notacion linq en lugar de los metodos de extension

    No veo porque aplicas los filtros sobre el TableXTranslations a nivel de filtro y despues haces lo mismo dentro del select, no tiene sentido

    saludos


    Leandro Tuttini

    Blog
    MVP Profile
    Buenos Aires
    Argentina

    lunes, 27 de enero de 2020 17:23
  • Hola Leandro.

    >>donde ves los joins ?

    Según la documentación de NET Core 3 (NET Core 3 Includes) los includes (como deberían ser mis tablas translation -son includes implícitos-) se traducen a SQL como JOINS:

    fragmento del documento anteriormente referenciado

    >> alli veo where dentro de otros where, eso no son ningun join, son subconsultas

    Lo sé y por eso digo que no son muy eficientes. De momento para algunas tablas con pocos registros me valen, pero para otras con más registros y que van a crecer mucho me terminarán dando problemas de rendimiento.

    >> no entendi para nada esa consultas que estas queriendo armar ... No veo porque aplicas los filtros sobre el TableXTranslations a nivel de filtro y despues haces lo mismo dentro del select, no tiene sentido

    La consulta, que quizás no lo expliqué bien, pretende encontrar un término entre sus traducciones (que están en las tablkas de traducciones), y traerse el que encuentre entre el idioma solicitado, el idioma por defecto solicitado y si no encuentra nada de los idiomas solicitados traerse algo si hay algún término en otro idioma que cuadre con lo buscado.

    Y todo ello en una única consulta (lo interesante es delegar esta acción en bbdd para luego no tener que operarlo en el código del programa, ya que creo que no es una operación compleja y que es de las que bbdd resuelve con rapidez).

    En concreto la consulta en SQL quedaría más o menos así:

    SELECT TOP(1) X.*, COALESCE(Xtransl1.Name, Xtransl2.Name, Xtransl3.Name) Name
      FROM [TablaX] X
      LEFT JOIN [TablaX_TRANSLATION] Xtransl1
    	ON X.Id = Xtransl1.FkTablaX AND Xtransl1.FkTranslation = 'es' AND ISNULL(Xtransl1.Name, '') <> ''
      LEFT JOIN [TablaX_TRANSLATION] Xtransl2
    	ON X.Id = Xtransl2.FkTablaX AND Xtransl2.FkTranslation = 'en' AND ISNULL(Xtransl2.Name, '') <> ''
      LEFT JOIN [TablaX_TRANSLATION] Xtransl3
    	ON X.Id = Xtransl3.FkTablaX AND ISNULL(Xtransl3.Name, '') <> ''
      WHERE 
    	Xtransl1.Name like '%alm%' 
    	OR
    	Xtransl2.Name like '%alm%'
    	OR
    	Xtransl3.Name like '%alm%'

    Donde 'es' es el idioma solicitado, 'en' el idioma por defecto y 'alm' el término que hay que encontrar en los nombres traducidos de los registros.

    Espero haberme explicado mejor y haber aclarado el sentido de la consulta.

    A ver si alguien me puede echar una mano sobre cómo pasarla a Expresiones Lambda (que es otro requisito del proyecto).

    Creo que sería tan sencillo como que los includes permitiesen filtrar con un where, pero hasta donde sé no se puede hacer...

    Gracias nuevamente y saludos, Héctor.

    martes, 28 de enero de 2020 8:01
  • hola

    >>los includes (como deberían ser mis tablas translation -son includes implícitos-) se traducen a SQL como JOINS

    Pero tu no estas usando ningun Include(), este es un metodo

    Loading Related Data

    >>A ver si alguien me puede echar una mano sobre cómo pasarla a Expresiones Lambda (que es otro requisito del proyecto).

    podrias ser algo como esto

    var query = from t in _context.TableX
    			let trans = t.TableXTranslations.FirstOrDefault(x=> x.FkTranslation. == lang)?.Name
    			let transDef = t.TableXTranslations.FirstOrDefault(x=> x.FkTranslation. == defLang)?.Name
    			select new ItemDto()
    			{
    				Id = t.Id,
    				Name = trans != null ? trans : transDef
    			};
    
    var result = await query.AsNoTracking().AsQueryable().ToListAsync();

    igual habria que ver que tan eficiente es, pero si es mucho mas legible

    saludos


    Leandro Tuttini

    Blog
    MVP Profile
    Buenos Aires
    Argentina

    martes, 28 de enero de 2020 14:30
  • Hola Leandro, de nuevo gracias por tu ayuda.

    >> Pero tu no estas usando ningun Include(), este es un metodo

    Explícitamente no estoy utilizando el método include, pero implícitamente sí que lo debe estar utilizando porque en las propiedades de navegación de las expresiones Lambda puedo utilizar las tablas relacionadas de las traducciones y seleccionarlas o realizar consultas sobre ellas como si usase un Include(), o eso es lo que parece... que puedo estar equivocado.

    >> podrias ser algo como esto

    Mañana en el trabajo lo probaré porque algo más legible es, ya veremos de rendimiento, aunque realmente falta una línea, debería quedar así:

    var query = from t in _context.TableX
    			let trans = t.TableXTranslations.FirstOrDefault(x=> x.FkTranslation. == lang)?.Name
    			let transDef = t.TableXTranslations.FirstOrDefault(x=> x.FkTranslation. == defLang)?.Name
                            let transOther = t.TableXTranslations.FirstOrDefault()?.Name
    			select new ItemDto()
    			{
    				Id = t.Id,
    				Name = trans ?? transDef ?? transOther 
    			};
    
    var result = await query.AsNoTracking().AsQueryable().ToListAsync();

    ¿Pero no hay forma de hacerlo de otra forma para que tenga menos subconsultas y tenga algo más de eficiencia?

    ¿no hay forma de traducir el SQL que puse antes a Linq o Lambda de una "forma elegante" y eficiente?

    Saludos, Héctor.

    martes, 28 de enero de 2020 20:04
  • hola

    >>aunque realmente falta una línea, debería quedar así

    cual es el sentido de transOther, porque esta tomando cualquier cosa pero si no hay en en y es en que otro idioma puede obtenerlo?

    tiene sentido mostrar cualquier otro idioma por ejemplo si lo esta en aleman cuando pides el español? creo que deberias indicar que no existe esa traduccion para que alguien lo note y la agregue

    >>¿Pero no hay forma de hacerlo de otra forma para que tenga menos subconsultas y tenga algo más de eficiencia?

    la verdad no se me ocurre de otra forma

    en definitiva los LEFT JOIN que usas terminan siendo subconsultas, sino la ultima seria crear un procedure o view y mapear esta con EF

    saludos


    Leandro Tuttini

    Blog
    MVP Profile
    Buenos Aires
    Argentina

    miércoles, 29 de enero de 2020 0:01
  • Hola de nuevo.

    >> cual es el sentido de transOther, porque esta tomando cualquier cosa pero si no hay en en y es en que otro idioma puede obtenerlo?

    Aunque parezca mentira, en el contexto de la aplicación sí que tiene sentido el intentar recuperar el idioma que se le pida, el por defecto y si no alguno en el que se encuentre. Pero aunque no tuviese sentido es un requisito funcional, con lo que debo cumplirlo.

    >>¿Pero no hay forma de hacerlo de otra forma para que tenga menos subconsultas y tenga algo más de eficiencia?

    >> la verdad no se me ocurre de otra forma

    Pues si no hay otra forma lo cambiaré y lo dejaré como hemos comentado, aunque me fastidia porque creo que debería haber otra forma algo más eficiente, pero no se me ocurre cómo...

    He visto por ahí un paquete de nuget, que por supuesto ahora no recuerdo el nombre, que te deja hacer filtros en los includes, voy a intentar investigar por ahí a ver si saco algo en claro (caso que lo consiga lo contaré aquí).

    Gracias por la ayuda. Saludos, Héctor.

    miércoles, 29 de enero de 2020 17:04