.net 从EntityFramework中检索不带ONE字段的对象

7bsow1i6  于 2023-05-08  发布在  .NET
关注(0)|答案(9)|浏览(144)

我使用实体框架连接数据库。我有个小问题
我有一个表,其中有一个varbinary(MAX)列(带有filestream)。
我使用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”。)
(e.g.这段代码抛出前面的异常:

File file2 = context.Files.Where(f => f.Id == idFile)
  .Select(f => new File() {Id = f.Id, DataType = f.DataType}).FirstOrDefault();

而“File”是我用context.Files.ToList()得到的类型。这是一个很好的类:

using File = MyProjectNamespace.Common.Data.DataModel.File;

File是我的EF datacontext的一个已知类:

public ObjectSet<File> Files
{
    get { return _files  ?? (_files = CreateObjectSet<File>("Files")); }
}
private ObjectSet<File> _files;
ttisahbt

ttisahbt1#

有没有一种方法来检索一个列表,但没有填写此列?
你不想被人看见。如果列已Map,则它是实体的自然组成部分。没有此列的实体不完整-它是不同的数据集=投影。
我在这里使用匿名类型,因为否则你会得到一个NotSupportedException:无法在LINQ to Entities查询中构造实体或复杂类型“ProjectName.File”。
作为例外,您不能投影到Map实体。我提到了上面的原因-投影使不同的数据集和EF不喜欢“部分实体”。
错误16错误3023:Map从第2717行开始的片段时出错:列文件。必须Map表文件中的数据:它没有默认值,并且不可为空。
仅从设计器中删除属性是不够的。你必须打开EDMX作为XML和删除列从SSDL以及这将使你的模型非常脆弱(每次更新数据库将把你的列)。如果你不想Map列,你应该使用不带列的数据库视图,Map视图而不是表,但是你将不能插入数据。
作为所有问题的解决方法,请使用table splitting并使用1:1与主File实体的关系。

cidc1ykv

cidc1ykv2#

我会这样做

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:无法在LINQ to Entities查询. 中构造实体或复杂类型“ProjectName.File”,因为尚未将该类Map为实体。您不能在LINQ to Entities查询中包含EF不知道的对象,并期望它生成适当的SQL语句。

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

jdzmm42g

jdzmm42g3#

你可以这样做:

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

或者这个:

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

取决于您的EF版本

qvtsj1bj

qvtsj1bj4#

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

p3rjfoxz

p3rjfoxz5#

我有这个要求,因为我有一个Document实体,它有一个Content字段,其中包含文件的内容,即大约100 MB大小,我有一个搜索函数,我想返回其余的列。
我选择使用投影:

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列,因此不会触及100 MB的数据,并且查询的速度与您希望/期望的一样快。
接下来,我将其转换回我的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,但它确实意味着您不能包含其他实体...

hivapdat

hivapdat6#

我想分享我的尝试,以解决这个问题的情况下,别人是在同样的情况。
我从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创建的确切查询来模仿EntityFramework的对象,即使使用了[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对象一样正常工作,请评论,以便我可以更新答案。

hc8w905p

hc8w905p7#

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

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();

另外,将表反规范化为进一步的表也不是一个坏的做法,即一个具有元数据,一个具有有效负载以避免投影。投影将工作,唯一的问题是,需要编辑任何时候一个新的列被添加到表。

jyztefdp

jyztefdp8#

我试过这个:
在edmx图(EF 6)中,我单击了我想在EF中隐藏的列,并在它们的属性上将getter和setter设置为private。这样,对我来说,它工作。
我返回了一些包含User引用的数据,所以我想隐藏Password字段,即使它是加密和加盐的,我只是不想在我的json上,我不想做:

Select(col => new {})

因为创建和维护这些数据是一件痛苦的事情,尤其是对于有很多关系的大表。
这种方法的缺点是,如果重新生成模型,则需要再次修改它们的getter和setter。

o4tp2gmn

o4tp2gmn9#

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

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

相关问题