从没有一个字段的实体框架中检索对象

Posted

技术标签:

【中文标题】从没有一个字段的实体框架中检索对象【英文标题】:Retrieve an object from entityframework without ONE field 【发布时间】:2012-02-16 21:56:46 【问题描述】:

我正在使用实体框架来连接数据库。我有一个小问题:

我有一张表,它有一个 varbinary(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:无法在 LINQ to Entities 查询中构造实体或复杂类型“ProjectName.File”。)

(例如,这段代码抛出了前面的异常:

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;

【问题讨论】:

您可以从您的 EF 对象中删除该列吗? 我希望我可以,但它是一个“NON NULL”列,EF 不喜欢我有不在模型中的非空列 EF 在排除非空列时遇到问题的唯一原因是在INSERT 到数据库期间。您可以使用过程、触发器和其他方法来解决这个问题。对于SELECT,您绝对可以排除表格列。 我试过了,但由于我删除了“数据”字段,即使我没有在任何地方做任何“插入”,我的 edmx 模型中也出现异常:错误 16 错误 3023:问题从第 2717 行开始的映射片段:必须映射表 Files 中的列 Files.Data:它没有默认值且不可为空。 你能编辑实际的数据库吗?可能值得将 Data 列移到它自己的表中。 【参考方案1】:

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

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

我在这里使用匿名类型,否则你会得到一个 NotSupportedException:实体或复杂类型“ProjectName.File” 不能在 LINQ to Entities 查询中构造。

正如例外所说,您不能投影到映射实体。我在上面提到了原因 - 投影使不同的数据集和 EF 不喜欢“部分实体”。

错误 16 错误 3023:从行开始映射片段时出现问题 2717:Column Files.Data in table Files must be mapped: 它没有 默认值,不可为空。

从设计器中删除属性是不够的。您必须将 EDMX 作为 XML 打开并从 SSDL 中删除列,这将使您的模型非常脆弱(数据库的每次更新都会使您的列恢复原状)。如果您不想映射列,则应使用不带列的数据库视图并映射视图而不是表,但您将无法插入数据。

作为所有问题的解决方法,请使用 table splitting 并将有问题的二进制列与与您的主要 File 实体具有 1 : 1 关系的另一个实体分开。

【讨论】:

这是放弃 EF 的一个原因。绝对疯了,这就像事情发生时的面包和黄油一样,不包括在正常数据库解决方案中作为单个最大列完全适合的单个大列,而其余所有列都可以用于列出这些对象等等。当前的解决方案大大增加了复杂性——无论是添加各种额外的类型来进行预测,还是将完全相同的类型转换为相同的类型并再次返回的代码;或者,制作一个 1:1 的表格,在不需要的时候做所有这些。 我们为什么要避免投影?是出于性能原因吗? @Gilles:对不起,这太苛刻了。我不想暗示投影不好。如果您知道后果,则投影很棒。结果是投影不是实体 - 当您返回投影时,EF 不会提供任何高级功能。急切加载可能不起作用(取决于查询),延迟加载不起作用,更改跟踪不起作用,等等。【参考方案2】:

我会这样做:

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 语句不应在其结果集中包含大列,因为查询中不需要它。

编辑:从您的编辑中,您会收到错误NotSupportedException:无法在 LINQ to Entities 查询中构造实体或复杂类型“ProjectName.File”。 因为您尚未将该类映射为实体。您不能在 LINQ to Entities 查询中包含 EF 不知道的对象并期望它生成适当的 SQL 语句。

您可以映射另一种类型,在其定义中排除 VarBinary(MAX) 列或使用上面的代码。

【讨论】:

我已经尝试过了,但 EF 告诉我不能在选择中放入复杂类型。 您可以发布您尝试过的代码以及您在上面的问题中遇到的错误吗?听起来您正在尝试使用 EF 不知道且无法为其生成 SQL 语句的类型的对象。 但是 EF 知道这些文件,我的 ObjectContext 有它的 EntitySet。就像我说的,如果我执行 context.Files.ToList();,它会给我一个 List 在上面的代码中,您区分了ProjectName.FileMyProjectNamespace.Common.Data.DataModel.File。听起来ProjectName.File 没有映射到 EF,但另一个是。 一点也不,只是在一条错误消息中,它使用程序集名称,而在另一个地方使用完整的命名空间。我的项目中只有一个文件,我可以保证这一点【参考方案3】:

你可以这样做:

var files = dbContext.Database.SqlQuery<File>("select FileId, DataType, MimeType from Files");

或者这个:

var files = objectContext.ExecuteStoreQuery<File>("select FileId, DataType, MimeType from Files");

取决于您的 EF 版本

【讨论】:

如果您有 dbcontext 而不是 objectcontext,您仍然可以访问对象上下文。 (dbContext 作为 IObjectContextAdapter).ObjectContext; dbcontext dbContext.Database.SqlQuery(string sql); 上还有一个可用的方法 这对我有用。但是,应该注意的是,我为空数据字段添加了一个空列。 "SELECT Id, Name, Path, CreateDate, LastUpdated, null AS Data FROM [File]";【参考方案4】:

我有这个要求是因为我有一个 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,
    //...

只需几个额外的步骤,但这对我来说效果很好。

【讨论】:

【参考方案5】:

对于 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

【讨论】:

【参考方案6】:

我想分享一下我解决此问题的尝试,以防其他人遇到同样的情况。

我从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&lt;&gt; 结果对象,所以我在最后添加了AsQueryable()。当然,这让我可以添加对.Where.Take 和其他我们都知道的命令的调用,并且它们运行良好。但有一个警告:

普通代码(基本上是context.myEntity.AsQueryable())返回System.Data.Entity.DbSet&lt;Data.DataModel.myEntity&gt;,而这种方法返回System.Linq.EnumerableQuery&lt;Data.DataModel.myEntity&gt;

显然,这意味着我的自定义查询会在需要时“按原样”执行,而我稍后添加的过滤是在之后完成的,而不是在数据库中。

因此,我尝试通过使用 EF 创建的确切查询来模仿实体框架的对象,即使使用那些 [Extent1] 别名,但它不起作用。在分析结果对象时,它的查询结束了

FROM [dbo].[TableName] AS [Extent1].Where(c =&gt; ...

而不是预期的

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 对象一样正常工作,请发表评论,以便我更新答案。

【讨论】:

【参考方案7】:

我在这里使用匿名类型,否则你会得到一个 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();

此外,将表格进一步去规范化也不是一个坏习惯,即一个带有元数据,一个带有有效负载以避免投影。投影可以工作,唯一的问题是,在将新列添加到表时需要进行编辑。

【讨论】:

【参考方案8】:

我试过了:

从 edmx 图 (EF 6),我单击了我想对 EF 隐藏的列,在它们的属性上,您可以将它们的 getter 和 setter 设置为私有。这样,对我来说就可以了。

我返回了一些包含用户引用的数据,所以我想隐藏密码字段,即使它已加密和加盐,我只是不想在我的 json 中使用它,我不想这样做:

Select(col => new ) 

因为创建和维护起来很麻烦,尤其是对于有很多关系的大表。

这种方法的缺点是,如果您重新生成模型,则需要再次修改它们的 getter 和 setter。

【讨论】:

【参考方案9】:

使用 Entity Framework Power Tools,您可以在 efpt.config.json 中执行以下操作:

"Tables": [
  
     "ExcludedColumns": [
        "FileData"
     ],
     "Name": "[dbo].[Attachment]",
     "ObjectType": 0
  
]

【讨论】:

以上是关于从没有一个字段的实体框架中检索对象的主要内容,如果未能解决你的问题,请参考以下文章

如何从没有关系laravel 8的另一个表中检索具有多行的行数据

如何从没有绑定源 C# 生成的 datagridview 中检索数据源?

从没有外键的两个模型的 JOIN 中检索值(Django)

使用实体框架自动编号

如何使用实体框架检索插入实体的 ID? [关闭]

具有自身 parentId 的实体框架核心嵌套对象