在 TPH 上使用 EF 包含

Using EF Include on TPH

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

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

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

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

是否可以实现用户预加载?我已经尝试编写 _context.Notifications.Include('TargetUser') byt 它抛出异常。


更新。例外情况是:

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

试图将 修改为:

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

但仍然抛出相同的异常。

我不知道您将与多少实体合作。如果可能的话,我会尝试不在数据库服务器上进行合并:

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

已经尝试了很多不同的解决方案,并且 none 符合我的要求,因为我正在处理 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();

所有实体再一次列出并排序。

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

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

1.网络冗余

选择 ID 然后 运行 一个查询,用 ID 加载项目是多余的,只需 运行 this

就可以达到相同的效果

解决方案:

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 个 NotificationUsers,而不是 10 个用户(例如,其中只有 4 个是 NotificationUsers)。

解决方案:将 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。 Async/Await

当写一个 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);

并使该方法异步并且 return 也成为一项任务

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

你试过类似的东西(更新到最新版本的 C#):

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

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

  • 你类public吗?
  • 您的导航属性是否定义为 publicvirtual

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