【问题标题】:Using EF Include on TPH在 TPH 上使用 EF 包含
【发布时间】:2016-05-06 10:40:11
【问题描述】:

我已经使用 THP 实现了具有简单继承的代码优先数据库架构:

我需要查询所有类型的所有通知。 NotificationUser 表中的TargetUser 属性是一个关联。 我正在尝试执行下一个代码:

var notifications = _context.Notifications;
foreach (var notification in notifications)
{
    Debug.WriteLine((notification is NotificationUser)? ((NotificationUser) notification).TargetUser?.Name : "-");
}

在数据库属性TargetUser 设置为正确的外键,但在代码中我没有得到任何结果。延迟加载已启用。

是否可以用户急切加载?我已经尝试写 _context.Notifications.Include('TargetUser') 字节它会引发异常。


更新。例外是:

A specified Include path is not valid. The EntityType 'Core.Concrete.NotificationBase' does not declare a navigation property with the name 'TargetUser'.

尝试将this answer修改为:

var notifications = _context.Notifications.OfType<NotificationUser>()
                .Include(n => n.TargetUser)
                .Cast<NotificationBase>()
                .Union(_context.Notifications.OfType<NotificationPlace>()

但仍然抛出相同的异常。

【问题讨论】:

  • Not working context.Notifications.Include('TargetUser') 看起来有一些一般性错误。你说有例外?我可以看看吗?
  • @AlexanderHaas,尝试再次使用 include 执行代码,并且属性已正确加载。好像我有一些语法错误。还是谢谢!
  • @AlexanderHaas,好的,它不起作用。我在查询中没有注意到 .OfType() 。没有它会引发异常。用消息更新问题。
  • 这个问题和这个一样 => stackoverflow.com/questions/27623637/…
  • 您包含一个属性 (TargetUser),然后您尝试将检索到的实体转换为没有包含属性的类型。所以确实它在那里抛出了一个异常,正常的行为。

标签: c# entity-framework tph


【解决方案1】:

我知道这是一个旧线程,但我仍然想为寻求相同解决方案的人发布一些改进。

1.网络冗余

选择 Ids 然后运行查询,加载带有 Ids 的项目是多余的,只需运行它就可以达到相同的效果

解决方案:

var userNotifications = _context.Notifications
    .OrderByDescending(n => n.DateTime)
    .Skip(offset)
    .Take(limit)
    .OfType<NotificationUser>()
    .Include(n => n.TargetUser)
    .Include(n => n.TargetUser.Images)
    .ToList();

这样,您无需等待 2 个数据库连接,而只需等待一个。还可以节省一些流量。

2。对忽略的实体进行分页?

有人会假设,这种特定方法仅用于查看继承类型的实体,因此我希望 Skip 和 Take 仅直接用于所述类型的实体。例如我想跳过 10 个 NotificationUser,而不是 10 个用户(例如,其中只有 4 个是 NotificationUser)。

解决方案:将 ofType 移到查询的上方

var userNotifications = _context.Notifications
    .OfType<NotificationUser>()
    .OrderByDescending(n => n.DateTime)
    .Skip(offset)
    .Take(limit)
    .Include(n => n.TargetUser)
    .Include(n => n.TargetUser.Images)
    .ToList();

3。异步/等待

在编写 API 时,您应该考虑使用 async/await,因为它不会阻塞线程,从而浪费更少的资源(如果您还没有使用它,这可能需要您重写很多现有代码尽管)。

请研究 async/await 的优点,并在等待结果等场景中使用它们。

解决方案:改变这个

private List<NotificationUser> GetNotificationUsers(int offset, int limit)
    {
        return _context.Notifications
                .OfType<NotificationUser>()
                .OrderByDescending(n => n.DateTime)
                .Skip(offset)
                .Take(limit)
                .Include(n => n.TargetUser)
                .Include(n => n.TargetUser.Images)
                .ToList();
    }

进入这个

private async Task<List<NotificationUser>> GetNotificationUsersAsync(int offset, int limit)
    {
        return await _context.Notifications
                .OfType<NotificationUser>()
                .OrderByDescending(n => n.DateTime)
                .Skip(offset)
                .Take(limit)
                .Include(n => n.TargetUser)
                .Include(n => n.TargetUser.Images)
                .ToListAsync();
    }

注意: 然后,您还必须从

更改使用此方法的任何地方
var x = GetNotificationUsers(skip, take);

var x = await GetNotificationUsersAsync(skip, take);

并使该方法异步并返回一个任务

【讨论】:

    【解决方案2】:

    我不知道您将使用的实体数量。如果可能,我会尝试不在数据库服务器上进行联合:

    var userNotifications = _context.Notifications.OfType<NotificationUser>()
                                    .Include(n => n.TargetUser).ToList();
    var placeNotifications = _context.Notifications.OfType<NotificationPlace>().ToList();
    var notifications = userNotifications.Union(placeNotifications);
    

    https://stackoverflow.com/a/27643393/2342504

    【讨论】:

    • 实际上,它是 web api 的一部分,实体的数量可能非常大,所以在我的情况下将它们全部加载到内存中并不是一个好习惯。
    【解决方案3】:

    已经尝试了很多不同的解决方案,但没有一个符合我的要求,因为我正在开发一个 API,并且查询必须支持分页并对数据库发出持续请求(而不是将所有实体加载到内存中)。

    终于找到了一个解决方案,也许不是最好的,但现在已经足够了。 首先,我请求一部分有序数据(分页逻辑):

    var notifications = _context.Notifications
                .OrderByDescending(n => n.DateTime)
                .Skip(offset)
                .Take(limit);
    

    (此时我对任何属性都不感兴趣)接下来,我将获取每个实体类型的已加载项的 ID:

    var ids = notifications.OfType<NotificationUser>().Select(n => n.Id).ToList();
    

    最后加载包括所有属性的特定实体:

    var userNotifications = _context.Notifications.OfType<NotificationUser>()
                 .Include(n => n.TargetUser)
                 .Include(n => n.TargetUser.Images)
                 .Where(n => ids.Contains(n.Id))
                 .ToList();
    

    所有实体都进入列表并再次排序。

    这里有很多不好的东西,希望有人能提供更好的解决方案。

    【讨论】:

      【解决方案4】:

      如果我理解得很好,您希望在相关时从表中获取所有实体 + 属性 TargetUser(对于 NotificationUser 类型的实体)。您说“启用延迟加载”,这是一件好事。

      您尝试过类似(更新到最新版本的 C#):

      var notifications = _context.Notifications;
      foreach (var notification in notifications)
      {
          Debug.WriteLine((notification is NotificationUser notificationUser) 
              ? notificationUser.TargetUser?.Name 
              : "-");
      }
      

      如果您没有得到任何结果,这可能意味着您的实体配置不当,无法使用延迟加载:

      • 你们的课程是公开的吗?
      • 您的导航属性是否定义为publicvirtual

      见:https://docs.microsoft.com/en-us/ef/ef6/querying/related-data#lazy-loading

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2014-11-21
        • 2021-11-21
        • 2023-04-04
        • 1970-01-01
        • 2020-10-03
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多