Entitas tidak dapat dikonstruksikan dalam kueri LINQ ke Entitas

389

Ada jenis entitas yang disebut produk yang dihasilkan oleh kerangka kerja entitas. Saya telah menulis kueri ini

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

Kode di bawah ini melempar kesalahan berikut:

"Entitas atau tipe kompleks Shop.Produk tidak dapat dibangun dalam permintaan LINQ ke Entitas"

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

Tapi ketika saya menggunakannya select pmalah select new Product { Name = p.Name};berfungsi dengan benar.

Bagaimana saya bisa membentuk bagian pilih kustom?

Ghooti Farangi
sumber
System.NotSupportedException: 'Entitas atau tipe kompleks' StudentInfoAjax.Models.Student 'tidak dapat dibangun dalam permintaan LINQ to Entities.'
Md Wahid

Jawaban:

390

Anda tidak dapat (dan seharusnya tidak dapat) memproyeksikan ke entitas yang dipetakan. Namun, Anda dapat memproyeksikan ke jenis anonim atau ke DTO :

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

Dan metode Anda akan mengembalikan Daftar DTO.

public List<ProductDTO> GetProducts(int categoryID)
{
    return (from p in db.Products
            where p.CategoryID == categoryID
            select new ProductDTO { Name = p.Name }).ToList();
}
Yakimych
sumber
152
Saya tidak mengerti mengapa saya tidak bisa melakukan ini ... Ini akan sangat berguna ...
Jonx
118
Entitas yang dipetakan di EF pada dasarnya mewakili tabel basis data. Jika Anda memproyeksikan ke entitas yang dipetakan, apa yang pada dasarnya Anda lakukan adalah memuat sebagian entitas, yang bukan kondisi valid. EF tidak akan memiliki petunjuk bagaimana menangani pembaruan entitas seperti itu di masa mendatang (perilaku default mungkin akan menimpa bidang yang tidak dimuat dengan nol atau apa pun yang akan Anda miliki di objek Anda). Ini akan menjadi operasi yang berbahaya, karena Anda berisiko kehilangan sebagian data Anda di DB, oleh karena itu tidak diperbolehkan memuat sebagian entitas (atau memproyeksikan ke entitas yang dipetakan) di EF.
Yakimych
26
@Yakimych itu masuk akal kecuali jika Anda memiliki beberapa entitas agregat yang Anda hasilkan / ciptakan melalui kueri dan karenanya sepenuhnya sadar / berniat untuk membuat entitas baru yang kemudian akan Anda manipulasi dan tambahkan nanti. Dalam hal ini Anda harus memaksa menjalankan kueri atau mendorong ke dto dan kembali ke entitas untuk menambahkan - yang membuat frustrasi
Cargowire
16
@ Cargowire - Saya setuju, skenario itu ada, dan itu membuat frustrasi ketika Anda tahu apa yang Anda lakukan tetapi tidak diizinkan melakukannya karena keterbatasan. Namun, jika ini diizinkan, akan ada banyak pengembang frustrasi mengeluh tentang data mereka hilang ketika misalnya mencoba menyelamatkan entitas yang dimuat sebagian. IMO, kesalahan yang meledak dengan banyak suara (melemparkan pengecualian, dll) lebih baik daripada perilaku yang dapat menyebabkan bug tersembunyi yang sulit untuk dilacak dan dijelaskan (hal-hal semacam berfungsi dengan baik sebelum Anda mulai memperhatikan data yang hilang).
Yakimych
14
DTO - Objek Transfer Data
tkerwood
275

Anda dapat memproyeksikan ke tipe anonim, dan kemudian dari itu ke tipe model

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

Sunting : Saya akan sedikit lebih spesifik karena pertanyaan ini mendapat banyak perhatian.

Anda tidak dapat memproyeksikan ke tipe model secara langsung (pembatasan EF), jadi tidak ada jalan lain untuk hal ini. Satu-satunya cara adalah memproyeksikan ke tipe anonim (iterasi 1), dan kemudian ke tipe model (iterasi ke-2).

Perlu diketahui juga bahwa ketika Anda memuat sebagian entitas dengan cara ini, mereka tidak dapat diperbarui, sehingga mereka harus tetap terpisah, sebagaimana adanya.

Saya tidak pernah benar-benar mengerti mengapa ini tidak mungkin, dan jawaban pada utas ini tidak memberikan alasan kuat untuk menolaknya (kebanyakan berbicara tentang data yang dimuat sebagian). Memang benar bahwa dalam entitas negara yang dimuat sebagian tidak dapat diperbarui, tetapi kemudian, entitas ini akan terlepas, sehingga upaya yang tidak disengaja untuk menyimpannya tidak mungkin dilakukan.

Pertimbangkan metode yang saya gunakan di atas: akibatnya kami masih memiliki entitas model yang dimuat sebagian. Entitas ini terpisah.

Pertimbangkan kode yang mungkin (ingin ada) ini:

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

Ini juga bisa menghasilkan daftar entitas yang terpisah, jadi kita tidak perlu membuat dua iterasi. Kompiler akan pintar untuk melihat bahwa AsNoTracking () telah digunakan, yang akan menghasilkan entitas yang terpisah, sehingga dapat memungkinkan kita untuk melakukan ini. Namun, jika AsNoTracking () dihilangkan, itu bisa melempar pengecualian yang sama seperti melempar sekarang, untuk memperingatkan kita bahwa kita perlu cukup spesifik tentang hasil yang kita inginkan.

Goran
sumber
3
Ini adalah solusi terbersih ketika Anda tidak perlu / tidak peduli dengan keadaan entitas yang dipilih yang ingin Anda proyeksikan.
Mário Meyrelles
2
Dan ketika Anda tidak peduli jika Anda mengembalikan IEnumerable atau IQueryable;). Tapi Anda tetap mendapatkan upvote saya karena solusi ini berfungsi untuk saya sekarang.
Michael Brennt
10
secara teknis, proyeksi untuk tipe model terjadi di luar permintaan, dan saya percaya juga memerlukan iterasi tambahan melalui daftar. Saya tidak akan menggunakan solusi ini untuk kode saya, tetapi ini adalah solusi untuk pertanyaan itu. uptick.
1c1cle
4
Saya lebih suka ini daripada solusi DTO yang diterima - jauh lebih elegan dan bersih
Adam Hey
7
Kecuali itu, dengan hormat, itu sebenarnya bukan jawaban untuk pertanyaan itu. Ini adalah jawaban untuk bagaimana melakukan proyeksi Linq To Objects, bukan proyeksi query Linq to Entities. Jadi opsi DTO adalah satu-satunya opsi re: Linq to Entities.
rism
78

Ada cara lain yang saya temukan bekerja, Anda harus membangun kelas yang berasal dari kelas Produk Anda dan menggunakannya. Contohnya:

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

Tidak yakin apakah ini "diizinkan", tetapi berhasil.

Tomasz Iniewicz
sumber
3
Pintar! Mencoba ini sekarang dan berhasil. Saya yakin itu entah bagaimana akan membakar saya.
Daniel
5
BTW hal ini menggigit Anda jika Anda mencoba untuk tetap menggunakan hasil GetProducts () karena EF tidak dapat menemukan pemetaan untuk PseudoProduct misalnya "System.InvalidOperationException: Informasi pemetaan dan metadata tidak dapat ditemukan untuk EntityType 'blah.PseudoProduct'".
sming
4
Jawaban terbaik, dan satu-satunya yang menjawab dalam parameter pertanyaan. Semua jawaban lain mengubah tipe pengembalian atau menjalankan IQueryable sebelum waktunya dan menggunakan LINQ untuk objek
rdans
2
100% kaget ternyata berhasil ... di EF 6.1 ini berfungsi.
TravisWhidden
2
@mejobloggs Cobalah atribut [NotMapped] pada kelas turunan, atau .Ignore <T> jika Anda menggunakan API yang lancar.
Dunc
37

Ini adalah salah satu cara untuk melakukan ini tanpa mendeklarasikan kelas aditional:

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

Namun, ini hanya digunakan jika Anda ingin menggabungkan beberapa entitas dalam satu entitas. Fungsi di atas (pemetaan produk ke produk sederhana) dilakukan seperti ini:

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;
}
Bojan Hrnkas
sumber
23

Cara sederhana lain :)

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;
}
Soren
sumber
Poin yang baik saya baru saja belajar sesuatu yang IQueryable dengan balasan Anda yang bagus. Akan lebih baik jika Anda akan menjelaskan MENGAPA itu tidak berguna setelah ToList () dan alasannya adalah bahwa Anda tidak dapat menggunakan daftar generik dalam permintaan LINQ-to-SQL. Jadi, jika Anda tahu Anda akan selalu mendorong hasil ke permintaan lain oleh penelepon maka tentu masuk akal untuk IQueryable. Tetapi jika tidak ... jika Anda akan menggunakannya sebagai daftar generik setelah itu, maka gunakan ToList () di dalam metode sehingga Anda tidak melakukan ToList () pada IQueryable masing-masing dan setiap panggilan ke metode ini.
PositiveGuy
Anda benar-benar baik-baik saja teman saya. Saya hanya meniru tanda tangan metode pertanyaan, karena itu saya mengubahnya menjadi Query-mampu ...;)
Soren
1
Ini berfungsi, productList menjadi tidak dapat diedit setelah ToList (). Bagaimana saya bisa membuatnya diedit?
doncadavona
Jika Anda memasukkan .ToListpermintaan, itu dieksekusi dan menarik data dari server lalu apa gunanya membuatnya lagi AsQueryable?
Moshii
1
@Moshii hanya untuk memenuhi metode tanda tangan tipe pengembalian, (seperti yang saya katakan dalam jawaban, itu tidak berguna lagi).
Soren
4

Anda dapat menggunakan ini dan itu harus berfungsi -> Anda harus menggunakan toListsebelum membuat daftar baru menggunakan pilih:

db.Products
    .where(x=>x.CategoryID == categoryID).ToList()
    .select(x=>new Product { Name = p.Name}).ToList(); 
Mohamed Adam
sumber
3
Namun ini masih akan melakukan 'SELECT * FROM [..]', bukan 'SELECT name FROM [..]'
Timo Hermans
1

Menanggapi pertanyaan lain yang ditandai sebagai duplikat ( lihat di sini ) saya menemukan solusi cepat dan mudah berdasarkan jawaban 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();

Catatan: Solusi ini hanya berfungsi jika Anda memiliki properti navigasi (kunci asing) pada kelas Tugas (di sini disebut 'Insiden'). Jika Anda tidak memilikinya, Anda bisa menggunakan salah satu solusi diposting lainnya dengan "AsQueryable ()".

JollyBrackets
sumber
1

Anda bisa menyelesaikan ini dengan menggunakan Data Transfer Objects (DTO's).

Ini sedikit mirip dengan viewmodels di mana Anda memasukkan properti yang Anda butuhkan dan Anda dapat memetakannya secara manual di controller Anda atau dengan menggunakan solusi pihak ketiga seperti AutoMapper.

Dengan DTO, Anda dapat:

  • Buat data bersambung (Json)
  • Singkirkan referensi melingkar
  • Kurangi networktraffic dengan meninggalkan properti yang tidak Anda butuhkan (sesuai tampilan)
  • Gunakan objectflattening

Saya sudah belajar ini di sekolah tahun ini dan ini adalah alat yang sangat berguna.

Jelman
sumber
0

Jika Anda menggunakan kerangka kerja Entity, kemudian coba hapus properti dari DbContext yang menggunakan model kompleks Anda sebagai Entity Saya memiliki masalah yang sama ketika memetakan banyak model ke dalam viewmodel bernama Entity

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

Menghapus entri dari DbContext memperbaiki kesalahan saya.

vikas suhag
sumber
0

jika Anda Pelaksana, Linq to EntityAnda tidak dapat menggunakan ClassTypedengan newdalam selectpenutupan permintaanonly anonymous types are allowed (new without type)

lihat cuplikan proyek saya ini

//...
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

Anda menambahkan new keyworddalam Pilih penutupan bahkan pada complex propertiesAnda akan mendapatkan kesalahan ini

sehingga removepara ClassTypes from newkata kunci pada Linq to Entityquery ,,

karena itu akan diubah menjadi pernyataan sql dan dieksekusi pada SqlServer

jadi kapan saya bisa menggunakan new with typespada selectpenutupan?

Anda dapat menggunakannya jika Anda berurusan dengan 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 

setelah saya dieksekusi ToListpada query itu in memory collection jadi kami bisa gunakan new ClassTypesdi pilih

Basheer AL-MOMANI
sumber
Tentu Anda dapat menggunakan jenis anonim, tetapi Anda tidak dapat membuat entitas dalam kueri LINQ, bahkan untuk menetapkan anggota anonim, karena LINQ-to-Entities masih melempar pengecualian yang sama.
Suncat2000
0

Dalam banyak kasus, transformasi tidak diperlukan. Pikirkan alasan Anda menginginkan Daftar dengan tipe sangat, dan evaluasi jika Anda hanya menginginkan data, misalnya, dalam layanan web atau untuk menampilkannya. Tidak masalah jenisnya. Anda hanya perlu tahu cara membacanya dan memeriksa apakah identik dengan properti yang didefinisikan dalam jenis anonim yang Anda tetapkan. Itu adalah skenario optimun, menyebabkan sesuatu yang Anda tidak perlu semua bidang suatu entitas, dan itulah alasan jenis anonim ada.

Cara sederhana adalah melakukan ini:

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

Itu tidak akan membiarkan Anda memetakan kembali ke Produk karena itu adalah tabel yang Anda tanyakan. Anda memerlukan fungsi anonim, kemudian Anda dapat menambahkannya ke ViewModel, dan menambahkan setiap ViewModel ke List<MyViewModel>dan mengembalikannya. Ini sedikit penyimpangan, tetapi saya memasukkan peringatan tentang menangani tanggal yang dapat dibatalkan karena ini adalah rasa sakit di belakang untuk ditangani, kalau-kalau Anda punya. Beginilah cara saya menanganinya.

Semoga Anda memiliki ProductViewModel:

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

Saya memiliki kerangka kerja injeksi / repositori dependensi tempat saya memanggil fungsi untuk mengambil data saya. Menggunakan pos Anda sebagai contoh, dalam panggilan fungsi Kontroler Anda, akan terlihat seperti ini:

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

Di kelas repositori:

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

Kembali ke controller, Anda lakukan:

 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>
vapcguy
sumber
-1

hanya tambahkan AsEnumerable ():

public IQueryable<Product> GetProducts(int categoryID)
{
    return from p in db.Products.AsEnumerable()
           where p.CategoryID== categoryID
           select new Product { Name = p.Name};
}
HamidReza
sumber
8
TIDAK PERNAH melakukannya! Ini akan mengambil semua data dari DB dan kemudian akan melakukan pemilihan.
Gh61
1
Inilah sebabnya mengapa di beberapa perusahaan Linq dilarang untuk digunakan.
hakan
-2

Anda dapat menambahkan AsEnumerable ke koleksi Anda seperti yang berikut:

public IQueryable<Product> GetProducts(int categoryID)
{
    return from p in db.Products.AsEnumerable()
           where p.CategoryID== categoryID
           select new Product { Name = p.Name};
}
HamidReza
sumber
Mengapa ini adalah jawaban yang buruk meskipun itu berhasil ... .AsEnumerable berakhir linq ke entitas. Klausa Di mana dan segala sesuatu lainnya ditangani di luar linq untuk Entitas. yaitu setiap produk diambil kemudian disaring oleh linq ke objek. Selain itu, ini hampir sama persis dengan jawaban .ToList di atas. stackoverflow.com/questions/5311034/...
KenF
1
Masalah dengan ini adalah hanya pilih * dari ... dilakukan, bukan pilih Produk {Nama = p.Name} baru, karena Anda juga akan mendapatkan referensi siklik. Dan Anda hanya menginginkan Nama.
Sterling Diaz