2008-09-17 44 views
6

tôi có như sau (khá chuẩn) cấu trúc bảng:Cách tải Nhiều truy vấn LINQ?

Post <-> PostTag <-> Tag 

Giả sử tôi có các hồ sơ sau:

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

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

PostID TagID 
1  1 
1  2 
2  2 

Nói cách khác, các bài viết đầu tiên có hai thẻ, thứ hai có một và cái thứ ba không có.

Tôi muốn tải tất cả các bài đăng và thẻ của nó trong một truy vấn nhưng không thể tìm thấy kết hợp điều khiển phù hợp. Tôi đã có thể tải bài đăng chỉ có thẻ hoặc bài đăng lặp lại khi có nhiều thẻ.

Cho cơ sở dữ liệu ở trên, Tôi muốn nhận ba bài đăng và thẻ của chúng (nếu có) trong thuộc tính bộ sưu tập của đối tượng Bài đăng. Có thể ở tất cả?

Cảm ơn

Trả lời

0

Tôi đã trả lời câu hỏi này ở một bài đăng khác: About eager loading. Trong trường hợp của bạn thì có lẽ nó sẽ là một cái gì đó như:

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

Mặc dù cẩn thận - các DataLoadOptions phải được thiết lập trước khi bất kỳ truy vấn được gửi đến cơ sở dữ liệu - nếu không, một ngoại lệ được ném (không biết tại sao nó là như thế này trong Linq2Sql - có thể sẽ được sửa trong phiên bản sau).

0

Tôi xin lỗi, Eager Loading sẽ thực thi thêm một truy vấn cho mỗi thẻ cho mỗi bài đăng.

Thử nghiệm với mã này:

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

Trong cơ sở dữ liệu ví dụ nó sẽ thực hiện 4 truy vấn mà không phải là chấp nhận được trong sản xuất. Bất cứ ai có thể đề xuất một giải pháp khác?

Cảm ơn

1

Đó là một chút lạ vì

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

kết quả trong một truy vấn như:

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] 

(Tôi đã rút ngắn tên và bổ sung * trên chọn).

Vì vậy, có vẻ như bạn đã chọn OK.

1

Tôi rất tiếc. Các giải pháp bạn cung cấp cho các công trình, nhưng tôi phát hiện ra rằng nó phá vỡ khi paginating với Take (N). Phương pháp hoàn chỉnh mà tôi đang sử dụng là:

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

Phương thức Take() nhận xét nó tạo truy vấn tương tự như những gì bạn đã đăng nhưng nếu tôi thêm lại Take() nó tạo ra 1 + N x Truy vấn M.

Vì vậy, tôi đoán câu hỏi của tôi bây giờ là: Có thay thế phương thức Take() để phân trang các bản ghi không?

Cảm ơn

2

Yay! Nó đã làm việc.

Nếu bất cứ ai đang có cùng một vấn đề ở đây là những gì tôi đã làm:

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

này thực hiện chỉ có hai truy vấn và tải tất cả các thẻ cho mỗi bài đăng.

Ý tưởng là lấy một số giá trị để giới hạn truy vấn ở bài đăng cuối cùng mà chúng ta cần (trong trường hợp này cột PublishDateGmt sẽ đủ) và sau đó giới hạn truy vấn thứ hai với giá trị đó thay vì Take().

Cảm ơn sự giúp đỡ của bạn sirrocco.

0

Tôi biết đây là một bài đăng cũ, nhưng tôi đã phát hiện ra cách sử dụng Take() trong khi chỉ thực hiện một truy vấn. Bí quyết là thực hiện phần Take() bên trong một truy vấn lồng nhau.

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

Sử dụng DataLoadOptions với truy vấn ở trên sẽ cung cấp cho bạn 10 bài đăng đầu tiên, bao gồm thẻ được liên kết, tất cả trong một truy vấn. SQL kết quả sẽ là phiên bản ngắn gọn hơn nhiều của những điều sau đây:

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)