在TPT模式下使用EF Include

4
我已经使用THP实现了简单继承的代码优先数据库架构: Database diagram 我需要查询所有类型的所有通知。 在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'.

尝试修改这个答案


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

但仍然会抛出相同的异常。


不工作的上下文。Notifications.Include('TargetUser') 看起来有一些通用错误。你说有一个异常?我能看到它吗? - Alexander Haas
@AlexanderHaas,我又尝试了一下使用include执行代码,属性已经正确加载了。看来我之前有一些语法错误。不管怎样,还是谢谢你! - Ivvan
@AlexanderHaas,好的,它没有起作用。我没有注意到我的查询中缺少.OfType<NotificationUser>()。没有它会抛出异常。已更新问题并添加了消息。 - Ivvan
这个问题与此相同 => https://dev59.com/dYbca4cB1Zd3GeqPUVRZ - CodeNotFound
你包含了一个属性(TargetUser),然后尝试将检索到的实体转换为不包含该属性的类型。因此,它确实会抛出异常,在这种情况下是正常行为。 - paradise
4个回答

2

我知道这是一个旧帖子,但我仍然想为寻找相同解决方案的人发布一些改进意见。

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个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. 异步/等待

在编写API时,应考虑使用异步/等待,因为它不会阻塞线程,从而浪费更少的资源(如果您尚未使用它,则可能需要重写大量现有代码)。

请仔细研究异步/等待的优点,并在等待结果等场景中使用它们。

解决方案:更改为:

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

并将该方法改为异步方法,同时返回一个任务(task)。


1

我不知道你要处理的实体数量。如果可能的话,我建议不要在数据库服务器上进行联合操作:

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

请查看https://dev59.com/dYbca4cB1Zd3GeqPUVRZ#27643393


实际上,它是 Web API 的一部分,实体数量可能非常大,因此在我的情况下将它们全部加载到内存中并不是一个好的做法。 - Ivvan

0

已经尝试了很多不同的解决方案,但都不符合我的要求,因为我正在开发一个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();

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

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


0
如果我理解正确的话,您想从表中获取所有实体以及相关属性TargetUser(对于类型为NotificationUser的实体)。您说“启用了延迟加载”,这是一件好事。
您尝试了类似以下代码(更新到最新版本的C#):
var notifications = _context.Notifications;
foreach (var notification in notifications)
{
    Debug.WriteLine((notification is NotificationUser notificationUser) 
        ? notificationUser.TargetUser?.Name 
        : "-");
}

如果您没有获得任何结果,则可能意味着您的实体配置不良,无法与惰性加载一起使用:

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

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接