这是查询:
using (var db = new AppDbContext())
{
var item = new IdentityItem {Id = 418, Name = "Abrahadabra" };
db.IdentityItems.Add(item);
db.Database.ExecuteSqlCommand("SET IDENTITY_INSERT Test.Items ON;");
db.SaveChanges();
}
执行时,新表上插入记录的Id
仍为1.
新:当我使用交易或TGlatzer的答案时,我得到例外:
必须为表格中的标识列指定显式值'项目' 当IDENTITY_INSERT设置为ON或复制用户时 插入NOT FOR REPLICATION标识列。
答案 0 :(得分:8)
根据之前的Question,您需要开始上下文的交易。保存更改后,您还必须重新标识“身份插入”列,最后必须提交事务。
using (var db = new AppDbContext())
using (var transaction = db .Database.BeginTransaction())
{
var item = new IdentityItem {Id = 418, Name = "Abrahadabra" };
db.IdentityItems.Add(item);
db.Database.ExecuteSqlCommand("SET IDENTITY_INSERT Test.Items ON;");
db.SaveChanges();
db.Database.ExecuteSqlCommand("SET IDENTITY_INSERT Test.Items OFF");
transaction.Commit();
}
答案 1 :(得分:3)
我没有尊重问题的标签,告诉这是关于EF6的 这个答案适用于EF Core
这里真正的罪魁祸首不是缺少的交易,而是一个小小的不便,Database.ExectueSqlCommand()
在以前没有明确打开的情况下不会保持连接打开。
using (var db = new AppDbContext())
{
var item = new IdentityItem {Id = 418, Name = "Abrahadabra" };
db.IdentityItems.Add(item);
db.Database.OpenConnection();
db.Database.ExecuteSqlCommand("SET IDENTITY_INSERT Test.Items ON;");
db.SaveChanges();
}
也会这样做,因为SET IDENTITY_INSERT [...] ON/OFF
将绑定到您的连接。
答案 2 :(得分:3)
要强制EF写入您实体的ID,您必须将ID配置为不存储生成,否则EF将永远不会在插入语句中包含该ID。
因此,您需要动态更改模型并根据需要配置实体ID
问题是模型是缓存的,并且在运行中更改它是非常棘手的(我很确定我已经完成了它但实际上我找不到代码,可能我把它扔掉了)。最简单的方法是创建两个不同的上下文,您可以通过两种不同的方式配置实体,如(x+x)
(当您需要编写ID时)和DatabaseGeneratedOption.None
(当您需要自动编号ID时)。
答案 3 :(得分:1)
绝不能在生产代码中使用它,只是为了好玩
我不建议这样做,因为这是一个疯狂的黑客,但无论如何。
我认为我们可以通过拦截sql命令并更改命令文本来实现它 (您可以继承自DbCommandInterceptor和adver ovveride ReaderExecuting)
我现在没有一个有效的例子,我必须去,但我认为这是可行的
示例代码
public class MyDbInterceptor : DbCommandInterceptor
{
public override void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
{
if (is your table)
{
command.CommandText = "Set Identoty off ,update insert into ,Set Identity off"
return;
}
base.ReaderExecuting(command, interceptionContext);
}
}
ORM是一个很好的抽象,我真的很喜欢它们,但我认为尝试&#34; hack&#34;是不合理的。它们支持更低(更接近数据库)级别的操作 我试图避免存储过程,但我认为在这种情况下(如你所说的例外)我认为你应该使用一个
答案 4 :(得分:1)
我有一个类似的问题。在我的生产代码中,实体依赖于身份生成。但是对于集成测试,我需要手动设置一些ID。在不需要显式设置它们的地方,我在test data builders中生成了它们。为此,我创建了一个DbContext
来继承我的生产代码中的代码,并为每个实体配置了身份生成,如下所示:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Entity1>().Property(e => e.Id).ValueGeneratedNever();
modelBuilder.Entity<Entity2>().Property(e => e.Id).ValueGeneratedNever();
...
}
但这还不够,我不得不禁用SQL Server IDENTITY_INSERT
。在单个表中插入数据时,此方法有效。但是,当您拥有彼此相关的实体并且想要插入对象图时,此操作将在DbContext.SaveChanges()
上失败。原因是,根据SQL Server documentation,您可以在会话期间一次只为一张表使用IDENTITY_INSERT ON
。我的同事建议使用与other answer to this question类似的DbCommandInterceptor
。我仅将其用于INSERT INTO
,但可以进一步扩展该概念。当前,它在单个INSERT INTO
中拦截并修改多个DbCommand.CommandText
语句。可以对代码进行优化以使用Span.Slice,以避免由于字符串操作而导致过多的内存,但是由于我找不到Split
方法,因此没有花时间在上面。无论如何,我正在使用此DbCommandInterceptor
进行集成测试。如果发现有用,请随时使用它。
/// <summary>
/// When enabled intercepts each INSERT INTO statement and detects which table is being inserted into, if any.
/// Then adds the "SET IDENTITY_INSERT table ON;" (and same for OFF) statement before (and after) the actual insertion.
/// </summary>
public class IdentityInsertInterceptor : DbCommandInterceptor
{
public bool IsEnabled { get; set; }
public override InterceptionResult<DbDataReader> ReaderExecuting(DbCommand command, CommandEventData eventData, InterceptionResult<DbDataReader> result)
{
if (IsEnabled)
{
ModifyAllStatements(command);
}
return base.ReaderExecuting(command, eventData, result);
}
private static void ModifyAllStatements(DbCommand command)
{
string[] statements = command.CommandText.Split(';', StringSplitOptions.RemoveEmptyEntries);
var commandTextBuilder = new StringBuilder(capacity: command.CommandText.Length * 2);
foreach (string statement in statements)
{
string modified = ModifyStatement(statement);
commandTextBuilder.Append(modified);
}
command.CommandText = commandTextBuilder.ToString();
}
private static string ModifyStatement(string statement)
{
const string insertIntoText = "INSERT INTO [";
int insertIntoIndex = statement.IndexOf(insertIntoText, StringComparison.InvariantCultureIgnoreCase);
if (insertIntoIndex < 0)
return $"{statement};";
int closingBracketIndex = statement.IndexOf("]", startIndex: insertIntoIndex, StringComparison.InvariantCultureIgnoreCase);
string tableName = statement.Substring(
startIndex: insertIntoIndex + insertIntoText.Length,
length: closingBracketIndex - insertIntoIndex - insertIntoText.Length);
// we should probably check whether the table is expected - list with allowed/disallowed tables
string modified = $"SET IDENTITY_INSERT [{tableName}] ON; {statement}; SET IDENTITY_INSERT [{tableName}] OFF;";
return modified;
}
}
答案 5 :(得分:0)
即使您关闭IDENTITY_INSERT
,您刚刚告诉SQL我将向您发送身份,您没有告诉实体框架将身份发送到SQL服务器。
所以基本上,你必须创建DbContext,如下所示..
// your existing context
public abstract class BaseAppDbContext : DbContext {
private readonly bool turnOfIdentity = false;
protected AppDbContext(bool turnOfIdentity = false){
this.turnOfIdentity = turnOfIdentity;
}
public DbSet<IdentityItem> IdentityItems {get;set;}
protected override void OnModelCreating(DbModelBuilder modelBuilder){
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<IdentityItem>()
.HasKey( i=> i.Id )
// BK added the "Property" line.
.Property(e => e.Id)
.HasDatabaseGeneratedOption(
turnOfIdentity ?
DatabaseGeneratedOption.None,
DatabaseGeneratedOption.Identity
);
}
}
public class IdentityItem{
}
public class AppDbContext: BaseAppDbContext{
public AppDbContext(): base(false){}
}
public class AppDbContextWithIdentity : BaseAppDbContext{
public AppDbContext(): base(true){}
}
现在以这种方式使用它......
using (var db = new AppDbContextWithIdentity())
{
using(var tx = db.Database.BeginTransaction()){
var item = new IdentityItem {Id = 418, Name = "Abrahadabra" };
db.IdentityItems.Add(item);
db.Database.ExecuteSqlCommand("SET IDENTITY_INSERT Test.Items ON;");
db.SaveChanges();
db.Database.ExecuteSqlCommand("SET IDENTITY_INSERT Test.Items OFF");
tx.Commit();
}
}
答案 6 :(得分:0)
我有一个非常类似的问题。
解决方案如下:
db.Database.ExecuteSqlCommand("disable trigger all on myTable ;")
db.Database.ExecuteSqlCommand("SET IDENTITY_INSERT myTable ON;");
db.SaveChanges();
db.Database.ExecuteSqlCommand("SET IDENTITY_INSERT myTable OFF");
db.Database.ExecuteSqlCommand("enable trigger all on myTable ;")
就我而言,消息Explicit value must be specified for identity...
是因为在插入一个被调用的触发器时会插入其他内容。
ALTER TABLE myTable NOCHECK CONSTRAINT all
也可以是有用的
答案 7 :(得分:0)
答案适用于Entity Framework 6 只需在交易外部使用IDENTITY_INSERT
using (var db = new AppDbContext())
{
db.Database.ExecuteSqlCommand("SET IDENTITY_INSERT Test.Items ON;");
using (var transaction = db .Database.BeginTransaction())
{
var item = new IdentityItem {Id = 418, Name = "Abrahadabra" };
db.IdentityItems.Add(item);
db.SaveChanges();
transaction.Commit();
}
db.Database.ExecuteSqlCommand("SET IDENTITY_INSERT Test.Items OFF");
}