“ReferentialConstraint 中的依赖属性映射到存储生成的列。”在持久计算列上(EntityFramework DB 首先)
Posted
技术标签:
【中文标题】“ReferentialConstraint 中的依赖属性映射到存储生成的列。”在持久计算列上(EntityFramework DB 首先)【英文标题】:"A dependent property in a ReferentialConstraint is mapped to a store-generated column." on a persisted computed column (EntityFramework DB first) 【发布时间】:2016-12-07 12:30:40 【问题描述】:我已经根据文章Implementing Table Inheritance in SQL Server 在我的 SQL-Server 中实现了一个模拟表继承结构。
除了使用简单的 1 到 0...1 关系这一事实之外,您还可以为类型表创建另一个约束,该类型表列出了基表的所有可能的子类型,如“一对一建模”段落中的文章中所述约束”。
您的每个子表都包含一个 TYPE 字段,该字段的 ComputedColumnSpecification 带有一个表示类型表中类型 ID 的持久数字。由于 TYPE 字段是约束的一部分,它将确保只能为基础数据集创建一个子项。
为了更好地理解,我创建了一个示例数据库,用于描述匹配的 ASP.NET 解决方案的问题。要在本地环境中复制问题,请在执行此脚本之前创建一个名为“PLAYGROUND”的数据库:
USE [PLAYGROUND]
GO
/****** Object: Table [dbo].[USER] ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[USER](
[ID] [int] IDENTITY(1,1) NOT NULL,
[TYPE__ID] [int] NOT NULL,
[Enabled] [bit] NOT NULL,
[Username] [nvarchar](32) NOT NULL,
[Password] [nchar](32) NOT NULL,
[Email] [nvarchar](255) NOT NULL,
CONSTRAINT [PK_USER] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
/****** Object: Table [dbo].[NATURAL_USER] ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE TABLE [dbo].[NATURAL_USER](
[ID] [int] NOT NULL,
[TYPE] AS ((1)) PERSISTED NOT NULL,
[BirthDate] [date] NOT NULL,
CONSTRAINT [PK_NATURAL_USER] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
SET ANSI_PADDING OFF
GO
/****** Object: Table [dbo].[JURIDICAL_USER] ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE TABLE [dbo].[JURIDICAL_USER](
[ID] [int] NOT NULL,
[TYPE] AS ((2)) PERSISTED NOT NULL,
[CompanyName] [nvarchar](256) NOT NULL,
[RegistrationNo] [nvarchar](max) NOT NULL,
[Description] [nvarchar](max) NOT NULL,
CONSTRAINT [PK_LEGAL_USER] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
SET ANSI_PADDING OFF
GO
/****** Object: Table [dbo].[USER_T] ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[USER_T](
[ID] [int] IDENTITY(1,1) NOT NULL,
[TYPE] [nvarchar](32) NOT NULL,
CONSTRAINT [PK_USER_T] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
/****** Object: Index [IX_USER] ******/
ALTER TABLE [dbo].[USER] ADD CONSTRAINT [IX_USER] UNIQUE NONCLUSTERED
(
[Username] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
/****** Object: Index [PK_USER_TYPE] ******/
CREATE UNIQUE NONCLUSTERED INDEX [PK_USER_TYPE] ON [dbo].[USER]
(
[ID] ASC,
[TYPE__ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
SET ANSI_PADDING ON
GO
/****** Object: Index [IX_USER_T] ******/
ALTER TABLE [dbo].[USER_T] ADD CONSTRAINT [IX_USER_T] UNIQUE NONCLUSTERED
(
[TYPE] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
SET ANSI_PADDING ON
GO
/****** TYPE DATA ******/
SET IDENTITY_INSERT [dbo].[USER_T] ON
GO
INSERT [dbo].[USER_T] ([ID], [TYPE]) VALUES (2, N'JURIDICAL_USER')
GO
INSERT [dbo].[USER_T] ([ID], [TYPE]) VALUES (1, N'NATURAL_USER')
GO
SET IDENTITY_INSERT [dbo].[USER_T] OFF
GO
/****** Contraints ******/
ALTER TABLE [dbo].[JURIDICAL_USER] WITH CHECK ADD CONSTRAINT [FK_JURIDICAL_USER___USER] FOREIGN KEY([ID])
REFERENCES [dbo].[USER] ([ID])
GO
ALTER TABLE [dbo].[JURIDICAL_USER] CHECK CONSTRAINT [FK_JURIDICAL_USER___USER]
GO
ALTER TABLE [dbo].[JURIDICAL_USER] WITH CHECK ADD CONSTRAINT [FK_JURIDICAL_USER___USER___TYPEVALIDATION] FOREIGN KEY([ID], [TYPE])
REFERENCES [dbo].[USER] ([ID], [TYPE__ID])
GO
ALTER TABLE [dbo].[JURIDICAL_USER] CHECK CONSTRAINT [FK_JURIDICAL_USER___USER___TYPEVALIDATION]
GO
ALTER TABLE [dbo].[NATURAL_USER] WITH CHECK ADD CONSTRAINT [FK_NATURAL_USER___USER] FOREIGN KEY([ID])
REFERENCES [dbo].[USER] ([ID])
GO
ALTER TABLE [dbo].[NATURAL_USER] CHECK CONSTRAINT [FK_NATURAL_USER___USER]
GO
ALTER TABLE [dbo].[NATURAL_USER] WITH CHECK ADD CONSTRAINT [FK_NATURAL_USER___USER___TYPEVALIDATION] FOREIGN KEY([TYPE])
REFERENCES [dbo].[USER_T] ([ID])
GO
ALTER TABLE [dbo].[NATURAL_USER] CHECK CONSTRAINT [FK_NATURAL_USER___USER___TYPEVALIDATION]
GO
ALTER TABLE [dbo].[USER] WITH CHECK ADD CONSTRAINT [FK_USER___USER_T] FOREIGN KEY([TYPE__ID])
REFERENCES [dbo].[USER_T] ([ID])
GO
ALTER TABLE [dbo].[USER] CHECK CONSTRAINT [FK_USER___USER_T]
GO
USE [master]
GO
ALTER DATABASE [PLAYGROUND] SET READ_WRITE
GO
表USER
是基表,表NATURAL_USER
和JURIDICAL_USER
是它的子表。 USER_T
是USER
的类型表。
现在,在我使用 EntityFramework 6 的 ASP.NET 应用程序中,我尝试通过以下方式创建新用户:
using (PLAYGROUNDEntities model = new PLAYGROUNDEntities())
USER user = new USER();
user.Username = "admin";
user.Password = "RANDOMHASH#123456";
user.Email = "admin@example.org";
user.NATURAL_USER = new NATURAL_USER();
user.NATURAL_USER.BirthDate = new DateTime(1980, 01, 01);
model.USER.Add(user);
model.SaveChanges();
在model.SaveChanges();
我得到了例外:
ReferentialConstraint 中的依赖属性映射到存储生成的列。列:“类型”。
示例解决方案:https://dl.dropboxusercontent.com/u/55589036/zzzOther/Playground.zip(示例代码在Default.aspx.cs
的Page_Load
中。
我了解,EntityFramework 尝试设置列字段并失败,因为它是存储生成的(持久化)。这甚至发生在我设置user.NATURAL_USER.TYPE = 1;
时。
我试图覆盖OnModelCreating
以附加我自己的规则并将TYPE
列定义为Computed
,但从未调用OnModelCreating
,因为我做了EDMX-after 和我想坚持下去。
所以,这个实体模型是基于数据库生成的,我想保持这种方式,而且我不想在每次再次更新模型时编辑任何代码。
另外,我认为表继承概念在数据库层实现得非常好,因为它不使用触发器。我想让它保持无触发。
我该如何解决这个问题?
【问题讨论】:
为什么不直接使用 EF 和它自己的继承(TPT 或 TPH)?您对使用 DB First 有项目限制吗? @bubi TPT 或 TPH 方法只是 EF 上的继承系统,仍然允许使用纯 SQL 在数据库上绕过它。 TPT/TPH 要求我在重新创建 EDMX 文件后在设计器中建立关联。如果我在这里错了,请纠正我。 TPT 和 TPH 是 [严格] 与 Code First 相关的方法。如果您正在处理现有数据库(DB First + EDMX)不是一个好方法。您的情况是 TPH(单表 + 鉴别器字段),但如果 DB + EDMX 是约束标准,则 EF TPH 不是一个好方法。 @bubi 对,这是正确的。不过,这将是代码优先的解决方案。不幸的是,我必须坚持数据库优先。 【参考方案1】:我对 EF 一无所知,但我会将您的 TYPE
列创建为普通列,不计算,不持久。
然后我将它们的默认值设置为所需的值并添加一个CHECK
约束以确保它无法更改。
您设置外键的其余 T-SQL 脚本保持不变。
例如,NATURAL_USER
看起来像这样:
CREATE TABLE [dbo].[NATURAL_USER](
[ID] [int] NOT NULL,
[TYPE] [int] NOT NULL,
[BirthDate] [date] NOT NULL,
CONSTRAINT [PK_NATURAL_USER] PRIMARY KEY CLUSTERED
(
[ID] ASC
))
GO
ALTER TABLE [dbo].[NATURAL_USER] WITH CHECK
ADD CONSTRAINT [CK_NATURAL_USER] CHECK (([TYPE]=(1)))
GO
ALTER TABLE [dbo].[NATURAL_USER]
CHECK CONSTRAINT [CK_NATURAL_USER]
GO
ALTER TABLE [dbo].[NATURAL_USER]
ADD CONSTRAINT [DF_NATURAL_USER_TYPE] DEFAULT ((1)) FOR [TYPE]
GO
【讨论】:
【参考方案2】:我在实施该方法时犯了一个严重的错误,但它以前有效。我不小心弄乱了约束FK_NATURAL_USER___USER___TYPEVALIDATION
。
它应该像FK_JURIDICAL_USER___USER___TYPEVALIDATION
约束一样构建。
EF 能够处理持久化的列。问题是它试图写入 [USER_T]
的 PK,这根本不应该是约束的一部分。
对于所有在这件事上浪费时间的人,我深表歉意。
【讨论】:
以上是关于“ReferentialConstraint 中的依赖属性映射到存储生成的列。”在持久计算列上(EntityFramework DB 首先)的主要内容,如果未能解决你的问题,请参考以下文章