2010-02-06 47 views
20

Tôi rất quan tâm đến LINQ to SQL với tính năng tải Lười biếng. Và trong dự án của tôi, tôi đã sử dụng AutoMapper để ánh xạ Mô hình DB sang Mô hình Miền (từ DB_RoleInfo đến DO_RoleInfo). Trong mã kho của tôi như sau:AutoMapper có hỗ trợ LINQ không?

public DO_RoleInfo SelectByKey(Guid Key) 
    { 
     return SelectAll().Where(x => x.Id == Key).SingleOrDefault(); 
    } 

    public IQueryable<DO_RoleInfo> SelectAll() 
    { 
     Mapper.CreateMap<DB_RoleInfo, DO_RoleInfo>(); 
     return from role in _ctx.DB_RoleInfo 
       select Mapper.Map<DB_RoleInfo, DO_RoleInfo>(role); 
    } 

SelectAll phương pháp được điều hành tốt, nhưng khi tôi gọi SelectByKey, tôi nhận được lỗi:

Method “RealMVC.Data.DO_RoleInfo MapDB_RoleInfo,DO_RoleInfo” could not translate to SQL.

Là nó mà Automapper không hỗ trợ LINQ hoàn toàn?

Thay vì Automapper, tôi đã cố gắng hướng dẫn lập bản đồ mã bên dưới:

public IQueryable<DO_RoleInfo> SelectAll() 
{ 
    return from role in _ctx.DB_RoleInfo 
    select new DO_RoleInfo 
    { 
     Id = role.id, 
     name = role.name, 
     code = role.code 
    }; 
} 

Phương pháp này hoạt động theo cách tôi muốn nó.

+0

Chỉ cần một ghi chú trên bản cập nhật của bạn, thêm vào của tôi Trả lời: Phiên bản thứ hai hoạt động vì LINQ to SQL đã * biết * rằng bạn đã thực hiện một phép chiếu không thể đảo ngược; 'SelectByKey' thực sự chỉ sử dụng LINQ to Objects. Nếu bạn kiểm tra truy vấn thực tế đang được tạo ra, tôi nghĩ bạn sẽ thấy rằng nó vẫn đang chọn tất cả các thực thể từ cơ sở dữ liệu, tương đương với việc sử dụng 'ToList()' và sau đó lọc danh sách kết quả. – Aaronaught

+2

Nó không phải là AutoMapper hỗ trợ LINQ. Đó là LINQ to SQL không hỗ trợ AutoMapper. Nhà cung cấp truy vấn LINQ to SQL nhìn vào cây biểu thức để xác định cách tạo truy vấn SQL. Khi nó đến phần Mapper.Map, nó không có ý tưởng làm thế nào để tạo ra các SQL. –

Trả lời

23

Thay đổi chức năng thứ hai của bạn như thế này:

public IEnumerable<DO_RoleInfo> SelectAll() 
{ 
    Mapper.CreateMap<DB_RoleInfo, DO_RoleInfo>(); 
    return from role in _ctx.DB_RoleInfo.ToList() 
      select Mapper.Map<DB_RoleInfo, DO_RoleInfo>(role); 
} 

AutoMapper chỉ hoạt động tốt với LINQ to SQL, nhưng nó không thể được thực hiện như một phần của truy vấn chậm. Thêm ToList() vào cuối truy vấn LINQ của bạn làm cho nó ngay lập tức đánh giá kết quả, thay vì cố gắng dịch phân đoạn AutoMapper như một phần của truy vấn.


Làm rõ

Khái niệm chậm thực hiện (không "lười biếng tải") không có ý nghĩa gì một khi bạn đã thay đổi loại dẫn đến một cái gì đó không phải là một thực thể dữ liệu. Hãy xem xét hai loại cổ phiếu này:

public class DB_RoleInfo 
{ 
    public int ID { get; set; } 
    public string Name { get; set; } 
} 

public class DO_RoleInfo 
{ 
    public Role Role { get; set; } // Enumeration type 
} 

Bây giờ xem xét các bản đồ sau:

Mapper.CreateMap<DB_RoleInfo, DO_RoleInfo> 
    .ForMember(dest => dest.Role, opt => opt.MapFrom(src => 
     (Role)Enum.Parse(typeof(Role), src.Name))); 

lập bản đồ này là hoàn toàn tốt (trừ khi tôi thực hiện một typo), nhưng giả sử bạn viết phương pháp SelectAll trong bài viết ban đầu của bạn thay vì sửa đổi của tôi:

public IQueryable<DO_RoleInfo> SelectAll() 
{ 
    Mapper.CreateMap<DB_RoleInfo, DO_RoleInfo>(); 
    return from role in _ctx.DB_RoleInfo 
      select Mapper.Map<DB_RoleInfo, DO_RoleInfo>(role); 
} 

Đây thực sự là loại công việc, nhưng bằng cách tự gọi mình là "có thể truy vấn", nó nằm. Điều gì xảy ra nếu tôi cố gắng viết điều này chống lại nó:

public IEnumerable<DO_RoleInfo> SelectSome() 
{ 
    return from ri in SelectAll() 
      where (ri.Role == Role.Administrator) || 
       (ri.Role == Role.Executive) 
      select ri; 
} 

Hãy suy nghĩ thật kỹ về điều này. Làm thế nào có thể LINQ to SQL có thể có thể biến thành công where của bạn thành truy vấn cơ sở dữ liệu thực tế?

LINQ không biết gì về lớp học DO_RoleInfo. Nó không biết cách lập bản đồ lạc hậu - trong một số trường hợp, thậm chí có thể không thực hiện được. Chắc chắn, bạn có thể xem mã này và truy cập "Ồ, thật dễ dàng, chỉ cần tìm kiếm 'Quản trị viên' hoặc 'Điều hành' trong cột Name", nhưng bạn là người duy nhất biết điều đó. Theo như LINQ to SQL là có liên quan, truy vấn là vô nghĩa thuần túy.

Hãy tưởng tượng rằng ai đó đã cho bạn những hướng dẫn này:

Go to the supermarket and bring back the ingredients for making Morton Thompson Turkey.

Trừ khi bạn đã thực hiện nó trước, và hầu hết mọi người đã không, câu trả lời của bạn để hướng dẫn mà rất có thể sẽ là:

  • Cái quái gì vậy?

Bạn có thể đi chợ, và bạn có thể nhận được các thành phần cụ thể theo tên, nhưng bạn không thể đánh giá điều kiện tôi đã đưa cho bạn trong khi bạn đang ở đó. Tôi phải "bỏ bản đồ" các tiêu chí trước tiên. Tôi phải nói với bạn, đây là những thành phần chúng ta cần cho công thức này - bây giờ đi và lấy chúng.


Nói tóm lại, đây không phải là một số đơn giản sự không tương thích giữa LINQ to SQL và AutoMapper. Nó không phải là duy nhất cho một trong hai thư viện đó. Nó không quan trọng cách bạn thực sự thực hiện ánh xạ tới loại không phải thực thể - bạn có thể dễ dàng thực hiện ánh xạ theo cách thủ công và bạn vẫn nhận được cùng một lỗi, bởi vì bây giờ bạn đang đưa Linq cho SQL một bộ các hướng dẫn không còn dễ hiểu, xử lý các lớp bí ẩn không có bản đồ nội tại cho bất kỳ loại thực thể cụ thể nào.

Vấn đề này là cơ bản cho khái niệm về ánh xạ O/R và thực thi truy vấn bị trì hoãn. A chiếu là một hoạt động một chiều một chiều. Khi bạn dự án, bạn không còn có thể quay trở lại công cụ truy vấn và giả sử ồ tiện đây, sau đây là một số điều kiện khác cho bạn. Đã quá muộn. Điều tốt nhất bạn có thể làm là lấy những gì nó đã cung cấp cho bạn và tự mình đánh giá các điều kiện bổ sung.


Cuối cùng nhưng không kém phần quan trọng, tôi sẽ để bạn giải quyết. Nếu chỉ điều bạn muốn để có thể làm lại từ bản đồ của bạn là lọc các hàng, bạn có thể viết này:

public IEnumerable<DO_RoleInfo> SelectRoles(Func<DB_RoleInfo, bool> selector) 
{ 
    Mapper.CreateMap<DB_RoleInfo, DO_RoleInfo>(); 
    return _ctx.DB_RoleInfo 
     .Where(selector) 
     .Select(dbr => Mapper.Map<DB_RoleInfo, DO_RoleInfo>(dbr)); 
} 

Đây là một phương pháp hữu ích để xử lý các bản đồ cho bạn và chấp nhận một bộ lọc trên số thực đơn gốckhông phải là pháp nhân được ánh xạ. Nó có thể hữu ích nếu bạn có nhiều loại bộ lọc khác nhau nhưng luôn cần phải thực hiện cùng một ánh xạ.

Cá nhân, tôi nghĩ bạn sẽ tốt hơn chỉ cần viết ra các truy vấn đúng cách, trước tiên xác định những gì bạn cần truy xuất từ cơ sở dữ liệu, sau đó thực hiện bất kỳ phép chiếu/ánh xạ nào, và cuối cùng, nếu bạn cần làm thêm lọc (bạn không nên), rồi thực hiện kết quả với ToList() hoặc ToArray() và viết nhiều điều kiện hơn so với danh sách cục bộ.

Đừng cố gắng sử dụng AutoMapper hoặc bất kỳ công cụ nào khác để ẩn các đối tượng thực được hiển thị bởi LINQ to SQL.Mô hình miền là giao diện công khai của bạn. Các truy vấn bạn viết là một khía cạnh của việc thực hiện riêng riêng của bạn. Điều quan trọng là phải hiểu sự khác biệt và duy trì sự phân tách mối quan tâm tốt.

+0

Cảm ơn câu trả lời của bạn! Nhưng theo cách bạn đưa ra, SelectAll sẽ lấy tất cả các bản ghi từ DB. Tôi muốn tận hưởng những lợi ích của tải lười biếng, Nó có nghĩa là ứng dụng sẽ chọn một bản ghi từ DB trong SelectByKey nhưng không phải tất cả các bản ghi trong SelectAll. –

+2

Câu trả lời của Gert Arnold dưới đây là giải pháp. Để tham khảo, đây là API có liên quan https://github.com/AutoMapper/AutoMapper/blob/master/src/AutoMapper/QueryableExtensions.cs –

54

Trong khi câu trả lời của @ Aaronaught là chính xác tại thời điểm viết, thường thì thế giới đã thay đổi và AutoMapper với nó. Trong thời gian đó, QueryableExtensions đã được thêm vào cơ sở mã đã thêm hỗ trợ cho các phép chiếu được dịch thành các biểu thức và cuối cùng là SQL.

Phương pháp mở rộng lõi là ProjectTo . Đây là mã của bạn trông như thế nào:

using AutoMapper.QueryableExtensions; 

public IQueryable<DO_RoleInfo> SelectAll() 
{ 
    Mapper.CreateMap<DB_RoleInfo, DO_RoleInfo>(); 
    return _ctx.DB_RoleInfo.ProjectTo<DO_RoleInfo>(); 
} 

và nó sẽ hoạt động giống như ánh xạ thủ công. (Câu lệnh CreateMap có ở đây cho mục đích minh họa. Thông thường, bạn sẽ xác định ánh xạ khi khởi động ứng dụng).

Do đó, chỉ các cột được yêu cầu cho ánh xạ được truy vấn và kết quả là IQueryable vẫn có nhà cung cấp truy vấn ban đầu (linq-to-sql, linq-to-entity, bất cứ điều gì). Vì vậy, nó vẫn còn composable và điều này sẽ chuyển thành một khoản WHERE trong SQL:.

SelectAll().Where(x => x.Id == Key).SingleOrDefault(); 

Project().To<T>() trước v 4.1.0

+2

Câu trả lời này sẽ trở thành câu trả lời mới. –

+3

Chỉ cần thêm bằng cách sử dụng AutoMapper.QueryableExtensions; – Colin

+0

Tôi nhận được lỗi sau "Phương thức 'Ở đâu' không thể thực hiện theo phương thức 'Chọn' hoặc không được hỗ trợ". Vui lòng xem câu hỏi tại đây (http://stackoverflow.com/q/16174351/1133338). –

Các vấn đề liên quan