【问题标题】:How to do computed column values in SQL Server with Entity Framework如何使用实体框架在 SQL Server 中计算列值
【发布时间】:2012-03-15 19:37:34
【问题描述】:

我习惯于使用身份列并为我生成 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 并加一。但这感觉不是正确的方法。

我想也可以使用触发器来执行此操作,但是从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。我正在开发一个应用程序来跟踪我一年中的工作地点,因为我必须每年提交一份行程表和我的市政退税表。完成后,我计划将其发布到云端,以便其他人可以免费使用。但我当然希望他们的 WorkPlace 与我的完全隔离。

使用匿名标识/Request.AnonymousID为每位访客自动创建用户(稍后会有注册功能,但没有必要尝试演示)。 Web 应用程序将具有诸如 /work-places/1、/work-places/2 等的静态 url。因为每个请求都保证有一个用户,所以不需要在 url 中标识用户 ID。当User.Identity.Name 中没有任何内容时,我可以识别来自Request.AnonymousID 的用户。

如果 WorkPlaceId 是一个身份列,我的控制器操作首先需要检查以确保用户拥有工作场所,然后再显示它。否则,我可以破解 URL 以查看每个用户在系统中设置的每个 WorkPlace。通过使 WorkPlaceId 仅对用户唯一,我不必担心。 URL /work-places/1 将向 2 个不同的用户显示完全不同的数据。

【问题讨论】:

  • 如果你使用的是 EF 5,你应该用它来标记它。
  • 太棒了,上次我检查它还没有创建。感谢您的提醒。
  • 是的,就是这样,但我很想知道更多关于这个小部件代表什么,因为这看起来不自然,并且必须有更好的方法来建模它。
  • 我不应该伪装 Widget... 我的意思是“任何在用户中唯一的任意实体”。在应用程序中,实际实体是WorkPlace。我已经更新了我的问题。感谢您的帮助。
  • 是的,这是第三段的候选,我已经澄清了:)

标签: sql-server ef-code-first primary-key calculated-columns entity-framework-5


【解决方案1】:

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

如果您的每个小部件都不同(即使相同的小部件被更多用户使用),那么这个主键是有意义的,但它不能在 sql server 中计算(除非您使用存储过程或其他 db 可编程性进行插入) .而且您只能使用 ef 读取自动生成的列(请参阅How should I access a computed column in Entity Framework Code First?)。

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

【讨论】:

  • 我认为这可能是答案,但你在第 3 段有点迷失了我。我已经更新了问题以澄清。如果必须计算这样的WidgetId 值,我听到您说它必须发生在 EF 层或更高层,除非我想创建一个存储过程/触发器(我不想)。那么,什么是好的方法呢?我可以查询用户的最大小部件 ID,加一,在实体上设置属性并 SaveChanges ......但即使源代码中的 2 行是相邻的,如果 2 不存在并发冲突的机会每个线程都试图同时为同一个用户插入一个 Widget?
  • 是的,您需要在线程之间有同步部分来进行保存。我认为第 3 段是最好的解决方案,但如果一个用户可以拥有 5 个“相同”小部件实例,则不适用。您是否在某些小部件(即名称是“时钟”或“天气”)之间共享了用户未更改的任何内容?如果不是,并且您唯一关心的是确保没有用户的小部件不存在,您只能将UserId FK NOT NULL,并且当用户被删除时,小部件必须被删除。
  • 我选择“识别关系”的主要原因与当您尝试从集合中删除实体时 EF 的行为方式有关(即User.Widgets.Remove(User.Widgets.First())。默认情况下,它会尝试设置FK 为 null。如果 FK 不可为空,则 EF 会引发异常。但是,如果 FK 是 PK 的一部分,则 EF 会改为删除实体。
  • 您希望您的 Widget 成为 weak 实体?您是否尝试将 WidgetId 和 UserId 作为 PK 的一部分,但将 WidgetId 设置为自动生成(身份)?我知道在这种情况下 WidgetId 本身可以是 PK,但这不应阻止您将两个字段都标记为 PK :)
  • 我已将我的问题扩充为完全明确。对困惑感到抱歉。我真的不想对依赖实体 pk 的任何部分使用标识列。
【解决方案2】:

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

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

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

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

【讨论】:

  • 再一次,我想也许我应该更清楚我的问题。这两个实体没有什么宏大的规模,这是一个简单的个人应用程序,我正在努力熟悉 VS11、EF5、MVC4 等。对于这两个实体,有明确的1 User .. n Widgets 关系。我认为您建议使用“动名词”表来映射m Users .. n Widgets。在我的模型中,如果没有用户,小部件就无法存在...... UserId 是其身份的一部分。我相信我已经正确地对其进行了规范化,并且添加一个单独的表来映射关系听起来像是一种复杂化而不是简化。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-05
  • 2018-07-12
  • 1970-01-01
  • 2015-01-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多