最近遇到了一个 EF Core 的恐怖问题,在添加数据时居然会自动删除数据库中已存在的数据,通过追查发现是一个多余的实体关系配置引发的。git
modelBuilder.Entity<Question>() .HasOne(q => q.Owner) .WithOne();
罪魁祸首就是上面的 WithOne()
。github
今天写了个很是简单的控制台程序重现了这个问题。sql
实体类 Question 的定义数据库
public class Question { public int Id { get; set; } public string Title { get; set; } public int UserId { get; set; } public User Owner { get; set; } }
实体类 User 的定义async
public class User { public int Id { get; set; } public string Name { get; set; } }
实体关系配置ui
modelBuilder.Entity<Question>() .HasOne(q => q.Owner) .WithOne();
触发问题的实体查询与添加代码code
class Program { static async Task Main(string[] args) { var conn = "server=.;database=question;integrated security=true"; var host = new HostBuilder() .ConfigureServices(services => { services.AddDbContext<MyDbContext>(options => options.UseSqlServer(conn)); }).Build(); using (var db = host.Services.GetRequiredService<MyDbContext>()) { var newQuestion = new Question { Title = "test " + DateTime.Now.ToLongDateString(), Owner = await db.Set<User>().FirstAsync(u => u.Id == 1) }; var latestQuestion = await db.Set<Question>() .Where(q => q.UserId == 1).OrderByDescending(q => q.Id).FirstOrDefaultAsync(); db.Set<Question>().Add(newQuestion); await db.SaveChangesAsync(); } } }
EF Core 生成的在 INSERT 以前的 DELETE SQL 语句server
exec sp_executesql N'SET NOCOUNT ON; DELETE FROM [Question] WHERE [Id] = @p0; SELECT @@ROWCOUNT;
上面的代码中,建立一个新的 Question 实例时,与一个从数据库查询出来的 Id 为 1 的 User 实例进行了关联,此时这个 User 实例进入 EF Core 的跟踪范围,但这个新建的 Question 实例还没被 EF Core 跟踪。后来使用一样的 UserId 从数据库查询 Question ,查询出来的 Question 实例因为 WithOne
实体关系从而与已经被跟踪的 User 实例(由于 UserId 同样)进行了关联。此时被 EF Core 跟踪到的实体状态是:Id 为 1 的 User 实体与从数据库查询获得的 Id 为 x 的 Question 实体进行了一对一关联。而在 db.Set<Question>().Add(newQuestion)
时,EF Core 跟踪到了实体状态的变化 —— User 实体与一个没有 Id 的新 Question 实体关联了,对于这样的状态变化,EF Core 理所固然地作出了“正确的决定” —— 删除以前关联的 Question 实体,添加新的 Question 实体。blog
去掉多条的 WithOne()
get
重现这个问题的完整示例代码:https://github.com/cnblogs-dudu/efcore-unexpected-deletion