エンティティフレームワークを使用してデータベースに接続しています。私は1つの小さな問題があります:
1つのvarbinary(MAX)列(filestream付き)を持つ1つのテーブルがあります。
SQL要求を使用して「データ」部分を管理していますが、残りはEF(ファイルのメタデータ)です。
ファイルのすべてのファイルID、ファイル名、GUID、変更日などを取得する必要があるコードが1つあります。これは「データ」フィールドをまったく必要としません。
リストを取得する方法はありますか?
何かのようなもの
_context.Files.Where(f=>f.xyz).Exclude(f=>f.Data).ToList();
_
?
匿名オブジェクトを作成できることは知っていますが、結果をメソッドに送信する必要があるため、匿名メソッドは送信しません。そして、これを匿名型のリストに入れてから、非匿名型(File)のリストを作成したくありません。
目標はこれを避けることです:
_using(RsSolutionsEntities context = new RsSolutionsEntities())
{
var file = context.Files
.Where(f => f.Id == idFile)
.Select(f => new {
f.Id, f.MimeType, f.Size, f.FileName, f.DataType,
f.DateModification, f.FileId
}).FirstOrDefault();
return new File() {
DataType = file.DataType, DateModification = file.DateModification,
FileId = file.FileId, FileName = file.FileName, Id = file.Id,
MimeType = file.MimeType, Size = file.Size
};
}
_
(ここでは、匿名型を使用しています。そうしないと、NotSupportedExceptionが発生します。エンティティ型または複合型 'ProjectName.File'は、LINQ to Entitiesクエリで構築できません。)
(たとえば、このコードは前の例外をスローします:
_File file2 = context.Files.Where(f => f.Id == idFile)
.Select(f => new File() {Id = f.Id, DataType = f.DataType}).FirstOrDefault();
_
「ファイル」は、context.Files.ToList()
で取得するタイプです。これは良いクラスです:
_using File = MyProjectNamespace.Common.Data.DataModel.File;
_
ファイルは私のEFデータコンテキストの既知のクラスです:
_public ObjectSet<File> Files
{
get { return _files ?? (_files = CreateObjectSet<File>("Files")); }
}
private ObjectSet<File> _files;
_
リストを取得する方法はありますか?
避けたい投影がないわけではありません。列がマップされる場合、それはエンティティの自然な部分です。この列のないエンティティは完全ではありません-異なるデータセット=投影です。
そうしないと、NotSupportedExceptionが発生するため、ここでは匿名型を使用しています。エンティティまたは複合型 'ProjectName.File'は、LINQ to Entitiesクエリで構築できません。
例外として、マップされたエンティティに投影することはできません。私は上記の理由に言及しました-投影は異なるデータセットを作成し、EFは「部分エンティティ」を好きではありません。
エラー16エラー3023:行2717で始まるフラグメントのマッピングの問題:テーブルファイル内の列Files.Dataをマップする必要があります:デフォルト値がなく、null許容できません。
デザイナーからプロパティを削除するだけでは不十分です。 EDMXをXMLとして開き、SSDLから列を削除する必要があります。これにより、モデルが非常に壊れやすくなります(データベースからの更新ごとに列が元に戻ります)。列をマップしたくない場合は、列なしでデータベースビューを使用し、テーブルではなくビューをマップする必要がありますが、データを挿入することはできません。
すべての問題の回避策として table splitting を使用し、問題のあるバイナリ列をメインのFile
エンティティと1対1の関係で別のエンティティに分離します。
私はこのようなことをします:
_var result = from thing in dbContext.Things
select new Thing {
PropertyA = thing.PropertyA,
Another = thing.Another
// and so on, skipping the VarBinary(MAX) property
};
_
Thing
は、EFが具体化する方法を知っているエンティティです。結果のSQLステートメントは、クエリで必要ないため、結果セットに大きな列を含めるべきではありません。
[〜#〜] edit [〜#〜]:編集からエラーを受け取りますNotSupportedException:エンティティまたは複合型 'ProjectName.File'はLINQ to Entitiesクエリで構築されます。そのクラスをエンティティとしてマッピングしていないためです。 できない EFが知らないオブジェクトをLINQ to Entitiesクエリに含め、適切なSQLステートメントを生成することを期待します。
定義でVarBinary(MAX)
列を除外する別のタイプをマップするか、上記のコードを使用できます。
あなたはこれを行うことができます:
var files = dbContext.Database.SqlQuery<File>("select FileId, DataType, MimeType from Files");
またはこれ:
var files = objectContext.ExecuteStoreQuery<File>("select FileId, DataType, MimeType from Files");
eFのバージョンに応じて
ファイルのコンテンツ、つまりサイズが約100MBのDocument
フィールドを持つContent
エンティティがあり、返りたい検索機能があるため、この要件がありました。残りの列。
投影を使用することにしました:
IQueryable<Document> results = dbContext.Documents.Include(o => o.UploadedBy).Select(o => new {
Content = (string)null,
ContentType = o.ContentType,
DocumentTypeId = o.DocumentTypeId,
FileName = o.FileName,
Id = o.Id,
// etc. even with related entities here like:
UploadedBy = o.UploadedBy
});
次に、私のWebApiコントローラーはこのresults
オブジェクトを共通のページネーション関数に渡します。この関数は.Skip
、.Take
および.ToList
。
これは、クエリが実行されたときにContent
列にアクセスしないため、100MBのデータは変更されず、クエリは希望どおり/期待どおりに高速であることを意味します。
次に、これをDTOクラスにキャストします。この場合、エンティティクラスとまったく同じなので、これは実装する必要のあるステップではないかもしれませんが、私の典型的なWebApiコーディングパターンに従っています。
var dtos = paginated.Select(o => new DocumentDTO
{
Content = o.Content,
ContentType = o.ContentType,
DocumentTypeId = o.DocumentTypeId,
FileName = o.FileName,
Id = o.Id,
UploadedBy = o.UploadedBy == null ? null : ModelFactory.Create(o.UploadedBy)
});
次に、DTOリストを返します。
return Ok(dtos);
したがって、元のポスターの要件に適合しない投影法を使用しますが、DTOクラスを使用している場合は、とにかく変換します。実際のエンティティと同じように簡単に以下を実行できます。
var dtos = paginated.Select(o => new Document
{
Content = o.Content,
ContentType = o.ContentType,
DocumentTypeId = o.DocumentTypeId,
//...
いくつかの追加手順がありますが、これはうまく機能しています。
EF Core 2の場合、次のようなソリューションを実装しました。
var files = context.Files.AsNoTracking()
.IgnoreProperty(f => f.Report)
.ToList();
基本的な考え方は、たとえば次のクエリを有効にすることです。
SELECT [f].[Id], [f].[Report], [f].[CreationDate]
FROM [File] AS [f]
これに:
SELECT [f].[Id], '' as [Report], [f].[CreationDate]
FROM [File] AS [f]
ここで完全なソースコードを見ることができます: https://github.com/aspnet/EntityFrameworkCore/issues/1387#issuecomment-495630292
そうしないと、NotSupportedExceptionが発生するため、ここでは匿名型を使用しています。エンティティまたは複合型 'ProjectName.File'は、LINQ to Entitiesクエリで構築できません。
var file = context.Files
.Where(f => f.Id == idFile)
.FirstOrDefault() // You need to exeucte the query if you want to reuse the type
.Select(f => new {
f.Id, f.MimeType, f.Size, f.FileName, f.DataType,
f.DateModification, f.FileId
}).FirstOrDefault();
また、テーブルをさらに非正規化することも悪いことではありません。1つはメタデータを使用し、もう1つはペイロードを使用して投影を回避します。プロジェクションは機能しますが、唯一の問題は、新しい列がテーブルに追加されるたびに編集する必要があることです。
他の誰かが同じ状況にある場合に、この問題を回避するための私の試みを共有したいと思います。
私は Jeremy Danyow が提案したものから始めました。
_// You need to include all fields in the query, just make null the ones you don't want.
var results = context.Database.SqlQuery<myEntity>("SELECT Field1, Field2, Field3, HugeField4 = NULL, Field5 FROM TableName");
_
私の場合、_IQueryable<>
_結果オブジェクトが必要なので、最後にAsQueryable()
を追加しました。もちろん、これにより_.Where
_、_.Take
_、および私たちが知っている他のコマンドへの呼び出しを追加でき、それらは正常に機能しました。ただし、注意事項があります。
通常のコード(基本的にcontext.myEntity.AsQueryable()
)は_System.Data.Entity.DbSet<Data.DataModel.myEntity>
_を返しましたが、このアプローチは_System.Linq.EnumerableQuery<Data.DataModel.myEntity>
_を返しました。
どうやらこれは、カスタムクエリが必要に応じて「そのまま」実行され、後で追加したフィルタリングがデータベースではなく後で実行されることを意味します。
したがって、EFが作成する正確なクエリを使用してEntity Frameworkのオブジェクトを模倣しようとしましたが、それらの_[Extent1]
_エイリアスを使用しても、機能しませんでした。結果のオブジェクトを分析すると、クエリは次のように終了しました
_FROM [dbo].[TableName] AS [Extent1].Where(c => ...
_
期待の代わりに
_FROM [dbo].[TableName] AS [Extent1] WHERE ([Extent1]...
_
とにかく、これは機能し、テーブルが大きくない限り、この方法は十分に高速です。それ以外の場合、古典的な動的SQLのように、文字列を連結して手動で条件を追加する以外にオプションはありません。私が何を話しているのかわからない場合の非常に基本的な例:
_string query = "SELECT Field1, Field2, Field3, HugeField4 = NULL, Field5 FROM TableName";
if (parameterId.HasValue)
query += " WHERE Field1 = " + parameterId.Value.ToString();
var results = context.Database.SqlQuery<myEntity>(query);
_
メソッドがこのフィールドを必要とする場合は、bool
パラメーターを追加して、次のようなことを実行できます。
_IQueryable<myEntity> results;
if (excludeBigData)
results = context.Database.SqlQuery<myEntity>("SELECT Field1, Field2, Field3, HugeField4 = NULL, Field5 FROM TableName").AsQueryable();
else
results = context.myEntity.AsQueryable();
_
誰かがLinq拡張機能を元のEFオブジェクトであるかのように適切に動作させることができた場合、答えを更新できるようにコメントしてください。
私はこれを試しました:
Edmxダイアグラム(EF 6)から、EFから非表示にする列をクリックし、そのプロパティでゲッターとセッターをプライベートに設定できます。そうすれば、私にとってはうまくいきます。
ユーザー参照を含むいくつかのデータを返すため、暗号化されソルトされているにもかかわらず、パスワードフィールドを非表示にしたかったのです。
Select(col => new {})
特に、多くのリレーションシップを持つ大きなテーブルの場合、作成と保守が面倒だからです。
この方法の欠点は、モデルを再生成する場合、ゲッターとセッターを再度変更する必要があることです。