这是Entity Framework 6.1.3
SQL Server表有一个双列复合键。
ID INT Identity(1,1) NOT NULL
VERSION INT NOT NULL
插入新记录是有效的,因为我没有在我的对象上设置ID;我只设置了VERSION。 因此,新记录将如下所示:
ID VERSION
1 1
完美!数据库生成ID,因为列配置了Identity,我的模型使用[DatabaseGenerated(DatabaseGeneratedOption.Identity)]进行修饰。
但是现在我需要插入另一行具有相同ID但不同的VERSION;因此复合键。所以我希望第二行是:
ID Version
1 1
1 2 <- second row has same ID and different version
我确实需要这两种方式,因为有一种情况是数据库应该自动生成一个新的ID,而另一种情况是我有相同的ID但是VERSION不同。
问题: 因为我的Code-First模型具有使用DatabaseGeneratedOption.Identity配置的ID,所以当我在我的对象上设置ID属性时,我的SaveChanges生成插入语句而没有 ID!
(VS中的诊断工具显示实体框架生成了此语句)
ADO.NET: Execute Reader "INSERT [dbo].[T1]([Version], ... VALUES (@0, ...)
请注意遗漏身份证明。因为我在我的对象上显式设置了ID,所以我希望看到这个语句。
INSERT [dbo].[T1]([ID], [Version], ... VALUES (@0, @1, ...)
这就是我想要完成的事情。
问题是: 如何以优雅的方式使Entity Framework 在其生成的insert语句中包含该ID列?
我不想使用存储过程或硬编码SQL语句或通过“挤压”来修改插入语句。专栏。
如果没有办法,我知道我必须完全删除身份的使用,并定义我自己的ID,我试图避免使用。
另外,我的SaveChanges()已经使用了SET IDENTITY_INSERT ON / OFF,因此不存在任何问题。
以下是我模型的相关部分:(我省略了其他属性)
[Key, Column(Order = 0)]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
[Key, Column(Order = 1)]
public int VERSION { get; set; }
我探索过的一个途径是在OnModelCreating中重置我的DbContext,但这并没有什么不同。 当然,在该修订版中,我确实从类中的ID属性中删除了DatabaseGenerated装饰器。我将其插入OnModelCreating:
if (this.AllowIdentityInsert)
{
modelBuilder.Entity<T1>().Property(x => x.ID).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
}
else
{
modelBuilder.Entity<T1>().Property(x => x.ID).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
}
如果我可以通过在SaveChanges之前将ID属性更改为None来成功控制模型,那么这可以工作并且是一个优雅的解决方案。
有没有人遇到这种情况并找到了一个好的解决方案? 非常感谢您的意见或建议。
答案 0 :(得分:1)
通常,您不希望以这种方式使用标识列,但我想如果您使用复合键,则可以。插入第二条记录时您将遇到的问题是您必须打开和关闭IDENTITY_INSERT
。因此,在这里考虑它的SQL就是一个例子,向您展示完成任务需要做些什么。
IF OBJECT_ID('tempdb..#TblName') IS NOT NULL
BEGIN
DROP TABLE #TblName
END
CREATE TABLE #TblName (
ID INT IDENTITY(1,1) NOT NULL, Version INT NOT NULL
)
INSERT INTO #TblName (Version) VALUES (1)
SET IDENTITY_INSERT #TblName ON
INSERT INTO #TblName (ID, Version) VALUES (1,2)
SET IDENTITY_INSERT #TblName OFF
SELECT *
FROM
#TblName
更典型的设计是通过触发器实际维护日志表并将历史记录存储在其中。因为在该表中,不需要标识列只是另一个INT。
还有一些桌面设计可以解决这个限制,但您可能还想研究创建SQL SEQUENCE https://msdn.microsoft.com/en-us/library/ff878058.aspx而不是使用IDENTITY
ID
列会在您需要时检索SEQUENCE
并始终插入值。如果您使用SEQUENCE
,则可以获得额外的好处,即能够添加另一个IDENTITY
列,该列将是本地表ID,通常建议使用该列,而不是仅仅依赖于复合键。
好的,这是(对我而言)一种非常有趣的方式来解决您的身份问题并维持“增加版本”。您可以使用Update able View而不是直接使用表格。您可以使用2 SEQUENCES
一个用于ID
,一个用于VersionId
,然后获取版本,您将在ROW_NUMBER()
中使用view
。您可以通过添加INSTEAD OF INSERT/UPDATE trigger
来扩展此解决方案,以更自动地处理IDS的设置,但我通常不喜欢触发器。无论如何,这是一个有趣的解决方案:
CREATE TABLE dbo.yourTable (
TableId INT NOT NULL IDENTITY(1,1)
,Id INT NOT NULL
,VersionId INT NOT NULL
,Col VARCHAR(100) NOT NULL
,PRIMARY KEY (Id, VersionId)
)
GO
CREATE SEQUENCE dbo.SEQ_yourTableIdBy1
START WITH 1
INCREMENT BY 1;
GO
CREATE SEQUENCE dbo.SEQ_yourTableVersionIdBy1
START WITH 1
INCREMENT BY 1;
GO
CREATE VIEW dbo.yourTable_v
AS
SELECT
Id
,VersionId
,Version = ROW_NUMBER() OVER (PARTITION BY Id ORDER BY VersionId)
,Col
,LatestVersion = CASE
WHEN ROW_NUMBER() OVER (PARTITION BY Id ORDER BY VersionId DESC) = 1
THEN 1 ELSE 0 END
FROM
dbo.yourTable
GO
--New Record
INSERT INTO dbo.yourTable_v(Id, VersionId, Col)
VALUES (NEXT VALUE FOR dbo.SEQ_yourTableIdBy1, NEXT VALUE FOR dbo.SEQ_yourTableVersionIdBy1, 'A')
SELECT * FROM dbo.yourTable_v
--Change To Existing Record
INSERT INTO dbo.yourTable_v(Id, VersionId, Col)
VALUES (1, NEXT VALUE FOR dbo.SEQ_yourTableVersionIdBy1, 'B')
SELECT * FROM dbo.yourTable_v
链接显示其工作原理http://rextester.com/GBHG23338
要使实体框架相信视图是一个表,您可能需要更改密钥定义和实体类型这里是关于该主题的msdn博客。 https://blogs.msdn.microsoft.com/alexj/2009/09/01/tip-34-how-to-work-with-updatable-views/
优点: