将额外的列加载到内存中的集合

编程语言 2026-07-10

我有一个对象列表,其中来自数据库已填充了部分属性,但不是全部属性:

class Example
{
    public int Id { get; set; }
    public int A { get; set; }
    public int B { get; set; }
    public int C { get; set; }
}

// ...

var context = contextFactory.CreateDbContext();
var objects = context.Examples
    .Select(e => new Example
    {
        Id = e.Id,
        A = e.A
    }).ToList();

现在,我想在该列表上加载更多属性。让EF Core以最有效的方式填充我想要的数据是什么?

目前,我的最佳想法大致是这样的:

var objectsDict = objects.ToDictionary(e => e.Id);
var context = contextFactory.CreateDbContext();

var newObjects = context.Examples
    .Select(e => new Example
    {
        Id = e.Id,
        A = objectsDict[e.Id].A,
        B = e.B
    }).ToList();

但我觉得一定有更好的办法。如果只是一个实体,我可能可以用 context.Entry 来做点什么,但我不知道有没有办法在不为每个实体都进行一次数据库往返查询的情况下对多个实体执行同样的操作。我到底错过了什么?

可能相关:EF Core Collection Load .. of a Collection

解决方案

如果你不关心先前加载集合的状态,那么就让EF从数据库加载数据,并考虑丢弃第一组不完整的数据以获取完整的集合。

var newObjects = context.Examples
    .Select(e => new Example
    {
        Id = e.Id,
        A = e.A,
        B = e.B,
        C = e.C
    }).ToList();

无论如何你都需要到数据库去获取扩展后的状态,因此你什么都没省下,反而在试图避免重新读取在最初就已读取的“A”属性等时增加了复杂性。当你有一个投影类,或一个实体时,应该始终考虑将其完整填充,而不是试图过度优化,或在不同关注点之间重复使用同一个类,在某些情况下属性会被加载,其他时候则不会。当一个对象形式的数据被传来传去时,应始终假设它是完整的;如果某个属性未设置,那是因为该属性的数据状态为null/默认值。

在你有两个不同的DTO/视图模型(DTOs/ViewModels)时,例如:

class ExampleSummary
{
    public int Id { get; set; }
    public int A { get; set; }
}

class ExampleFull
{
    public int Id { get; set; }
    public int A { get; set; }
    public int B { get; set; }
}

...来自同一张表,你已经加载了一组Example1,其中包含你想覆盖在读取Example2时的数据的属性的更改:

// Initial set loaded..
var objects = context.Examples
    .Select(e => new ExampleSummary
    {
        Id = e.Id,
        A = e.A
    }).ToList();
//  Later there may be changes to objects.A values so when we load the new ExampleFull from the same table:

var expandedObjects = context.Examples
    .Select(e => new ExampleFull
    {
        Id = e.Id,
        A = e.A,
        B = e.B,
    }).ToList();

现在将原始摘要集合中的任何“A”值复制到新加载的expandedObjects:

var matches = expandedObjects.Join(objects, a=> a.Id, b=> b.Id, (a,b) => new { Expanded = a, Summary = b }).ToList();
foreach (var match in matches)
    match.Expanded.A = match.Summary.A;

这一步将两个集合——可能已修改的原始集合,以及新加载的扩展集合——按Id连接起来,然后遍历集合中的每个匹配,将原始行中的值复制到我们的扩展对象属性中。

这种方法的优点在于它能处理在读取这两个集合之间可能新增或删除行的情况。如果第二次扩展读取包含了原始集合中不存在的一行,我们仍然保留数据库中的值。如果原始列表中有一行已被删除或因某些原因未被读取(例如由于 .Where() 子句等),那么就没有要复制的值。连接后的“匹配”只是引用指针,因此不会在两个集合中创建项的副本。

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章