最近遇到了一個 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