在 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吗?
- 您的导航属性是否定义为
public
、virtual
?
参见:https://docs.microsoft.com/en-us/ef/ef6/querying/related-data#lazy-loading
我已经使用 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吗?
- 您的导航属性是否定义为
public
、virtual
?
参见:https://docs.microsoft.com/en-us/ef/ef6/querying/related-data#lazy-loading