1

我习惯于使用身份列并为我生成 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 个不同的用户显示完全不同的数据。

4

2 回答 2

1

标识列的目的是在您需要列时在表中具有一些 ID,无论其他列值是什么,该列都是唯一的,并且 sql server 仅支持自动生成表中的一个标识列(没有多个或复杂的主自动生成键)。

如果您的每个小部件都不同(即使更多用户使用相同的小部件),那么这个主键是有意义的,但它不能在 sql server 中计算(除非您使用存储过程或其他数据库可编程性进行插入)。而且您只能使用 ef 读取自动生成的列(请参阅我应该如何访问实体框架代码中的计算列?)。

但是,如果您有更多的小部件类型(在此示例中,小部件类型为 1、2 和 3编辑: WidgetId 可能是表 WidgetType 的 FK,其中 Id 是自动生成的,因此您将拥有由 2 个自动生成的 ID 组成的 PK),您可以制作WidgetType(或 WidgetInstance、WidgetSomething 等等,描述不受用户影响的部分小部件的实体)表并在其中具有自动生成的 ID,并在此表中将其用作 FK 和主键的一部分。这将允许您在插入此表时自动生成并存在 UserId 和 WidgetId。

于 2012-03-15T22:20:34.863 回答
0

我讨厌过度简化并为此道歉。每当出现这个问题时,它都像是数据库设计问题。

小部件和用户都被视为主数据。小部件表应仅包含有关小部件的属性,而用户表应仅包含有关用户的属性。如果小部件与用户相关,则可能可以在小部件表中包含用户 ID,但小部件 ID 应该是身份,用户 ID 应该是 FK。

小部件和用户的链接应该在这些主表之外完成。如果它们与账单/发票相关,通常使用发票标题表(invoiceid、userid、日期时间、总成本、总价...)和发票行表(invoicelineid、invoiceid、widgetid、单位)的组合成本、单价、数量……)

随着数据量的增长,一点点标准化将确保您的应用程序能够扩展。此外,当需要将这些数据转化为商业智能时,这些牢固的关系将有助于提高数据质量。

于 2012-03-16T03:27:53.043 回答