Asp.net 如何强制实体框架插入标识列?
我想写一些C代码,用一些种子数据初始化我的数据库。显然,这需要能够在插入时设置各种标识列的值。我使用的是代码优先的方法。默认情况下,Asp.net 如何强制实体框架插入标识列?,asp.net,sql-server,database,entity-framework,ef-code-first,Asp.net,Sql Server,Database,Entity Framework,Ef Code First,我想写一些C代码,用一些种子数据初始化我的数据库。显然,这需要能够在插入时设置各种标识列的值。我使用的是代码优先的方法。默认情况下,DbContext处理数据库连接,因此您不能在上设置IDENTITY\u INSERT[dbo].[MyTable]。因此,到目前为止,我所做的是使用DbContext构造函数,它允许我指定要使用的DB连接。然后,我在数据库连接中将IDENTITY\u INSERT设置为ON,然后尝试使用实体框架插入我的记录。以下是一个到目前为止我得到的示例: public cla
DbContext
处理数据库连接,因此您不能在
上设置IDENTITY\u INSERT[dbo].[MyTable]。因此,到目前为止,我所做的是使用DbContext
构造函数,它允许我指定要使用的DB连接。然后,我在数据库连接中将IDENTITY\u INSERT
设置为ON
,然后尝试使用实体框架插入我的记录。以下是一个到目前为止我得到的示例:
public class MyUserSeeder : IEntitySeeder {
public void InitializeEntities(AssessmentSystemContext context, SqlConnection connection) {
context.MyUsers.Add(new MyUser { MyUserId = 106, ConceptPersonId = 520476, Salutation = "Mrs", Firstname = "Novelette", Surname = "Aldred", Email = null, LoginId = "520476", Password="28c923d21b68fdf129b46de949b9f7e0d03f6ced8e9404066f4f3a75e115147489c9f68195c2128e320ca9018cd711df", IsEnabled = true, SpecialRequirements = null });
try {
connection.Open();
SqlCommand cmd = new SqlCommand("SET IDENTITY_INSERT [dbo].[MyUser] ON", connection);
int retVal = cmd.ExecuteNonQuery();
context.SaveChanges();
}
finally {
connection.Close();
}
}
}
虽然cmd.ExecuteNonQuery()
工作正常,但当我运行context.SaveChanges()
时,我被告知“当identity_INSERT设置为ON或复制用户正在插入非复制标识列时,必须为表‘MyUser’中的标识列指定显式值。”
大概是因为MyUserId(MyUser表中的标识列)是主键,所以在调用context.SaveChanges()
时,entity framework不会尝试设置它,即使我为MyUser
实体提供了MyUserId
属性的值
那么,有没有一种方法可以强制实体框架尝试插入实体的主键值呢?或者有没有一种方法可以临时将
MyUserId
标记为不是主键值,以便EF尝试插入它?您不需要对连接做任何有趣的事情,您可以去掉中间人,直接使用它
然后,您可以通过以下方式实现您想要的:
context.ExecuteStoreCommand("SET IDENTITY_INSERT [dbo].[MyUser] ON");
我不知道有什么内在的方式告诉EF设置身份插入
它不是完美的,但它会比您当前的方法更灵活,更少“黑客”
更新:
我刚刚意识到问题的第二个方面。既然您已经告诉SQL您想要进行标识插入,EF甚至没有尝试为所述标识插入值(为什么?我们没有告诉它)
我没有任何代码优先方法的经验,但从一些快速搜索中,似乎需要告诉EF不应从存储生成列。您需要这样做
Property(obj => obj.MyUserId)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.None)
.HasColumnName("MyUserId");
希望这能使你指向正确的方向:-(<)/P> < P>我只是一个DBA,但每当出现这种情况时,我认为它是一种代码气味。那就是,为什么你有任何依赖于特定行的特定值的东西呢?也就是说,在你的例子中,Novelette夫人为什么需要一个106?呃,不要总是依赖于这种情况,你可以得到她的身份价值,并在你硬编码106的任何地方使用。有点麻烦,但方式更灵活(在我看来).经过仔细考虑后,我决定entity framework拒绝插入标识列是一项功能,而不是一个错误。:-)如果我要在数据库中插入所有条目,包括它们的标识值,我还必须为entity framework为我自动创建的每个链接表创建一个实体!只是n这是正确的方法 因此,我要做的是设置种子类,只使用C#code并创建EF实体,然后使用
DbContext
保存新创建的数据。获取转储的SQL并将其转换为C#code需要更长的时间,但仅仅用于“种子”的数据不会(也不应该)太多“数据-它应该是少量的数据,代表实时数据库中的数据类型,可以快速放入新数据库中进行调试/开发。这确实意味着,如果我想将实体链接在一起,我必须对已经插入的内容进行查询,否则我的代码将不知道它们生成的标识值,例如,在我设置并完成上下文后,此类内容将出现在种子代码中。SaveChanges
forMyRoles
:
var roleBasic = context.MyRoles.Where(rl => rl.Name == "Basic").First();
var roleAdmin = context.MyRoles.Where(rl => rl.Name == "Admin").First();
var roleContentAuthor = context.MyRoles.Where(rl => rl.Name == "ContentAuthor").First();
MyUser thisUser = context.MyUsers.Add(new MyUser {
Salutation = "Mrs", Firstname = "Novelette", Surname = "Aldred", Email = null, LoginUsername = "naldred", Password="c1c966821b68fdf129c46de949b9f7e0d03f6cad8ea404066f4f3a75e11514748ac9f68695c2128e520ca0275cd711df", IsEnabled = true, SpecialRequirements = null
});
thisUser.Roles.Add(roleBasic);
这样做还使我更有可能在更改模式时更新种子设定数据,因为在更改种子设定代码时,我可能会破坏种子设定代码(如果删除字段或实体,则使用该字段/实体的现有种子设定代码将无法编译)。如果使用SQL脚本进行种子设定,则不会出现这种情况,SQL脚本也不会与数据库无关
所以我认为,如果您试图设置实体的标识字段来进行DB种子数据,那么您肯定采取了错误的方法
如果我真的将一堆数据从SQL Server拖到PostgreSQL(一个完整的实时数据库,而不仅仅是一些种子数据),我可以通过EF来完成,但我希望同时打开两个上下文,并编写一些代码从源上下文抓取所有不同的实体并将它们放到目标上下文中,然后保存更改
通常,唯一适合插入标识值的时间是在同一DBMS(SQL Server->SQL Server、PostgreSQL->PostgreSQL等)中从一个DB复制到另一个DB时,然后在SQL脚本中执行,而不是在EF代码中执行(SQL脚本不会与数据库无关,但也不必如此;您不能在不同的DBMS之间切换)
有没有办法强迫实体框架尝试插入实体的主键值
是的,但不像我想看到的那么干净
假设您使用的是自动生成的标识密钥,EF将完全忽略您存储密钥值的尝试。出于上述许多原因,这似乎是“设计的”,但仍有一些时候您希望完全控制种子数据(或初始加载).我建议英孚在未来版本中接受这种播种。但在他们接受之前,
public class NewsprintInitializer: DropCreateDatabaseIfModelChanges<NewsprintContext>
{
protected override void Seed(NewsprintContext context)
{
var vendorSeed = new List<Vendor>
{
new Vendor { VendorID = 1, Name = "#1 Papier Masson / James McClaren" },
new Vendor { VendorID = 5, Name = "#5 Abitibi-Price" },
new Vendor { VendorID = 6, Name = "#6 Kruger Inc." },
new Vendor { VendorID = 8, Name = "#8 Tembec" }
};
// Add desired records AND Junk records for gaps in the IDs, because .VendorID is ignored on .Add
int idx = 1;
foreach (Vendor currentVendor in vendorSeed)
{
while (idx < currentVendor.VendorID)
{
context.Vendors.Add(new Vendor { Name = "**Junk**" });
context.SaveChanges();
idx++;
}
context.Vendors.Add(currentVendor);
context.SaveChanges();
idx++;
}
// Cleanup (Query/Find and Remove/delete) the Junk records
foreach (Vendor del in context.Vendors.Where(v => v.Name == "**Junk**"))
{
context.Vendors.Remove(del);
}
context.SaveChanges();
// setup for other classes
}
}
public class MyDataContext: DbContext
{
public virtual DbSet<Person> People { get; set; }
}
private void CopyPeople()
{
var records = _sourceContext.People.AsNoTracking().ToArray();
_targetContext.People.AddRange(records);
_targetContext.SaveChanges();
}
private void InsertRecords(Person[] people)
{
// setup expected id - presumption: empty table therefore 1
int expectedId = 1;
// now add all people in order of ascending id
foreach(var person in people.OrderBy(p => p.PersonId))
{
// if the current person doesn't have the expected next id
// we need to reseed the identity column of the table
if (person.PersonId != expectedId)
{
// we need to save changes before changing the seed value
_targetContext.SaveChanges();
// change identity seed: set to one less than id
//(SQL Server increments current value and inserts that)
_targetContext.Database.ExecuteSqlCommand(
String.Format("DBCC CHECKIDENT([Person], RESEED, {0}", person.PersonId - 1)
);
// update the expected id to the new value
expectedId = person.PersonId;
}
// now add the person
_targetContext.People.Add(person);
// bump up the expectedId to the next value
// Assumption: increment interval is 1
expectedId++;
}
// now save any pending changes
_targetContext.SaveChanges();
}
Entities.Database.ExecuteSqlCommand(String.Format("DBCC CHECKIDENT ([TableNameHere], RESEED, {0})", newObject.Id-1););
Entities.YourTable.Add(newObject);
Entities.SaveChanges();
sb.Append("SET IDENTITY_INSERT [dbo].[tblCustomer] ON;");
foreach(...)
{
var insert = string.Format("INSERT INTO [dbo].[tblCustomer]
([ID],[GivenName],[FamilyName],[NINumber],[CustomerIdent],
[InputterID],[CompanyId],[Discriminator])
VALUES({0}, '{1}', '{2}', '{3}', '{4}', 2, 2, 'tblCustomer'); ",
customerId, firstName, surname, nINumber, Guid.NewGuid());
sb.Append(insert);
...
}
sb.Append("SET IDENTITY_INSERT [dbo].[tblCustomer] OFF;");
using (var sqlConnection = new SqlConnection(connectionString))
{
var svrConnection = new ServerConnection(sqlConnection);
var server = new Server(svrConnection);
server.ConnectionContext.ExecuteNonQuery(sb.ToString());
}
public static bool UpdateLeadTime(int ltId, int ltDays)
{
try
{
using (var db = new LeadTimeContext())
{
var result = db.LeadTimes.SingleOrDefault(l => l.LeadTimeId == ltId);
if (result != null)
{
result.LeadTimeDays = ltDays;
db.SaveChanges();
logger.Info("Updated ltId: {0} with ltDays: {1}.", ltId, ltDays);
}
else
{
LeadTime leadtime = new LeadTime();
leadtime.LeadTimeId = ltId;
leadtime.LeadTimeDays = ltDays;
try
{
db.LeadTimes.Add(leadtime);
db.SaveChanges();
logger.Info("Inserted ltId: {0} with ltDays: {1}.", ltId, ltDays);
}
catch (Exception ex)
{
logger.Warn("Error captured in UpdateLeadTime({0},{1}) was caught: {2}.", ltId, ltDays, ex.Message);
logger.Warn("Inner exception message: {0}", ex.InnerException.InnerException.Message);
if (ex.InnerException.InnerException.Message.Contains("IDENTITY_INSERT"))
{
logger.Warn("Attempting workaround...");
try
{
db.Database.Connection.Open(); // required to update database without db.SaveChanges()
db.Database.ExecuteSqlCommand("SET IDENTITY_INSERT[dbo].[LeadTime] ON");
db.Database.ExecuteSqlCommand(
String.Format("INSERT INTO[dbo].[LeadTime]([LeadTimeId],[LeadTimeDays]) VALUES({0},{1})", ltId, ltDays)
);
db.Database.ExecuteSqlCommand("SET IDENTITY_INSERT[dbo].[LeadTime] OFF");
logger.Info("Inserted ltId: {0} with ltDays: {1}.", ltId, ltDays);
// No need to save changes, the database has been updated.
//db.SaveChanges(); <-- causes error
}
catch (Exception ex1)
{
logger.Warn("Error captured in UpdateLeadTime({0},{1}) was caught: {2}.", ltId, ltDays, ex1.Message);
logger.Warn("Inner exception message: {0}", ex1.InnerException.InnerException.Message);
}
finally
{
db.Database.Connection.Close();
//Verification
if (ReadLeadTime(ltId) == ltDays)
{
logger.Info("Insertion verified. Workaround succeeded.");
}
else
{
logger.Info("Error!: Insert not verified. Workaround failed.");
}
}
}
}
}
}
}
catch (Exception ex)
{
logger.Warn("Error in UpdateLeadTime({0},{1}) was caught: {2}.", ltId.ToString(), ltDays.ToString(), ex.Message);
logger.Warn("Inner exception message: {0}", ex.InnerException.InnerException.Message);
Console.WriteLine(ex.Message);
return false;
}
return true;
}
public MyDbContext(DbConnection existingConnection, bool contextOwnsConnection)
: base(existingConnection, contextOwnsConnection = true)
{
//optional
this.Configuration.ProxyCreationEnabled = true;
this.Configuration.LazyLoadingEnabled = true;
this.Database.CommandTimeout = 360;
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<MyTable>()
.Property(a => a.Id)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
base.OnModelCreating(modelBuilder);
}
using (var connection = new System.Data.SqlClient.SqlConnection(ConfigurationManager.ConnectionStrings["ConnectionStringName"].ConnectionString))
{
connection.Open();
using (var context = new MyDbContext(connection, true))
{
context.Database.ExecuteSqlCommand("SET IDENTITY_INSERT [dbo].[MyTable] ON");
context.MyTable.AddRange(objectList);
context.SaveChanges();
context.Database.ExecuteSqlCommand("SET IDENTITY_INSERT [dbo].[MyTable] OFF");
}
connection.Close();
}