2008-09-17 17 views
6

tengo el siguiente (bastante estándar) estructura de la tabla:Cómo cargar consultas Múltiples a muchas LINQ?

Post <-> PostTag <-> Tag 

Supongamos que tengo los siguientes registros:

PostID Title 
1,  'Foo' 
2,  'Bar' 
3,  'Baz' 

TagID Name 
1, 'Foo' 
2, 'Bar' 

PostID TagID 
1  1 
1  2 
2  2 

En otras palabras, el primer mensaje tiene dos etiquetas, el segundo tiene uno y el tercero no tiene ninguno.

Me gustaría cargar todas las publicaciones y sus etiquetas en una consulta pero no he podido encontrar la combinación correcta de operadores. He podido cargar publicaciones con etiquetas solo o publicaciones repetidas cuando hay más de una etiqueta.

Dada la base de datos anterior, Me gustaría recibir tres publicaciones y sus etiquetas (si corresponde) en una propiedad de colección de los objetos Post. ¿Es posible?

Gracias

Respuesta

0

he respondido a esto en otro post: About eager loading. En su caso probablemente sería algo así como:

DataLoadOptions options = new DataLoadOptions();  
options.LoadWith<Post>(p => p.PostTag); 
options.LoadWith<PostTag>(pt => pt.Tag); 

Aunque tenga cuidado - los DataLoadOptions deben establecer antes de cualquier consulta se envía a la base de datos - si no, se produce una excepción (ni idea de por qué es así en Linq2Sql - probablemente será reparado en una versión posterior).

0

Lo siento, Eager Loading ejecutará una consulta adicional por etiqueta por publicación.

probado con este código:

var options = new DataLoadOptions(); 
options.LoadWith<Post>(p => p.PostTags); 
options.LoadWith<PostTag>(pt => pt.Tag); 
using (var db = new BlogDataContext()) 
{ 
    db.LoadOptions = options; 
    return (from p in db.Posts 
      where p.Status != PostStatus.Closed 
      orderby p.PublishDateGmt descending 
      select p); 
} 

En la base de datos de ejemplo se ejecutaría 4 consultas que no es aceptable en la producción. ¿Alguien puede sugerir otra solución?

Gracias

1

Es un poco extraño porque

DataLoadOptions o = new DataLoadOptions (); 
o.LoadWith<Listing> (l => l.ListingStaffs); 
o.LoadWith<ListingStaff> (ls => ls.MerchantStaff); 
ctx.LoadOptions = o; 

IQueryable<Listing> listings = (from a in ctx.Listings 
      where a.IsActive == false 
          select a); 
List<Listing> list = listings.ToList (); 

resultados de una consulta como:

SELECT [t0].*, [t1].*, [t2].*, (
SELECT COUNT(*) 
FROM [dbo].[LStaff] AS [t3] 
INNER JOIN [dbo].[MStaff] AS [t4] ON [t4].[MStaffId] = [t3].[MStaffId] 
WHERE [t3].[ListingId] = [t0].[ListingId] 
) AS [value] 
FROM [dbo].[Listing] AS [t0] 
LEFT OUTER JOIN ([dbo].[LStaff] AS [t1] 
INNER JOIN [dbo].[MStaff] AS [t2] ON [t2].[MStaffId] = [t1].[MStaffId]) ON 
[t1].[LId] = [t0].[LId] WHERE NOT ([t0].[IsActive] = 1) 
ORDER BY [t0].[LId], [t1].[LStaffId], [t2].[MStaffId] 

(he acortan los nombres y añadieron al * en el selecto).

Por lo que parece hacer el seleccionar bien.

1

Lo siento. La solución que das funciona, pero descubrí que se rompe al paginar con Take (N). El método completo que estoy usando es el siguiente:

public IList<Post> GetPosts(int page, int records) 
{ 
    var options = new DataLoadOptions(); 
    options.LoadWith<Post>(p => p.PostTags); 
    options.LoadWith<PostTag>(pt => pt.Tag); 
    using (var db = new BlogDataContext()) 
    { 
     db.LoadOptions = options; 
     return (from p in db.Posts 
       where p.Status != PostStatus.Closed 
       orderby p.PublishDateGmt descending 
       select p) 
       .Skip(page * records) 
       //.Take(records) 
       .ToList(); 
    } 
} 

Con el Take() método comentó que genera una consulta similar a lo que usted envió, pero si añado The Take() de nuevo se genera 1 + N x M consultas.

Entonces, supongo que mi pregunta ahora es: ¿Existe un reemplazo del método Take() para paginar registros?

Gracias

2

Yay! Funcionó.

Si alguien está teniendo el mismo problema aquí es lo que hice:

public IList<Post> GetPosts(int page, int record) 
{ 
    var options = new DataLoadOptions(); 
    options.LoadWith<Post>(p => p.PostTags); 
    options.LoadWith<PostTag>(pt => pt.Tag); 
    using (var db = new DatabaseDataContext(m_connectionString)) 
    { 
     var publishDateGmt = (from p in db.Posts 
           where p.Status != PostStatus.Hidden 
           orderby p.PublishDateGmt descending 
           select p.PublishDateGmt) 
           .Skip(page * record) 
           .Take(record) 
           .ToList() 
           .Last(); 
     db.LoadOptions = options; 
     return (from p in db.Posts 
       where p.Status != PostStatus.Closed 
        && p.PublishDateGmt >= publishDateGmt 
       orderby p.PublishDateGmt descending 
       select p) 
       .Skip(page * record) 
       .ToList(); 
    } 
} 

Esto ejecuta sólo dos consultas y cargas de todas las etiquetas para cada puesto.

La idea es obtener algún valor para limitar la consulta en la última publicación que necesitamos (en este caso, la columna PublishDateGmt será suficiente) y luego limitar la segunda consulta con ese valor en lugar de Take().

Gracias por su ayuda sirrocco.

0

Sé que esta es una publicación anterior, pero he descubierto una forma de utilizar Take() al realizar solo una consulta. El truco es realizar el Take() dentro de una consulta anidada.

var q = from p in db.Posts 
     where db.Posts.Take(10).Contains(p) 
     select p; 

Usando DataLoadOptions con la consulta anterior le dará los primeros diez puestos, incluyendo sus etiquetas asociadas, todo en una sola consulta. El SQL resultante será una versión mucho menos concisa de lo siguiente:

SELECT p.PostID, p.Title, pt.PostID, pt.TagID, t.TagID, t.Name FROM Posts p 
JOIN PostsTags pt ON p.PostID = pt.PostID 
JOIN Tags t ON pt.TagID = t.TagID 
WHERE p.PostID IN (SELECT TOP 10 PostID FROM Posts) 
Cuestiones relacionadas