EF Core插入Item时遇IDENTITY_INSERT及对象名无效问题求助
让我们一步步拆解你的问题,先搞定之前的IDENTITY_INSERT坑,再解决现在的Invalid object name 'Game'错误。
关于IDENTITY_INSERT的问题
你一开始的误区在于完全没必要给Tournaments和Games表开启IDENTITY_INSERT——你根本没有插入这两个表的新数据,只是从它们各自的上下文里取出已存在的实体关联到Item上。
为什么会触发关联表的IDENTITY_INSERT提示?大概率是EF把你从其他上下文获取的Tournament/Game实体当成了新实体,尝试自动插入它们。解决这个的正确姿势是:把这些关联实体附加到ItemDbContext里,并标记为Unchanged,明确告诉EF它们已经存在于数据库中,不需要插入。
另外,SQL Server有个硬性规定:一个数据库连接会话里只能给一个表开启IDENTITY_INSERT,这就是你尝试给第二个表开启时报错的核心原因。
你后来删迁移重弄的方法虽然解决了问题,但其实更优雅的写法应该是这样(不需要手动管理连接,用事务确保IDENTITY_INSERT的开关在同一会话内):
if (!context.Items.Any()) { // 从其他上下文取已存在的实体 var tournament = _tournamentDbContext.Tournaments.First(x => x.Id == 1); var game = _gameDbContext.Games.First(x => x.Id == 1); var itemType = context.ItemTypes.First(x => x.Id == 1); // 把关联实体附加到当前上下文,标记为已存在状态 context.Attach(tournament).State = EntityState.Unchanged; context.Attach(game).State = EntityState.Unchanged; // 如果Item的Id是自增列,建议不要手动指定Id,除非有特殊业务需求 var item = new Models.Items.Item { Name = "Test", ItemType = itemType, Price = 100, Short_Description = "TestShort", Tournament = tournament, Game = game, DateCreated = DateTime.Now }; context.Items.Add(item); // 必须手动指定Item的Id时,用事务包裹IDENTITY_INSERT的开关操作 using (var transaction = context.Database.BeginTransaction()) { context.Database.ExecuteSqlRaw("SET IDENTITY_INSERT [Items].[Items] ON;"); context.SaveChanges(); context.Database.ExecuteSqlRaw("SET IDENTITY_INSERT [Items].[Items] OFF;"); transaction.Commit(); } }
现在的Invalid object name 'Game'错误
这个问题的核心是你的ItemDbContext不知道Game和Tournament实体的数据库映射规则。虽然你从其他上下文获取了这些实体,但当ItemDbContext生成保存Item的SQL时,它需要知道Game表的正确名称和Schema,否则会默认去找不带Schema的Game表,而你的Game表显然是在Games Schema下的。
推荐两种解决办法:
方法1:在ItemDbContext里配置关联实体的映射
不需要添加DbSet,只需要在OnModelCreating里告诉EF这两个实体的表位置:
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Item>().ToTable("Item", Schema); modelBuilder.Entity<ItemType>().ToTable("ItemType", Schema); modelBuilder.Entity<ItemTag>().ToTable("ItemTag", Schema); // 配置Tournament和Game的表映射(根据你的实际Schema和表名调整) modelBuilder.Entity<Tournament>().ToTable("Tournaments", "Tournaments"); modelBuilder.Entity<Game>().ToTable("Game", "Games"); // 显式配置外键关联(如果Tournament/Game和Item是一对多关系,调整WithMany的参数) modelBuilder.Entity<Item>() .HasOne(i => i.Tournament) .WithMany() .HasForeignKey("TournamentId"); modelBuilder.Entity<Item>() .HasOne(i => i.Game) .WithMany() .HasForeignKey("GameId"); }
方法2:给Item类添加显式外键字段(更推荐)
这种方式能避免EF的隐式外键推断错误,代码可读性也更高:
public class Item { public int Id { get; set; } public string Name { get; set; } public string Short_Description { get; set; } public decimal Price { get; set; } // 添加显式外键字段 public int ItemTypeId { get; set; } public int TournamentId { get; set; } public int GameId { get; set; } public ItemType ItemType { get; set; } public Tournament Tournament { get; set; } public Game Game { get; set; } [DataType(DataType.Date)] public DateTime DateCreated { get; set; } }
之后在OnModelCreating里配置外键关联,EF就能准确生成SQL,不会找不到Game表了。
额外小技巧:用Id关联而非实体
跨上下文操作时,直接用Id赋值比传递实体更简单,还能避免上下文跟踪的问题:
var item = new Models.Items.Item { Name = "Test", ItemTypeId = 1, TournamentId = 1, GameId = 1, Price = 100, Short_Description = "TestShort", DateCreated = DateTime.Now }; context.Items.Add(item); context.SaveChanges();
内容的提问来源于stack exchange,提问作者Ashley

