Varlık bir LINQ to Entities sorgusunda oluşturulamaz


389

Varlık çerçevesi tarafından oluşturulan, ürün adı verilen bir varlık türü vardır. Bu sorguyu yazdım

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

Aşağıdaki kod aşağıdaki hatayı atar:

"Varlık veya karmaşık tür Shop.Product bir LINQ to Entities sorgusunda oluşturulamaz"

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

Ama select pbunun yerine kullandığımda select new Product { Name = p.Name};doğru çalışıyor.

Özel seçim bölümünü nasıl oluşturabilirim?


System.NotSupportedException: 'Varlık veya karmaşık tür' StudentInfoAjax.Models.Student 'LINQ to Entities sorgusunda oluşturulamıyor.'
Md Wahid

Yanıtlar:


390

Eşlenen bir varlığa projeksiyon yapamazsınız (yapamamalısınız). Bununla birlikte, anonim bir türe veya bir DTO'ya yansıtabilirsiniz :

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

Ve yönteminiz bir DTO Listesi döndürecektir.

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

152
Bunu neden yapmamam gerektiğini anlamıyorum ... Bu çok yararlı olurdu ...
Jonx

118
EF'deki eşlenen varlıklar temel olarak veritabanı tablolarını temsil eder. Eşlenen bir varlığa yansıtırsanız, temelde yaptığınız şey , geçerli bir durum olmayan bir varlığı kısmen yüklemektir. EF'in gelecekte böyle bir varlığın güncellemesini nasıl ele alacağına dair hiçbir fikri olmayacaktır (varsayılan davranış muhtemelen yüklü olmayan alanların üzerine null değeri veya nesnenizde bulunan her şeyin üzerine yazmak olacaktır). DB'deki verilerinizin bir kısmını kaybetme riskiniz olacağından, bu tehlikeli bir işlem olacaktır, bu nedenle EF'de varlıkları kısmen yüklemenize (veya eşlenen varlıklara yansıtmanıza) izin verilmez.
Yakimych

26
Bir sorgu aracılığıyla oluşturduğunuz / oluşturduğunuz ve bu nedenle daha sonra işleyeceğiniz ve daha sonra ekleyeceğiniz yepyeni bir varlık oluşturmanın tamamen farkında / niyetindeyseniz, @Yakimych mantıklıdır. Bu durumda, sorguyu çalıştırmaya zorlamanız ya da eklemek için bir
dto'ya

16
@Cargowire - Kabul ediyorum, bu senaryo var ve ne yaptığınızı bildiğiniz, ancak sınırlamalar nedeniyle yapmasına izin verilmediğinde sinir bozucu. Bununla birlikte, buna izin verilmiş olsaydı, örneğin kısmen yüklü varlıkları kurtarmaya çalışırken verilerinin kaybolmasından şikayet eden çok sayıda sinirli geliştirici olurdu. IMO, çok fazla gürültü (bir istisna atma, vb.) İle patlayan bir hata, izlenmesi ve açıklanması zor gizli hatalara neden olabilecek davranışlardan daha iyidir (eksik verileri fark etmeden önce işler iyi çalışır).
Yakimych


275

Anonim bir türe yansıtabilirsiniz ve daha sonra ondan model türüne

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

Düzenleme : Bu soru çok ilgi gördüğü için biraz daha spesifik olacağım.

Doğrudan model türüne yansıtma yapamazsınız (EF kısıtlaması), bu yüzden bunun bir yolu yoktur. Tek yol, anonim tür (1. yineleme) ve ardından model tipine (2. yineleme) yansıtmaktır.

Varlıkları bu şekilde kısmen yüklediğinizde, bu öğelerin güncellenemeyeceğini, bu nedenle olduğu gibi ayrılmaları gerektiğini de unutmayın.

Bunun neden mümkün olmadığını asla tam olarak anlamadım ve bu konudaki cevaplar buna karşı güçlü nedenler vermiyor (çoğunlukla kısmen yüklü verilerden bahsediyor). Kısmen yüklü durumdaki varlığın güncellenememesi doğrudur, ancak daha sonra bu varlık ayrılır, bu nedenle bunları kaydetmeye yönelik yanlışlıkla girişimler mümkün olmaz.

Yukarıda kullandığım yöntemi düşünün: sonuç olarak hala kısmen yüklü bir model varlığımız var. Bu varlık ayrılmıştır.

Bu (var olma isteği) olası kodu düşünün:

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

Bu aynı zamanda müstakil varlıkların bir listesi ile sonuçlanabilir, bu nedenle iki tekrarlama yapmamız gerekmez. Bir derleyici, AsNoTracking () 'in kullanıldığını görmek akıllıca olur, bu da ayrık varlıklara neden olur, böylece bunu yapmamıza izin verebilir. Bununla birlikte, AsNoTracking () atlanmışsa, istediğimiz sonuç hakkında yeterince spesifik olmamız gerektiği konusunda bizi uyarmak için şu anda atılanla aynı istisnayı atabilir.


3
Bu, yansıtmak istediğiniz seçili varlığın durumuna ihtiyaç duymadığınız / ilgilenmediğiniz zaman en temiz çözümdür.
Mário Meyrelles

2
Ve IEnumerable veya IQueryable döndürür umurumda değil;). Ama yine de benim oyumu alıyorsunuz çünkü bu çözüm benim için çalışıyor.
Michael Brennt

10
teknik olarak, model tipine projeksiyon sorgu dışında gerçekleşiyor ve ben de liste üzerinden ek bir yineleme gerektirir inanıyorum. Bu çözümü kodum için kullanmayacağım, ama bu sorunun çözümü. Uptick.
1c1cle

4
Bunu kabul edilen DTO çözümüne tercih ediyorum - çok daha zarif ve temiz
Adam Hey

7
Ancak bununla ilgili olarak, aslında sorunun cevabı değildir. Bu bir Linq to Objects sorgu projeksiyonu değil, Linq To Objects projeksiyonu nasıl yapılır sorusudur. DTO seçeneği tek seçenek: Linq to Entities.
rism

78

İş bulduğum başka bir yol var, Ürün sınıfınızdan türeyen bir sınıf oluşturmanız ve bunu kullanmanız gerekir. Örneğin:

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

Bunun "izin verilip verilmediği" konusunda emin değilim, ancak çalışıyor.


3
Zeki! Bunu şimdi denedim ve işe yarıyor. Eminim beni bir şekilde yakacak.
Daniel

5
BTW, GetProducts () sonuçlarını devam ettirmeye çalışırsanız EF sizi PseudoProduct için eşlemeyi bulamadığı için ısırır, örn. "System.InvalidOperationException: EntityType 'blah.PseudoProduct'" için eşleme ve meta veri bilgileri bulunamadı.
sming

4
En iyi cevap ve sorunun parametreleri içinde cevap veren tek cevap. Diğer tüm yanıtlar dönüş türünü değiştirir veya IQueryable'ı erken çalıştırır ve nesnelere linq kullanır
rdans

2
% 100 çalıştığı için şok oldu ... EF 6.1'de çalışıyor.
TravisWhidden

2
@mejobloggs Türetilmiş sınıfta [NotMapped] özelliğini veya akıcı API kullanıyorsanız .Ignore <T> öğesini deneyin.
Dunc

37

Bunu, aditional sınıfını bildirmeden yapmanın bir yolu vardır:

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

Ancak, bu yalnızca birden fazla varlığı tek bir varlıkta birleştirmek istiyorsanız kullanılır. Yukarıdaki işlevler (basit ürün-ürün eşleme) şu şekilde yapılır:

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

Başka bir basit yol :)

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

iyi nokta sadece güzel cevabınızla IQueryable bir şey öğrendim. Neden bir ToList () sonra yararlı olmadığını açıklarsanız iyi olurdu ve nedeni bir LINQ-SQL sorgusunda genel listeleri kullanamaz olmasıdır. Yani sonuçları her zaman arayan tarafından başka bir sorguya iteceğini biliyorsanız, o zaman kesinlikle IQueryable olmak mantıklı. Ama değilse ... daha sonra genel bir liste olarak kullanacaksanız, bu yöntemin her çağrısında IQueryable üzerinde bir ToList () yapmamanız için ToList () yöntemini kullanın.
PositiveGuy

Tamamen arkadaşım. Sadece soru yöntemi imzasını taklit ediyorum, bu yüzden onu Sorgu yapabilen bir şekilde dönüştürüyorum ...;)
Soren

1
Bu işe yarar, ToList () öğesinden sonra productList düzenlenemez hale gelir. Nasıl düzenlenebilir yapabilirim?
doncadavona

.ToListSorgu koyarsanız , yürütülür ve sunucudan veri alınır, sonra tekrar yapmak için ne anlamı var AsQueryable?
Moshii

1
@Moshii sadece yöntem dönüş tipi imzasını tatmin etmek için (cevapta söylediğim gibi, artık yararlı değil).
Soren

4

Bunu kullanabilirsiniz ve çalışıyor olmalıdır -> toListYeni listeyi seçmeden önce şunu kullanmalısınız:

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

3
Bu ancak yine de değil, bir '[..] SELECT *' a yapacağını 'DAN SEÇME isim [..]'
Timo Hermans

1

Yinelenen olarak işaretlenen diğer soruya yanıt olarak ( buraya bakın ) Soren'in cevabına dayalı hızlı ve kolay bir çözüm buldum:

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

Not: Bu çözüm yalnızca Görev sınıfında (burada 'Olay' olarak adlandırılır) bir navigasyon özelliğiniz (yabancı anahtar) varsa çalışır. Buna sahip değilseniz, yayınlanan diğer çözümlerden birini "AsQueryable ()" ile kullanabilirsiniz.


1

Veri Aktarım Nesneleri'ni (DTO'lar) kullanarak bunu çözebilirsiniz.

Bunlar, ihtiyacınız olan özellikleri koyduğunuz görünüm modülleri gibidir ve bunları kontrol cihazınızda manuel olarak veya AutoMapper gibi üçüncü taraf çözümleri kullanarak eşleştirebilirsiniz.

DTO'lar ile şunları yapabilirsiniz:

  • Verileri serileştirilebilir hale getirme (Json)
  • Dairesel referanslardan kurtulun
  • İhtiyacınız olmayan özellikleri bırakarak ağ trafiğini azaltın (görünüm doğru yönde)
  • Nesne düzleştirmeyi kullan

Bunu bu yıl okulda öğreniyorum ve bu çok kullanışlı bir araç.


0

Varlık çerçevesini kullanıyorsanız, Varlık olarak karmaşık modelinizi kullanan DbContext'ten özelliği kaldırmayı deneyin Birden çok modeli Entity adlı bir görünüm modeline eşlerken aynı sorun yaşadım

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

DbContext giriş kaldırmak hatamı düzeltti.


0

Eğer yürütme eğer Linq to Entitysen kullanamazsınız ClassTypeile newdeselect sorgunun kapatılmasıonly anonymous types are allowed (new without type)

projemin bu pasajına bak

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

eklediğinizde, new keywordSeçme kapağını da eklediyseniz, complex propertiesbu hatayı alacaksınız

böylece anahtar kelime sorguları ,,removeClassTypes from newLinq to Entity

çünkü sql deyimine dönüşecek ve SqlServer üzerinde yürütülecek

böylece ne zaman kullanabilir new with typesüzerinde selectkapanması?

Eğer uğraşıyorsan kullanabilirsiniz 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 

ben ToListsorgu üzerinde yürüttükten sonra oldu in memory collection böylece biz new ClassTypesselect kullanmak


Anonim türleri kullanabileceğinizden emin olabilirsiniz, ancak LINQ-to-Entities aynı istisnayı attığından, anonim bir üye ayarlamak için bile LINQ sorgusu içinde bir varlık oluşturamazsınız.
Suncat2000

0

Çoğu durumda, dönüşüme gerek yoktur. Listeyi güçlü bir şekilde yazmanın nedenini düşünün ve yalnızca bir web hizmetinde mi yoksa görüntülemek için mi veri istediğinizi değerlendirin. Türü önemli değil. Sadece nasıl okunacağını bilmeniz ve tanımladığınız anonim tipte tanımlanan özelliklerle aynı olup olmadığını kontrol etmeniz gerekir. Optimun senaryosu budur, bir varlığın tüm alanlarına ihtiyacınız olmayan bir şeye neden olur ve anonim türün var olmasının nedeni budur.

Bunu yapmanın basit bir yolu:

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

0

Sorguladığınız tablonuz olduğu için Ürün ile yeniden eşleştirmenize izin vermiyor. Anonim bir işleve ihtiyacınız vardır, daha sonra bunu bir ViewModel'e ekleyebilir ve her ViewModel'i bir a ekleyebilir List<MyViewModel>ve bunları geri gönderebilirsiniz . Bu hafif bir tartışma, ancak null edilebilir tarihleri ​​ele alma konusunda uyarılar ekliyorum, çünkü bunlar varsa, başa çıkmak için bir ağrı. Ben böyle idare ettim.

İnşallah ProductViewModel:

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

Verilerimi kapmak için bir işlev çağırdığım bağımlılık enjeksiyon / havuz çerçevem ​​var. Yayınınızı örnek olarak kullanmak, Denetleyici işlev çağrınızda şöyle görünür:

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

Havuz sınıfında:

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

Denetleyiciye geri döndüğünüzde:

 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

sadece AsEnumerable () ekleyin:

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

8
Asla yapma! Bu, DB'deki tüm verileri getirir ve sonra seçimi yapar.
Gh61

1
Bu yüzden bazı şirketlerde Linq kullanmak yasaktır.
hakan

-2

aşağıdaki gibi koleksiyonunuza AsEnumerable ekleyebilirsiniz:

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

Çalışmasına rağmen neden bu kötü bir cevap ... .AsEnumerable varlıkları linq bitirir. Where cümlesi ve diğer her şey Varlıklara linq dışında ele alınır. yani her ürün alınır ve sonra linq ile nesnelere filtre edilir. Bunun dışında yukarıdaki .ToList yanıtıyla hemen hemen aynı. stackoverflow.com/questions/5311034/…
KenF

1
Buradaki problem, sadece döngüsel bir referans alacağınız için, yeni bir ürün {Name = p.Name} seçmemeniz ... gerçekleştirilmesidir. Ve sadece Adı istiyorsun.
Sterling Diaz
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.