从不带 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;

从不带 ONE 字段的实体框架中检索对象

有没有办法检索列表但没有填充此列?

并非没有您想要避免的投影。如果列已映射,则它是实体的自然组成部分。没有此列的实体不完整 - 它是不同的数据集 = 投影。

我在这里使用匿名类型,否则你会得到一个 不支持异常:实体或复杂类型"项目名称.文件" 不能在 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
  }
]