web-dev-qa-db-ja.com

エンティティをLINQ to Entitiesクエリで構築することはできません

エンティティフレームワークによって生成されるproductというエンティティタイプがあります。私はこの質問を書いた

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};を使うとき、それは正しく働きます。

カスタム選択セクションを作成する方法

353
Ghooti Farangi

マッピングされたエンティティに投影することはできません(できないはずです)。ただし、匿名型または 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();
}
358
Yakimych

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

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()が省略された場合、目的の結果について十分に具体的である必要があることを警告するために、現在スローされているのと同じ例外がスローされる可能性があります。

255
Goran

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

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

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

72
Tomasz Iniewicz

これは、追加クラスを宣言せずにこれを行う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;
}

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

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

もう一つの簡単な方法:)

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

これを使えばうまくいくはずです - > selectを使って新しいリストを作る前にtoListを使わなければなりません。

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

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

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

DTOを使えば、次のことができます。

  • データを直列化可能にする(Json)
  • 循環参照を取り除く
  • 不要なプロパティを残すことでネットワークトラフィックを削減します(viewmodelwise)
  • オブジェクト平坦化を使用

私は今年学校でこれを学びました、そしてそれは非常に役に立つ道具です。

1
Jelman

重複しているとマークされた他の質問( はこちら を参照)に答えて、私は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();

注:この解決策は、Taskクラスにナビゲーションプロパティ(外部キー)がある場合にのみ機能します(ここでは 'Incident'と呼びます)。それがない場合は、 "AsQueryable()"で他の投稿されたソリューションの1つを使用することができます。

1
JollyBrackets

asEnumerable()を追加するだけです。

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

Linq to Entityを実行している場合は、クエリonly anonymous types are allowed (new without type)ClassTypeクロージャーでnewと共にselectを使用することはできません。

私のプロジェクトのこの断片を見てください。

//...
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にもSelectクロージャーにcomplex propertiesを追加したので、このエラーが発生します

ClassTypes from newクエリのLinq to Entityキーワードはremoveです。

それはsqlステートメントに変換されSqlServer上で実行されるからです。

だからいつselectクロージャーでnew with typesを使うことができますか?

あなたが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を実行した後、それはin memory collectionになりましたので、selectでnew ClassTypesを使用できます。

0

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

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

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

0
vikas suhag

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

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

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