EFCore 如何丢失我的“不可删除”的文件数据,如何将其重新附加到数据库?
我将 net6.0
(C# 10.0) 与 Entity Framework 6.0.1 结合使用。
我的 Parent
和 Child
模型之间存在 1:1 关系。每个模型共享相同的主键(标准拆分表设计):
modelBuilder.Entity<Parent>()
.HasOne(fb => fb.Child)
.WithOne(i => i.Parent)
.HasForeignKey<Child>(s => s.Id)
.OnDelete(DeleteBehavior.Cascade);
这两个模型在实践中都不应该被删除(在生产服务器上)。作为防止这种情况的一种方法,我实现了 DbContext
,以便在关系被切断或任一模型被删除时抛出异常:
private static readonly List<Type> DeletePreventedModels = new List<Type>() {
typeof(Parent), typeof(Child)
};
private void UpdateChanges() {
// Prevent deletion of any protected models
foreach (EntityEntry entry in ChangeTracker.Entries()) {
if (entry.State != EntityState.Deleted) continue;
Type t = entry.Entity.GetType();
if (DeletePreventedModels.Any(mt => mt.IsAssignableFrom(t))) {
var id = (entry.Entity is IStringKeyData d) ? d.Id : null;
throw new ArgumentException($"{t.Name} {id} cannot be deleted");
}
if (t.IsAssignableTo(typeof(Parent)) && entry.State != EntityState.Unchanged) {
Parent fb = (Parent) entry.Entity;
if (fb.Child == null) throw new NullReferenceException($"Parent {fb.Id} had null child");
}
}
}
public override int SaveChanges() {
UpdateChanges();
return base.SaveChanges();
}
public override Task<int> SaveChangesAsync(
bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = new()
) {
UpdateChanges();
return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
}
我采取了这种严厉的方法,因为我已经丢失儿童数据。具体来说,我发现 MySQL 表中缺少行。没有其他任何东西可以编辑(甚至访问)该表,并且我无法弄清楚这些删除来自何处。我知道数据最初是基于数据库备份而存在的。
为了尝试纠正问题,我向 Parent
类添加了一些代码,该类在从数据库加载模型后运行(它执行 .Include(p => p.Child )
顺便说一句)。
internal async Task FixChild(MyDataContext context) {
if (Child == null) {
this.Child = new Child() { Id = this.Id, Parent = this };
if (context.Entry(Child).State == EntityState.Detached) {
context.Attach(Child);
}
await Child.RestoreFromBackup(context);
context.Log.Warning("[UPDATE] EMPTY CHILD {state} {id}",
context.Data.Entry(Child).State, Id);
await context.SaveChangesAsync();
}
}
当此代码运行时,将成功检测到损坏的子项并从备份中恢复。子级以分离状态启动,然后在 RestoreFromBackup
函数运行后显示 Modified
状态(基于日志语句)。但是,当我们点击 SaveChanges 行时,我得到:
The database operation was expected to affect 1 row(s), but actually affected 0 row(s);
日志中没有其他警告/错误。恢复的子数据似乎是完整的,因为我可以记录恢复的对象并且它与数据库备份匹配(自备份以来数据库结构未更改)。
总结:
- 如何防止子数据丢失?
- 如何正确保存恢复的Child数据?
I am using net6.0
(C# 10.0) with Entity Framework 6.0.1.
I have a 1:1 relationship between an Parent
and Child
models. Each share the same primary key (standard split-table design):
modelBuilder.Entity<Parent>()
.HasOne(fb => fb.Child)
.WithOne(i => i.Parent)
.HasForeignKey<Child>(s => s.Id)
.OnDelete(DeleteBehavior.Cascade);
Neither model should ever be deletable in practice (on a production server). As a way of guarding against this, I have implemented my DbContext
to throw an exception if the relationship is severed, or either of the models is deleted:
private static readonly List<Type> DeletePreventedModels = new List<Type>() {
typeof(Parent), typeof(Child)
};
private void UpdateChanges() {
// Prevent deletion of any protected models
foreach (EntityEntry entry in ChangeTracker.Entries()) {
if (entry.State != EntityState.Deleted) continue;
Type t = entry.Entity.GetType();
if (DeletePreventedModels.Any(mt => mt.IsAssignableFrom(t))) {
var id = (entry.Entity is IStringKeyData d) ? d.Id : null;
throw new ArgumentException(quot;{t.Name} {id} cannot be deleted");
}
if (t.IsAssignableTo(typeof(Parent)) && entry.State != EntityState.Unchanged) {
Parent fb = (Parent) entry.Entity;
if (fb.Child == null) throw new NullReferenceException(quot;Parent {fb.Id} had null child");
}
}
}
public override int SaveChanges() {
UpdateChanges();
return base.SaveChanges();
}
public override Task<int> SaveChangesAsync(
bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = new()
) {
UpdateChanges();
return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
}
I have taken this heavy-handed approach because I have been losing Child data. Specifically, I am finding rows missing from the MySQL table. There is nothing else which edits (or even accesses) the table, and I cannot figure out where these deletions are coming from. I know the data was originally present based upon database backups.
In attempts to correct the problem, I have added some code to the Parent
class which is run after the model is loaded from the database (which does .Include(p => p.Child)
btw).
internal async Task FixChild(MyDataContext context) {
if (Child == null) {
this.Child = new Child() { Id = this.Id, Parent = this };
if (context.Entry(Child).State == EntityState.Detached) {
context.Attach(Child);
}
await Child.RestoreFromBackup(context);
context.Log.Warning("[UPDATE] EMPTY CHILD {state} {id}",
context.Data.Entry(Child).State, Id);
await context.SaveChangesAsync();
}
}
When this code runs, the broken children are successfully detected and restored from the backup. The child starts in the detached state, and then shows the Modified
state (based on the log statement) once the RestoreFromBackup
function runs. However, when we hit the SaveChanges line, I get:
The database operation was expected to affect 1 row(s), but actually affected 0 row(s);
There are no other warnings/errors in the logs. The restored child data seems complete, in that I can log the restored object and it matches the database backup (the database structure has not been changed since the backup).
In summary:
- How do I prevent the Child data from being lost?
- How do I properly save the restored Child data?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论