如何使EF Core Include()不跟踪实体?

时间:2019-03-19 11:27:31

标签: c# entity-framework .net-core entity-framework-core

TLDR :我想知道是否可以在EF核心的单个查询中为一种实体类型使用不同的“包含逻辑”。

编辑:为清楚起见,我在标题中说的是跟踪实体,因为我认为这就是EF所做的事情,但是BitConverter在此之前没有任何用处。 / p>

当前的问题是在相对较小的React应用程序上,该应用程序以ASP.NET Core Web API应用程序为后盾。我要完成的是,当我调用.AsNoTracking()时,我希望应用程序给我一个像这样的json:

api/parents

我的设置如下:

EF查询

[
  {
    "id": "parentid1",
    "extraProperty": "value",
    "children": [
      {
        "id": "childid1",
        "parent": {
          "id": "parentid1",
          "extraProperty": "value"
        }
      }
    ]
  }
]

之后,我将实体映射到dto的AutoMapper进行了很多操作。我还为该应用程序使用了默认的Json序列化器(Newtonsoft)。它使用(from p in _context.Parents select p) .Include(p => p.Children) .ThenInclude(c => c.Parent) .ToList(); 配置。

通过此设置,api调用会返回以下响应:

SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore

您会看到“忽略”了父级的自我引用。

我想出的解决方案是,我应该配置Newtonsoft以“序列化”参考循环,然后我尝试了一下。并抛出该错误,因为上面提供的EF查询返回的实体列表如下所示:

[
  {
    "id": "parentid1",
    "extraProperty": "value",
    "children": [
      {
        "id": "childid1"
      }
    ]
  }
]

如果查看我的Include调用,它会明确表示对于我的子对象,我只需要父对象,而在该父对象中没有其他引用。

据我所知,EF在遇到该查询的任何父对象时会使用初始设置[ { "id": "parentid1", "extraProperty": "value", "children": [ { "id": "childid1", "parent": { "id": "parentid1", "extraProperty": "value", "children": [ { "id": "childid1", "parent": { "id": "parentid1", "extraProperty": "value" ... } } ] } } ] } ] 。因此,当它在.Include(child).ThenInclude(parent)对象中找到一个Parent对象时,它使用Child而不是不使用包含(在ThenInclude之后没有任何包含)。 / p>

如果不需要,我不想通过使用映射器或序列化器进行破解来解决此问题。我想知道我所寻找的是否可能:在单个查询中为一个实体类型使用不同的“包含逻辑”。

2 个答案:

答案 0 :(得分:1)

ThenInclude(parent)调用是多余的,因为您是从父级开始的-物化的子级将已经具有其Parent属性。您需要按照第一条评论中的@IvanStoev定制序列化。

另一种方法是按父级查询子级,然后投影所需的结果:

var parents = _context.Children
    .Include( c => c.Parent )
    .GroupBy( c => c.Parent )
    .ToArray()
    .Select( g => 
        {
            // assign children to g.Key (the parent object)
            g.Key.Children = g.ToArray(); // I don't know type of `Children` property
            // select parent
            return g.Key;
        } );

答案 1 :(得分:0)

我决定从最前端解决问题,因为其他解决方案对我不起作用。为了将来参考,我将发布我的旅程:

EF

起初,在数据访问层上解决问题似乎是合乎逻辑的,因为我只是错过了一个非常基本的东西。我意识到在评论后采取这种方式是不合逻辑的。基本上,当您从EF获取实体并且您的对象具有引用时,每次您拥有一个以上对象时,它们都是同一对象(通过引用而不是值)。因此,期望它们中包含不同的数据(在我的情况下,其中一个具有一些细节而另一个则没有)是不合逻辑的。

那之后,我想也许我可以在制图阶段解决这个问题。我尝试针对不同的场景进行不同的配置文件/配置,但它变得非常难看。然后,我认为也许使用单个Profile并执行一些AfterMap()逻辑就可以了(首先加载所有数据,然后剥离掉不需要的数据)。但是,除非您做一些丑陋的事情,否则适用相同的原理。 AutoMapper还会保留引用,因此当您修改child.Parent对象时,原始的Parent也将被修改。我本可以使用克隆并做一些其他的技巧,但就像我说的那样,这在我看来很丑。

目前,我正在React应用程序上手动执行任务。当我从服务器获取数据时,我只是做

parent.children.forEach(c => c.parent = parent);

请牢记parent.children.parentnull。实体对象或DTO并非如此,序列化之后就是这种情况。因为我为串行器设置了ReferenceLoopHandling.Ignore

这解决了我的应用程序所有问题,但我的api似乎不完整。返回的JSON似乎丢失了某些内容(也许就是我,IDK)。

总而言之,从长远来看,我考虑在不同情况下使用多个具有相同对象类型的DTO,或者出于完整性考虑,在api中返回id字段。

感谢所有评论和答复。