エンティティはLINQ to Entitiesクエリで作成できません


389

エンティティフレームワークによって生成される製品と呼ばれるエンティティタイプがあります。私はこのクエリを書きました

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

以下のコードは次のエラーをスローします:

"エンティティまたは複合タイプShop.ProductはLINQ to Entitiesクエリで作成できません"

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

しかし、select p代わりに使用するとselect new Product { Name = p.Name};、正しく動作します。

どうすればカスタム選択セクションを実行できますか?


System.NotSupportedException: 'エンティティまたは複合型' StudentInfoAjax.Models.Student 'は、LINQ to Entitiesクエリで作成できません。
Md Wahid

回答:


390

マップされたエンティティにプロジェクトを投影することはできません(できるはずです)。ただし、匿名型またはDTOに投影できます。

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

そして、あなたのメソッドは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
なぜこれを実行できないのか理解できません...これはとても
役に立ち

118
まあ、EFでマップされたエンティティは基本的にデータベーステーブルを表します。マップされたエンティティに投影する場合、基本的に行うのはエンティティを部分的にロードすることであり、これは有効な状態ではありません。EFは、将来このようなエンティティの更新を処理する方法などの手掛かりを持ちません(デフォルトの動作は、ロードされていないフィールドをnullまたはオブジェクトにあるもので上書きすることでしょう)。DB内の一部のデータが失われる危険性があるため、これは危険な操作です。したがって、EFでエンティティ(またはマップされたエンティティにプロジェクト)を部分的にロードすることはできません。
ヤキミチ

26
@Yakimychは、クエリを介して生成/作成している集計エンティティがあり、したがって、操作して後で追加する新しいエンティティを作成することを完全に認識/意図している場合を除き、意味があります。この場合、クエリを強制的に実行するか、dtoにプッシュしてエンティティに戻して追加する必要があります。これはイライラします
Cargowire

16
@Cargowire-私は同意します、そのシナリオは存在します、そしてあなたがあなたが何をしているかを知っているが制限のためにそれをすることが許されないときそれは苛立たしいです。ただし、これが許可されていれば、部分的にロードされたエンティティを保存しようとすると、データが失われることについて不満を抱く開発者がたくさんいます。IMO、大量のノイズ(例外のスローなど)で爆発するエラーは、追跡して説明することが困難な隠れたバグを引き起こす可能性のある動作よりも優れています(欠落したデータに気づき始める前にうまくいく)。
ヤキミッチ2011


275

匿名型に射影し、それからモデル型に射影できます

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

編集:この質問は多くの注目を集めたので、もう少し具体的にします。

モデルタイプに直接投影することはできません(EF制限)。そのため、これを回避する方法はありません。唯一の方法は、匿名タイプ(1回目の反復)に投影してから、モデルタイプ(2回目の反復)に投影することです。

また、この方法でエンティティを部分的にロードすると、エンティティを更新できないため、そのままの状態で切り離しておく必要があります。

これが不可能である理由を完全に理解したことはありません。このスレッドの回答は、それに対する強い理由を示していません(主に部分的にロードされたデータについて)。部分的にロードされた状態ではエンティティを更新できないのは正しいことですが、このエンティティはデタッチされるため、誤ってエンティティを保存しようとすることはできません。

上記で使用した方法を検討してください。結果として部分的に読み込まれたモデルエンティティがまだ残っています。このエンティティは分離されています。

この(存在したい)可能なコードを考えてみましょう:

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

これにより、分離されたエンティティのリストが生成されることもあるため、2回繰り返す必要はありません。コンパイラーは、AsNoTracking()が使用されていることを確認するのが賢明です。これにより、エンティティーが分離されるため、これを行うことができます。ただし、AsNoTracking()が省略された場合は、現在スローしているのと同じ例外をスローして、必要な結果を十分に特定する必要があることを警告します。


3
これは、投影する選択したエンティティの状態が必要ない、またはその状態を気にしない場合に最もクリーンなソリューションです。
マリオMeyrelles

2
そして、IEnumerableまたはIQueryableを返すかどうかを気にしない場合;)。しかし、それでもあなたは私の賛成を得ます。この解決策は今私のために機能します。
Michael Brennt 2013

10
技術的には、モデルタイプへの射影はクエリの外部で行われており、リスト全体で追加の反復が必要だと思います。私のコードではこのソリューションを使用しませんが、質問に対するソリューションです。上向き。
1c1cle 2014年

4
私はこれを、受け入れられたDTOソリューションよりもはるかにエレガントでクリーンなものを好みます
Adam Hey

7
それ以外の点では、それは実際には質問に対する回答ではありません。これは、Linq to Entitiesクエリプロジェクションではなく、Linq To Objectsプロジェクションを実行する方法に関する答えです。したがって、DTOオプションは唯一のオプションre:Linq to Entitiesです。
rism 2015年

78

私が作品を見つけた別の方法があります、あなたはあなたの製品クラスから派生するクラスを構築し、それを使用する必要があります。例えば:

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

これが「許可」されているかどうかはわかりませんが、機能します。


3
賢い!これを今試してみましたが、うまくいきます。でもどういうわけか私は火傷するだろうと確信しています。
ダニエル

5
ちなみに、EFがPseudoProductのマッピングを見つけることができないため、GetProducts()の結果を永続化しようとすると、これは問題になります。
2013

4
ベストアンサー、および質問のパラメーター内で回答する唯一の回答。他のすべての回答は、戻り値の型を変更するか、IQueryableを早期に実行し、オブジェクトにlinqを使用します
rdans

2
100%ショックで動作しました... EF 6.1ではこれが動作しています。
TravisWhidden 2015

2
@mejobloggs派生クラスで[NotMapped]属性を試すか、Fluent APIを使用している場合は.Ignore <T>を試してください。
2016年

37

追加のクラスを宣言せずにこれを行う1つの方法を次に示します。

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

ただし、これは、複数のエンティティを1つのエンティティに結合する場合にのみ使用されます。上記の機能(単純な製品から製品へのマッピング)は次のように行われます。

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

別の簡単な方法:)

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

良い点私はあなたの素晴らしい返事でIQueryableを学びました。ToList()の後でそれが役に立たない理由を説明したとしたら良かったでしょう。その理由は、LINQ-to-SQLクエリでジェネリックリストを使用できないためです。したがって、呼び出し元が常に結果を別のクエリにプッシュすることがわかっている場合は、IQueryableであることが確実です。しかし、そうでない場合...後で汎用リストとして使用する場合は、メソッド内でToList()を使用して、このメソッドへのすべての呼び出しでIQueryableに対してToList()を実行しないようにします。
PositiveGuy

あなたは私の友人を完全に大丈夫です。私は質問メソッドのシグネチャを模倣します。そのため、クエリ可能な形式に変換します...;)
Soren

1
これは機能し、productListはToList()の後で編集できなくなります。どうすれば編集可能にできますか?
doncadavona 2015

あなたが入れた場合は.ToList、クエリで、それがサーバーからデータを実行し、引っ張られ、再びそれを作るためのポイントは何ですかAsQueryable?。
Moshii

1
@Moshiiは、メソッドの戻り値の型シグニチャを満たすためにだけです(回答で述べたように、これはもう役に立ちません)。
Soren

4

あなたはこれを使うことができ、それはうまくいくはずです-> toListselectを使って新しいリストを作る前に使う必要があります:

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

3
ただし、これは「SELECT * FROM [..]」ではなく「SELECT * FROM [..]」を実行します
Timo Hermans

1

重複としてマークされた他の質問(ここを参照)に応じて、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();

注:このソリューションは、タスククラス(ここでは「インシデント」)にナビゲーションプロパティ(外部キー)がある場合にのみ機能します。それがない場合は、「AsQueryable()」で他の投稿されたソリューションのいずれかを使用できます。


1

これは、データ転送オブジェクト(DTO)を使用して解決できます。

これらは、必要なプロパティを配置するビューモデルと少し似ており、コントローラで手動で、またはAutoMapperなどのサードパーティのソリューションを使用してマップできます。

DTOを使用すると、次のことができます。

  • データをシリアライズ可能にする(Json)
  • 循環参照を取り除く
  • 不要なプロパティを残すことにより、ネットワークトラフィックを削減する(viewmodelwise)
  • オブジェクトの平坦化を使用する

私は今年学校でこれを学びました、そしてそれは非常に便利なツールです。


0

Entityフレームワークを使用している場合は、DbContextからプロパティを削除してみてください。複雑なモデルをEntityとして使用しています。複数のモデルをEntityという名前のビューモデルにマッピングするときに同じ問題がありました。

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

DbContextからエントリを削除すると、エラーが修正されました。


0

実行しているLinq to Entity場合は、クエリのクロージャでClassTypewith newを使用できませんselectonly anonymous types are allowed (new without type)

私のプロジェクトのこのスニペットを見てください

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

あなたがこのエラーが発生new keywordするcomplex properties場合でも、Select Selectクロージャーを追加した

その上のキーワードのクエリは,,removeClassTypes from newLinq to Entity

sqlステートメントに変換され、SqlServerで実行されるため

そのとき、私は使用することができますnew with typesselectの閉鎖?

あなたが扱っているならあなたはそれを使うことができます 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 

ToListクエリで実行した後、selectでin memory collection 使用できるようになりましたnew ClassTypes


もちろん、匿名型を使用できますが、LINQ-to-Entitiesでも同じ例外がスローされるため、匿名メンバーを設定する場合でも、LINQクエリ内にエンティティを作成することはできません。
Suncat2000

0

多くの場合、変換は必要ありません。強い型のリストが必要な理由を考えて、たとえばWebサービスやデータの表示など、データだけが必要かどうかを評価します。タイプは関係ありません。あなたはそれを読む方法を知っていて、それがあなたが定義した匿名タイプで定義されたプロパティと同一であることを確認する必要があるだけです。それが最適なシナリオです。エンティティのすべてのフィールドを必要としない何かを引き起こします。それが匿名型が存在する理由です。

簡単な方法はこれを行うことです:

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

0

これはクエリを実行しているテーブルであるため、Productにマップし直すことはできません。匿名関数が必要な場合は、それをViewModelに追加し、各ViewModelをに追加してList<MyViewModel>返すことができます。これは少し余談ですが、null値を使用できる日付の処理については注意が必要です。これらがある場合に備えて、これらは対処するのが難しいためです。これが私が扱った方法です。

うまくいけば、あなたが持っていますProductViewModel

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

データを取得するための関数を呼び出す依存性注入/リポジトリフレームワークがあります。投稿を例として使用すると、コントローラー関数呼び出しでは次のようになります。

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

リポジトリクラス:

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

コントローラーに戻って、次のことを行います。

 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

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
絶対にしないでください!これにより、DBからすべてのデータがフェッチされ、選択が行われます。
Gh61、2017

1
これが、一部の企業でLinqの使用が禁止されている理由です。
hakan 2017年

-2

次のようにAsEnumerableをコレクションに追加できます。

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

なぜ機能するのにこれが悪い答えなのか... .AsEnumerableはエンティティへのlinqを終了します。Where句とその他すべては、Linq to Entitiesの外部で処理されます。つまり、すべての製品が取得され、linqによってオブジェクトにフィルターされます。これを除けば、上記の.ToListの回答とほとんど同じです。 stackoverflow.com/questions/5311034/...
KenF

1
これの問題は、select * from ...が実行されるだけで、select New Product {Name = p.Name}ではないことです。循環参照も取得されるためです。そして、あなたは名前だけを望みます。
スターリングディアス
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.