最近遇到了一个 EF Core 的恐怖问题,在添加数据时竟然会自动删除数据库中已存在的数据,经过追查发现是一个多余的实体关系配置引起的。
modelBuilder.Entity<Question>()
.HasOne(q => q.Owner)
.WithOne();
罪魁祸首就是上面的 WithOne()
。
今天写了个非常简单的控制台程序重现了这个问题。
实体类 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 的定义
public class User
{
public int Id { get; set; }
public string Name { get; set; }
}
实体关系配置
modelBuilder.Entity<Question>()
.HasOne(q => q.Owner)
.WithOne();
触发问题的实体查询与添加代码
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 语句
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 实体。
解决方法
去掉多条的 WithOne()
示例代码
重现这个问题的完整示例代码:https://github.com/cnblogs-dudu/efcore-unexpected-deletion