从不带 ONE 字段的实体框架中检索对象
本文关键字:框架 检索 对象 实体 ONE 字段 | 更新日期: 2023-09-27 18:21:21
我正在使用实体框架与数据库连接。我有一个小问题:
我有一个表,它有一个变量(MAX(列(带有文件流(。
我正在使用SQL请求来管理"数据"部分,但其余部分(文件的元数据(使用EF。
我有一个代码必须获取所有文件 ID、文件名、guid、修改日期......的文件。这根本不需要"数据"字段。
有没有办法检索列表但没有填充此列?
类似的东西
context.Files.Where(f=>f.xyz).Exclude(f=>f.Data).ToList();
??
我知道我可以创建匿名对象,但我需要将结果传输到方法,所以没有匿名方法。而且我不想把它放在匿名类型的列表中,然后创建一个我的非匿名类型(文件(的列表。
目标是避免这种情况:
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;
有没有办法检索列表但没有填充此列?
并非没有您想要避免的投影。如果列已映射,则它是实体的自然组成部分。没有此列的实体不完整 - 它是不同的数据集 = 投影。
我在这里使用匿名类型,否则你会得到一个 不支持异常:实体或复杂类型"项目名称.文件" 不能在 LINQ 到实体查询中构造。
例外情况是您无法投影到映射的实体。我在上面提到了原因 - 投影制作不同的数据集,EF 不喜欢"部分实体"。
错误16 错误 3023:从行开始映射片段时出现问题 2717:列文件。表中的数据 必须映射文件:它没有 默认值,且不可为空。
从设计器中删除属性是不够的。您必须以 XML 格式打开 EDMX 并从 SSDL 中删除列,这将使您的模型非常脆弱(来自数据库的每次更新都会将您的列放回原处(。如果不想映射列,则应使用不带列的数据库视图,并映射视图而不是表,但您将无法插入数据。
作为所有问题的解决方法,请使用表拆分并将有问题的二进制列与主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 语句不应在其结果集中包含大列,因为查询中不需要它。
从您的编辑中,您收到错误 不支持异常:实体或复杂类型"ProjectName.File"无法在 LINQ to Entities 查询中构造。 因为您尚未将该类映射为实体。不能在 EF 不知道的 LINQ 到实体查询中包含对象,并期望它生成适当的 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 版本
对于 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
我有这个要求,因为我有一个Document
实体,它有一个包含文件内容的Content
字段,即大小约为 100MB,并且我有一个搜索功能,我想返回其余的列。
我选择使用投影:
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,
//...
只需几个额外的步骤,但这对我来说效果很好。
<小时 />更新:目前我正在使用扩展方法,因此我不必在访问此实体的多个位置维护字段列表。所以我有:
public static IQueryable<Document> SelectExcludingContent(this IQueryable<Document> query)
{
return query.Select(o => new Document { DocumentId = o.DocumentId, FileName = o.FileName, ItemId = o.ItemId, Notes = o.Notes });
}
然后我像这样使用它:
IQueryable<Document> results = db.Documents
.SelectExcludingContent();
请注意,这不使用 DTO,但它确实意味着您不能包含其他实体......
分享我在其他人遇到同样情况的情况下解决此问题的尝试。
我从杰里米·丹尤(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 创建的确切查询来模拟实体框架的对象,即使使用这些[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 对象一样,请发表评论,以便我可以更新答案。
我在这里使用匿名类型,否则你会得到一个 不支持异常:实体或复杂类型"项目名称.文件" 不能在 LINQ 到实体查询中构造。
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();
而且,将表进一步非规范化也不是一个不好的做法,即一个带有元数据,一个带有有效载荷以避免投影。 投影将起作用,唯一的问题是,每当将新列添加到表中时都需要编辑。
我试过这个:
在 edmx 图 (EF 6( 中,我单击了要从 EF 中隐藏的列,在其属性上,您可以将其 getter 和 setter 设置为私有。这样,对我来说它就有效了。
我返回了一些包含用户引用的数据,所以我想隐藏密码字段,即使它是加密和加盐的,我只是不想在我的 json 上使用它,我不想做一个:
Select(col => new {})
因为这在创建和维护时很痛苦,尤其是对于具有大量关系的大表。
这种方法的缺点是,如果你再生你的模型,你需要再次修改它们的getter和setter。
使用实体框架高级工具,您可以在 efpt.config.json 中执行以下操作:
"Tables": [
{
"ExcludedColumns": [
"FileData"
],
"Name": "[dbo].[Attachment]",
"ObjectType": 0
}
]