如何首先在 EF 代码中创建持久计算列?
Posted
技术标签:
【中文标题】如何首先在 EF 代码中创建持久计算列?【英文标题】:How to make persisted computed column in EF code first? 【发布时间】:2013-01-11 17:30:34 【问题描述】:如何使该列与数据库中的 PERSISTED COMPUTED 列相似?
我当前的尝试(它加载所有 CompCol 行,种子中为 null):
public class Call
public Call()
[Key]
public int Id get; set;
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public string CompCol
get
return "ABC-" + Convert.ToString(Id).PadLeft(5, '0');
protected set
【问题讨论】:
@GlenLittle — 你能详细说明一下吗?您的描述似乎表明它不仅仅是在语句中添加关键字PERSISTED
。 (有关更多信息,请参阅下面的答案。)
@InteXX - 我记得在(非常)旧版本的 SQL 中,“持久”或“计算”的某些方面需要企业版。但是,我想我记错了。我删除了我的评论!
【参考方案1】:
我找到的解决方案是:
确保关闭自动迁移。这样 VS 会生成一个脚本(流利的 api 代码)供我们进一步定制,而不仅仅是运行它。所以在配置类中:
public Configuration()
AutomaticMigrationsEnabled = false;
将字段添加到类并将其设置为计算字段,setter 是私有的,因为我们显然无法写入计算字段:
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public string BreakdownNo get; private set;
然后在 Package Manager Console 中执行 add-migration [xyz-name]
以生成迁移代码,该代码将出现在具有给定名称的迁移文件夹下。
在迁移中注释掉 Up()
中的代码并添加自定义 SQL,如下所示:
public override void Up()
//AddColumn("dbo.Calls", "BreakdownNo", c => c.String());
Sql("ALTER TABLE dbo.Calls ADD BreakdownNo AS ('BD'+RIGHT('00000'+ CAST(Id AS VARCHAR), 6))");
在 PM 中执行update-database
,它应该会正确添加计算列。
进一步说明:如果您弄错了公式,那么您将不得不通过执行 update-database -targetMigration: [name of migration to go back to]
来恢复迁移,然后执行另一个 add-migration name
并在那里修改您的公式,以更新结束-数据库。可能有更好的方法,但这是我发现和使用的。
然而,我还没有找到使该字段持续存在的方法。
【讨论】:
这是一个很好的例子,说明要让自动迁移正常工作是多么困难。 您不能通过在您的ALTER TABLE
语句中简单地包含关键字PERSISTED
来将其设置为持久化吗? Documentation.
更新:是的,这行得通。我们有一个持久的计算列。例如:ALTER TABLE [LogEntries] ADD [ReportTime] AS (DATEADD(HOUR, -7, [LogTime])) PERSISTED
【参考方案2】:
为什么不这样调用sql:
public class demo
void demoMethod()
Model1 model = new Model1();//Model1 : DbContext
model.Database.ExecuteSqlCommand("alter table Results drop column Total; alter table Results add Total AS (Arabic + English + Math + Science)");
【讨论】:
【参考方案3】:我使用接受的答案中提出的方法遇到了一些麻烦。我提供了一个对我有用的替代解决方案。
我在运行这个查询时遇到了错误:
oDb.LogEntries.SingleOrDefault(Function(LogEntry) LogEntry.LogTime = dDate)
错误信息:
“LogEntry”上的“MinutesOffline”属性无法设置为“System.Int32”值。您必须将此属性设置为“System.Single”类型的非空值。
正如我们所见,EF 6.2 正在尝试将值写入属性。这是否是由于 EF 内部试图写入Private Set
,我不知道。它几乎看起来像。但最终结果才是最重要的:语句失败。
我没有将列设置为DatabaseGeneratedOption.Computed
,而是完全忽略了它:Builder.Entity(Of LogEntry).Ignore(Function(LogEntry) LogEntry.MinutesOffline)
。
这使我能够创建一个只读属性:
Public ReadOnly Property MinutesOffline As Single
Get
Return IIf(Me.Scale < 1, 5, 0)
End Get
End Property
它还有一个额外的好处,就是我们不必在生成的迁移中注释掉任何行。
我们仍然需要在Up()
中进行自定义Sql()
调用:
ALTER TABLE [LogEntries] ADD [MinutesOffline] AS (IIF([Scale] < 1, 5, 0)) PERSISTED
...PERSISTED
关键字在这里确实有效。这将成为一个持久计算列。
YMMV
--编辑--
我发现了为什么会出现转换错误;它与迁移无关,与我的代码有关。我在创建时没有正确转换计算列:
ALTER TABLE [LogEntries] ADD [MinutesOffline] AS (IIF([Scale] < 1, 5, 0)) PERSISTED
这样做的正确语法是这样的:
ALTER TABLE [LogEntries] ADD [MinutesOffline] AS (CAST((IIF([Scale] < 1, 5, 0)) AS REAL)) PERSISTED
因此,我已恢复 Ignore()
调用并将所有内容切换回接受的答案中建议的方法。
向JotaBe 致敬以寻求帮助。
【讨论】:
以上是关于如何首先在 EF 代码中创建持久计算列?的主要内容,如果未能解决你的问题,请参考以下文章
使用 CASE WHEN 和 IN 在 SQL 中创建计算列