Thực thể không thể được xây dựng trong truy vấn LINQ to Entities


389

Có một loại thực thể được gọi là sản phẩm được tạo bởi khung thực thể. Tôi đã viết truy vấn này

public IQueryable<Product> GetProducts(int categoryID)
{
    return from p in db.Products
           where p.CategoryID== categoryID
           select new Product { Name = p.Name};
}

Mã dưới đây đưa ra lỗi sau:

"Cửa hàng thực thể hoặc loại phức tạp. Sản phẩm không thể được xây dựng trong truy vấn LINQ to Thực thể"

var products = productRepository.GetProducts(1).Tolist();

Nhưng khi tôi sử dụng select pthay vì select new Product { Name = p.Name};nó hoạt động chính xác.

Làm thế nào tôi có thể tạo khuôn trước một phần chọn tùy chỉnh?


System.NotSupportedException: 'Loại thực thể hoặc loại phức tạp' StudentInfoAjax.Models.Student 'không thể được xây dựng trong truy vấn LINQ to Entities.'
Md Wahid

Câu trả lời:


390

Bạn không thể (và không thể) chiếu lên một thực thể được ánh xạ. Tuy nhiên, bạn có thể chiếu lên một loại ẩn danh hoặc lên DTO :

public class ProductDTO
{
    public string Name { get; set; }
    // Other field you may need from the Product entity
}

Và phương pháp của bạn sẽ trả về Danh sách của DTO.

public List<ProductDTO> GetProducts(int categoryID)
{
    return (from p in db.Products
            where p.CategoryID == categoryID
            select new ProductDTO { Name = p.Name }).ToList();
}

152
Tôi không hiểu tại sao tôi không thể làm điều này ... Điều này sẽ rất hữu ích ...
Jonx

118
Vâng, các thực thể được ánh xạ trong EF về cơ bản đại diện cho các bảng cơ sở dữ liệu. Nếu bạn chiếu lên một thực thể được ánh xạ, về cơ bản những gì bạn làm là tải một phần thực thể, không phải là trạng thái hợp lệ. EF sẽ không có bất kỳ manh mối nào về cách xử lý một bản cập nhật của một thực thể như vậy trong tương lai (hành vi mặc định có thể sẽ ghi đè lên các trường không được tải bằng null hoặc bất cứ thứ gì bạn có trong đối tượng của mình). Đây sẽ là một hoạt động nguy hiểm, vì bạn sẽ có nguy cơ mất một số dữ liệu của mình trong DB, do đó không được phép tải một phần các thực thể (hoặc dự án lên các thực thể được ánh xạ) trong EF.
Yakimych

26
@Yakimych có ý nghĩa ngoại trừ nếu bạn có một số thực thể tổng hợp mà bạn đang tạo / tạo thông qua truy vấn và do đó nhận thức đầy đủ / có ý định tạo một thực thể hoàn toàn mới mà sau đó bạn sẽ thao tác và sau đó thêm. Trong trường hợp này, bạn phải buộc chạy truy vấn hoặc đẩy vào một dto và quay lại một thực thể để thêm - điều này gây bực bội
Cargowire

16
@Cargowire - Tôi đồng ý, kịch bản đó tồn tại và thật bực bội khi bạn biết bạn đang làm gì nhưng không được phép làm điều đó do những hạn chế. Tuy nhiên, nếu điều này được cho phép, sẽ có rất nhiều nhà phát triển nản lòng phàn nàn về việc dữ liệu của họ bị mất khi ví dụ như cố gắng lưu các thực thể được tải một phần. IMO, một lỗi phát ra nhiều tiếng ồn (ném ngoại lệ, v.v.) tốt hơn hành vi có thể gây ra các lỗi ẩn khó theo dõi và giải thích (mọi thứ hoạt động tốt trước khi bạn bắt đầu nhận thấy dữ liệu bị thiếu).
Yakimych


275

Bạn có thể chiếu thành kiểu ẩn danh, rồi từ kiểu này sang kiểu mô hình

public IEnumerable<Product> GetProducts(int categoryID)
{
    return (from p in Context.Set<Product>()
            where p.CategoryID == categoryID
            select new { Name = p.Name }).ToList()
           .Select(x => new Product { Name = x.Name });
}

Chỉnh sửa : Tôi sẽ cụ thể hơn một chút vì câu hỏi này đã được chú ý rất nhiều.

Bạn không thể chiếu trực tiếp vào loại mô hình (hạn chế EF), vì vậy không có cách nào khác. Cách duy nhất là chiếu vào loại ẩn danh (lần lặp thứ 1), và sau đó đến kiểu mô hình (lần lặp thứ 2).

Cũng xin lưu ý rằng khi bạn tải một phần các thực thể theo cách này, chúng không thể được cập nhật, vì vậy chúng vẫn được tách ra, như chúng vẫn vậy.

Tôi chưa bao giờ hoàn toàn hiểu lý do tại sao điều này là không thể, và câu trả lời trên chủ đề này không đưa ra lý do mạnh mẽ chống lại nó (chủ yếu nói về dữ liệu được tải một phần). Đúng là trong thực thể trạng thái được tải một phần không thể được cập nhật, nhưng sau đó, thực thể này sẽ bị tách ra, do đó, những nỗ lực vô tình để cứu chúng sẽ không thể thực hiện được.

Hãy xem xét phương pháp tôi đã sử dụng ở trên: kết quả là chúng ta vẫn có một thực thể mô hình được tải một phần. Thực thể này được tách ra.

Xem xét mã này có thể (muốn tồn tại):

return (from p in Context.Set<Product>()
        where p.CategoryID == categoryID
        select new Product { Name = p.Name }).AsNoTracking().ToList();

Điều này cũng có thể dẫn đến một danh sách các thực thể tách rời, vì vậy chúng ta không cần phải thực hiện hai lần lặp. Một trình biên dịch sẽ là thông minh khi thấy rằng AsNoTracking () đã được sử dụng, điều này sẽ dẫn đến các thực thể tách rời, vì vậy nó có thể cho phép chúng ta làm điều này. Tuy nhiên, nếu AsNoTracking () bị bỏ qua, nó có thể ném ngoại lệ giống như hiện tại, để cảnh báo chúng ta rằng chúng ta cần phải đủ cụ thể về kết quả mà chúng ta muốn.


3
Đây là giải pháp sạch nhất khi bạn không cần / không quan tâm đến trạng thái của thực thể được chọn mà bạn muốn chiếu.
Mário Meyrelles

2
Và khi bạn không quan tâm nếu bạn trả lại IEnumerable hoặc IQueryable;). Nhưng bạn vẫn nhận được upvote của tôi vì giải pháp này làm việc cho tôi bây giờ.
Michael Brennt

10
về mặt kỹ thuật, việc chiếu đến loại mô hình đang xảy ra bên ngoài truy vấn và tôi tin rằng cũng yêu cầu một lần lặp bổ sung thông qua danh sách. Tôi sẽ không sử dụng giải pháp này cho mã của mình, nhưng nó là giải pháp cho câu hỏi. tiếp thu.
1c1cle

4
Tôi thích giải pháp DTO được chấp nhận này - thanh lịch và sạch sẽ hơn nhiều
Adam Hey

7
Ngoại trừ điều đó, với sự tôn trọng, nó không thực sự là một câu trả lời cho câu hỏi. Đây là một câu trả lời về cách thực hiện phép chiếu Linq To Object, không phải là phép chiếu truy vấn Linq to Entities. Vì vậy, tùy chọn DTO là tùy chọn duy nhất re: Linq to Entities.
nghĩa

78

Có một cách khác mà tôi đã tìm thấy hoạt động, bạn phải xây dựng một lớp xuất phát từ lớp Sản phẩm của bạn và sử dụng nó. Ví dụ:

public class PseudoProduct : Product { }

public IQueryable<Product> GetProducts(int categoryID)
{
    return from p in db.Products
           where p.CategoryID== categoryID
           select new PseudoProduct() { Name = p.Name};
}

Không chắc chắn nếu điều này là "được phép", nhưng nó hoạt động.


3
Tài giỏi! Đã thử điều này bây giờ và nó hoạt động. Tôi chắc chắn rằng bằng cách nào đó nó sẽ đốt cháy tôi.
Daniel

5
BTW này sẽ cắn bạn nếu bạn cố gắng duy trì kết quả của Get Products () vì EF không thể tìm thấy ánh xạ cho Pseudo SẢNt, ví dụ: "System.InvalidOperationException: Không thể tìm thấy thông tin siêu dữ liệu cho EntityType 'blah.Pseudo SẢNt'".
sming

4
Câu trả lời hay nhất và là câu trả lời duy nhất trong các tham số của câu hỏi. Tất cả các câu trả lời khác thay đổi kiểu trả về hoặc sớm thực hiện IQueryable và sử dụng LINQ to Objects
rdans

2
100% sốc khi nó hoạt động ... trong EF 6.1, nó đang hoạt động.
TravisWhidden

2
@mejobloggs Hãy thử thuộc tính [NotMapped] trên lớp dẫn xuất hoặc .Ignore <T> nếu bạn đang sử dụng API thông thạo.
Dunc

37

Đây là một cách để làm điều này mà không cần khai báo lớp quảng cáo:

public List<Product> GetProducts(int categoryID)
{
    var query = from p in db.Products
            where p.CategoryID == categoryID
            select new { Name = p.Name };
    var products = query.ToList().Select(r => new Product
    {
        Name = r.Name;
    }).ToList();

    return products;
}

Tuy nhiên, điều này chỉ được sử dụng nếu bạn muốn kết hợp nhiều thực thể trong một thực thể. Các chức năng trên (sản phẩm đơn giản để ánh xạ sản phẩm) được thực hiện như sau:

public List<Product> GetProducts(int categoryID)
{
    var query = from p in db.Products
            where p.CategoryID == categoryID
            select p;
    var products = query.ToList();

    return products;
}

23

Một cách đơn giản khác :)

public IQueryable<Product> GetProducts(int categoryID)
{
    var productList = db.Products
        .Where(p => p.CategoryID == categoryID)
        .Select(item => 
            new Product
            {
                Name = item.Name
            })
        .ToList()
        .AsQueryable(); // actually it's not useful after "ToList()" :D

    return productList;
}

điểm tốt Tôi vừa học được điều gì đó với IQueryable với câu trả lời hay của bạn. Mặc dù vậy, thật tuyệt nếu bạn giải thích TẠI SAO nó không hữu ích sau ToList () và lý do là bạn không thể sử dụng danh sách chung trong truy vấn LINQ-to-SQL. Vì vậy, nếu bạn biết rằng bạn sẽ luôn đẩy kết quả vào một truy vấn khác bởi người gọi thì chắc chắn có ý nghĩa là IQueryable. Nhưng nếu không ... nếu bạn sẽ sử dụng nó làm danh sách chung sau đó, thì hãy sử dụng ToList () bên trong phương thức để bạn không thực hiện ToList () trên IQueryable mỗi và mỗi lệnh gọi phương thức này.
Tích cực Mua

Bạn hoàn toàn ổn, bạn của tôi. Tôi chỉ bắt chước chữ ký phương thức câu hỏi, vì điều đó tôi chuyển đổi nó thành Truy vấn có thể ...;)
Soren

1
Điều này hoạt động, ProductList trở nên không thể chỉnh sửa sau ToList (). Làm thế nào tôi có thể làm cho nó có thể chỉnh sửa?
doncadavona

Nếu bạn đặt .ToListtruy vấn, nó được thực thi và lấy dữ liệu từ máy chủ thì điểm cần làm lại là AsQueryablegì?
Moshii

1
@Moshii chỉ để đáp ứng chữ ký kiểu trả về của phương thức, (như tôi đã nói trong câu trả lời, nó không còn hữu ích nữa).
Soren

4

Bạn có thể sử dụng cái này và nó sẽ hoạt động -> Bạn phải sử dụng toListtrước khi tạo danh sách mới bằng cách chọn:

db.Products
    .where(x=>x.CategoryID == categoryID).ToList()
    .select(x=>new Product { Name = p.Name}).ToList(); 

3
Tuy nhiên, điều này vẫn sẽ thực hiện 'CHỌN * TỪ [..]', chứ không phải 'CHỌN tên TỪ [..]'
Timo Hermans

1

Để trả lời cho câu hỏi khác được đánh dấu là trùng lặp ( xem tại đây ) tôi đã tìm ra một giải pháp nhanh chóng và dễ dàng dựa trên câu trả lời của Soren:

data.Tasks.AddRange(
    data.Task.AsEnumerable().Select(t => new Task{
        creator_id   = t.ID,
        start_date   = t.Incident.DateOpened,
        end_date     = t.Incident.DateCLosed,
        product_code = t.Incident.ProductCode
        // so on...
    })
);
data.SaveChanges();

Lưu ý: Giải pháp này chỉ hoạt động nếu bạn có thuộc tính điều hướng (khóa ngoại) trên lớp Nhiệm vụ (ở đây được gọi là 'Sự cố'). Nếu bạn không có điều đó, bạn chỉ có thể sử dụng một trong những giải pháp được đăng khác với "AsQueryable ()".


1

Bạn có thể giải quyết điều này bằng cách sử dụng Đối tượng truyền dữ liệu (DTO's).

Đây là một chút giống như chế độ xem trong đó bạn đặt các thuộc tính bạn cần và bạn có thể ánh xạ chúng theo cách thủ công trong bộ điều khiển của mình hoặc bằng cách sử dụng các giải pháp của bên thứ ba như AutoMapper.

Với DTO, bạn có thể:

  • Tạo dữ liệu nối tiếp (Json)
  • Loại bỏ các tham chiếu tròn
  • Giảm hệ số mạng bằng cách để lại các thuộc tính bạn không cần (viewmodelwise)
  • Sử dụng đối tượng

Tôi đã học điều này ở trường năm nay và nó là một công cụ rất hữu ích.


0

Nếu bạn đang sử dụng khung Entity, thì hãy thử xóa thuộc tính khỏi DbContext, sử dụng mô hình phức tạp của bạn làm Thực thể tôi gặp vấn đề tương tự khi ánh xạ nhiều mô hình vào chế độ xem có tên là Entity

public DbSet<Entity> Entities { get; set; }

Xóa mục nhập khỏi DbContext đã sửa lỗi của tôi.


0

nếu bạn đang thực thi, Linq to Entitybạn không thể sử dụng ClassTypevới newtrong việc selectđóng truy vấnonly anonymous types are allowed (new without type)

hãy xem đoạn trích này trong dự án của tôi

//...
var dbQuery = context.Set<Letter>()
                .Include(letter => letter.LetterStatus)
                .Select(l => new {Title =l.Title,ID = l.ID, LastModificationDate = l.LastModificationDate, DateCreated = l.DateCreated,LetterStatus = new {ID = l.LetterStatusID.Value,NameInArabic = l.LetterStatus.NameInArabic,NameInEnglish = l.LetterStatus.NameInEnglish} })
                               ^^ without type__________________________________________________________________________________________________________^^ without type

trong số bạn đã thêm vào phần new keywordđóng cửa Chọn ngay cả trên complex propertiesbạn sẽ gặp lỗi này

nên removecác ClassTypes from newtừ khóa trên Linq to Entitycác truy vấn ,,

bởi vì nó sẽ chuyển đổi thành câu lệnh sql và được thực thi trên SqlServer

Vậy khi nào tôi có thể sử dụng new with typeskhi selectđóng cửa?

bạn có thể sử dụng nó nếu bạn đang làm việc với LINQ to Object (in memory collection)

//opecations in tempList , LINQ to Entities; so we can not use class types in select only anonymous types are allowed
var tempList = dbQuery.Skip(10).Take(10).ToList();// this is list of <anonymous type> so we have to convert it so list of <letter>

//opecations in list , LINQ to Object; so we can use class types in select
list = tempList.Select(l => new Letter{ Title = l.Title, ID = l.ID, LastModificationDate = l.LastModificationDate, DateCreated = l.DateCreated, LetterStatus = new LetterStatus{ ID = l.LetterStatus.ID, NameInArabic = l.LetterStatus.NameInArabic, NameInEnglish = l.LetterStatus.NameInEnglish } }).ToList();
                                ^^^^^^ with type 

Sau khi tôi thực hiện ToListtruy vấn, nó trở thành in memory collection để chúng ta có thể sử dụng new ClassTypestrong select


Chắc chắn bạn có thể sử dụng các loại ẩn danh, nhưng bạn không thể tạo một thực thể trong truy vấn LINQ, thậm chí để đặt một thành viên ẩn danh, vì LINQ-to-Entities vẫn ném ngoại lệ tương tự.
Suncat2000

0

Trong nhiều trường hợp, sự chuyển đổi là không cần thiết. Hãy suy nghĩ về lý do bạn muốn Danh sách loại mạnh và đánh giá xem bạn chỉ muốn dữ liệu, ví dụ, trong một dịch vụ web hoặc để hiển thị nó. Nó không quan trọng các loại. Bạn chỉ cần biết cách đọc nó và kiểm tra nó giống hệt với các thuộc tính được xác định trong loại ẩn danh mà bạn đã xác định. Đó là kịch bản tối ưu, gây ra thứ gì đó bạn không cần tất cả các trường của một thực thể và đó là lý do loại ẩn danh tồn tại.

Một cách đơn giản là làm điều này:

IEnumerable<object> list = dataContext.Table.Select(e => new { MyRequiredField = e.MyRequiredField}).AsEnumerable();

0

Nó sẽ không cho phép bạn ánh xạ lại vào Sản phẩm vì đó là bảng bạn đang truy vấn. Bạn cần một hàm ẩn danh, sau đó bạn có thể thêm nó vào ViewModel và thêm từng ViewModel vào a List<MyViewModel>và trả lại chúng. Đó là một sự lạc quan nhẹ, nhưng tôi bao gồm những lời cảnh báo về việc xử lý những ngày vô giá trị bởi vì đây là một nỗi đau ở phía sau để giải quyết, chỉ trong trường hợp bạn có bất kỳ. Đây là cách tôi xử lý nó.

Hy vọng bạn có một ProductViewModel:

public class ProductViewModel
{
    [Key]
    public string ID { get; set; }
    public string Name { get; set; }
}

Tôi có khung tiêm / kho lưu trữ phụ thuộc nơi tôi gọi một hàm để lấy dữ liệu của mình. Sử dụng bài đăng của bạn làm ví dụ, trong lệnh gọi hàm Trình điều khiển của bạn, nó sẽ trông như thế này:

int categoryID = 1;
var prods = repository.GetProducts(categoryID);

Trong lớp kho lưu trữ:

public IEnumerable<ProductViewModel> GetProducts(int categoryID)
{
   List<ProductViewModel> lstPVM = new List<ProductViewModel>();

   var anonymousObjResult = from p in db.Products
                            where p.CategoryID == categoryID 
                            select new
                            {
                                CatID = p.CategoryID,
                                Name = p.Name
                            };

        // NOTE: If you have any dates that are nullable and null, you'll need to
        // take care of that:  ClosedDate = (DateTime?)p.ClosedDate ?? DateTime.Now

        // If you want a particular date, you have to define a DateTime variable,
        // assign your value to it, then replace DateTime.Now with that variable. You
        // cannot call a DateTime.Parse there, unfortunately. 
        // Using 
        //    new Date("1","1","1800"); 
        // works, though. (I add a particular date so I can edit it out later.)

        // I do this foreach below so I can return a List<ProductViewModel>. 
        // You could do: return anonymousObjResult.ToList(); here
        // but it's not as clean and is an anonymous type instead of defined
        // by a ViewModel where you can control the individual field types

        foreach (var a in anonymousObjResult)
        {                
            ProductViewModel pvm = new ProductViewModel();
            pvm.ID = a.CatID;  
            pvm.Name = a.Name;
            lstPVM.Add(rvm);
        }

        // Obviously you will just have ONE item there, but I built it 
        // like this so you could bring back the whole table, if you wanted
        // to remove your Where clause, above.

        return lstPVM;
    }

Quay lại bộ điều khiển, bạn làm:

 List<ProductViewModel> lstProd = new List<ProductViewModel>();

 if (prods != null) 
 {
    // For setting the dates back to nulls, I'm looking for this value:
    // DateTime stdDate = DateTime.Parse("01/01/1800");

    foreach (var a in prods)
    {
        ProductViewModel o_prod = new ReportViewModel();
        o_prod.ID = a.ID;
        o_prod.Name = a.Name;
       // o_prod.ClosedDate = a.ClosedDate == stdDate ? null : a.ClosedDate;
        lstProd.Add(o_prod);
    }
}
return View(lstProd);  // use this in your View as:   @model IEnumerable<ProductViewModel>

-1

chỉ thêm AsEnumerable ():

public IQueryable<Product> GetProducts(int categoryID)
{
    return from p in db.Products.AsEnumerable()
           where p.CategoryID== categoryID
           select new Product { Name = p.Name};
}

8
Không bao giờ làm điều đó! Điều này sẽ lấy tất cả dữ liệu từ DB và sau đó sẽ thực hiện chọn.
Gh61

1
Đây là lý do tại sao ở một số công ty Linq bị cấm sử dụng.
hakan

-2

bạn có thể thêm AsEnumerable vào bộ sưu tập của mình như sau:

public IQueryable<Product> GetProducts(int categoryID)
{
    return from p in db.Products.AsEnumerable()
           where p.CategoryID== categoryID
           select new Product { Name = p.Name};
}

Tại sao đây là một câu trả lời tồi mặc dù nó hoạt động ... .Những kết thúc linq cho các thực thể. Mệnh đề Where và mọi thứ khác được xử lý bên ngoài linq cho Thực thể. tức là mọi sản phẩm được lấy ra sau đó được lọc bởi linq đến các đối tượng. Ngoài ra, nó khá giống với câu trả lời .ToList ở trên. stackoverflow.com/questions/5311034/
Mạnh

1
Vấn đề với điều này chỉ là một lựa chọn * từ ... được thực hiện, không chọn Sản phẩm mới {Name = p.Name}, vì bạn cũng sẽ nhận được một tài liệu tham khảo theo chu kỳ. Và bạn chỉ muốn Tên.
Sterling Diaz
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.