我习惯于使用身份列并为我生成 pk。我想知道,有没有办法确定身份列的范围或以其他方式计算它在外键中是唯一的?
例如,使用标识列,表可能如下所示:
| UserId | WidgetId | <-- Table Widget, WidgetId is identity column & pk
---------------------
| 1 | 1 |
| 1 | 2 |
| 1 | 3 |
| 2 | 4 |
| 2 | 5 |
| 2 | 6 |
如果我想实现更像以下的东西怎么办:
| UserId | WidgetId | <-- Table Widget, both UserId and WidgetId compose the pk
---------------------
| 1 | 1 |
| 1 | 2 |
| 1 | 3 |
| 2 | 1 |
| 2 | 2 |
| 2 | 3 |
我知道 EF 允许使用数据库生成的列值,例如标识和计算。我的问题是,如何实现这样的计算列?
我意识到它可以在应用程序中完成,方法是找到用户拥有的最高 WidgetId 并递增一。但这感觉不是正确的方法。
我想也可以使用触发器来执行此操作,但是从 db 中添加触发器DbContext
会依赖于 RDBMS,对吧?
有没有更好的方法,可以减少对使用哪个后端 EF 的依赖?
更新
更清楚地说,在上面的模型中,Widget 和 User 之间是有目的的识别关系。UserId 是 User 表的主键,通过将 FK to User 作为 Widget PK 的一部分,这意味着 Widget 永远不会在用户之间共享。当一个用户被删除时,所有的小部件都会被删除。单独通过 WidgetId 查询 Widget 是没有意义的;要查询特定的 Widget,UserId 和 WidgetId 参数都是必需的。
我正在探索这个,因为我记得在 Evans DDD 中读到聚合中的非根实体 id 只需要在聚合中是唯一的。上面是这种情况的一个例子:
根以外的实体具有本地标识,但该标识只需要在 AGGREGATE 内是可区分的,因为没有任何外部对象可以从根实体的上下文中看到它。(领域驱动设计,埃文斯)
更新 2
在 Gorgan 的回答之后,我在我的小部件工厂类中实现了以下内容。这是实现上述所需结果的正确方法吗?
public class WidgetFactory : BaseFactory
{
private object _sync = new object();
internal WidgetFactory(IWriteEntities entityWriter) : base(entityWriter)
{
// my DbContext class implements the IWriteEntities interface
}
public Widget CreateOrUpdate(User user, int? widgetId, string prop1,
bool prop2, string prop3)
{
Widget widget = null;
if (!widgetId.HasValue)
{
// when widgetId is null do a create (construct & hydrate), then
EntityWriter.Insert(widget); // does DbEntityEntry.State = Added
}
else
{
// otherwise query the widget from EntityWriter to update it, then
EntityWriter.Update(widget); // does DbEntityEntry.State = Modified
}
// determine the appropriate WidgetId & save
lock (_sync)
{
widget.WidgetId = widgetId.HasValue ? widget.WidgetId
: EntityWriter.Widgets.Where(w => w.UserId == user.Id)
.Max(w => w.WidgetId) + 1;
EntityWriter.SaveChanges(); // does DbContext.SaveChanges()
}
return widget;
}
}
小部件
我想我不应该掩饰这个词。实际的实体是 WorkPlace。我正在开发一个应用程序来跟踪我一年中的工作地点,因为我必须每年提交一份行程表和我的市政退税表。完成后,我计划将其发布到云端,以便其他人可以免费使用。但我当然希望他们的工作场所与我的完全隔离。
使用匿名身份自动为每个访问者创建用户/ Request.AnonymousID
(稍后会有注册功能,但没有必要尝试演示)。Web 应用程序将具有诸如 /work-places/1、/work-places/2 等的静态 url。因为每个请求都保证有一个用户,所以不需要在 url 中标识用户 ID。Request.AnonymousID
我可以从User.Identity.Name
.
如果 WorkPlaceId 是一个身份列,我的控制器操作首先需要检查以确保用户在显示工作场所之前拥有它。否则,我可以破解 URL 以查看每个用户在系统中设置的每个 WorkPlace。通过使 WorkPlaceId 仅对用户唯一,我不必担心。URL /work-places/1 将向 2 个不同的用户显示完全不同的数据。