C# 正在更新EF6中的实体子集合
我有一个名为Driver的模型,其中包含一个“DriverQualifications”列表,在更新时,我希望添加/删除/更新当前DriverQualifications的值 我当前尝试通过首先清除列表并读取所有元素进行更新:C# 正在更新EF6中的实体子集合,c#,entity-framework,entity-framework-6,C#,Entity Framework,Entity Framework 6,我有一个名为Driver的模型,其中包含一个“DriverQualifications”列表,在更新时,我希望添加/删除/更新当前DriverQualifications的值 我当前尝试通过首先清除列表并读取所有元素进行更新: public void UpdateOne(Driver val) { using (var db = new COMP1690Entities()) { Driver d = db.Drivers.Where((dr) => dr.
public void UpdateOne(Driver val)
{
using (var db = new COMP1690Entities())
{
Driver d = db.Drivers.Where((dr) => dr.Id == val.Id).Include("DriverQualifications.Qualification").FirstOrDefault();
d.DriverQualifications.Clear();
foreach (DriverQualification q in val.DriverQualifications)
{
q.Fk_Qualifications_Id = q.Qualification.Id;
q.Qualification = null;
d.DriverQualifications.Add(q);
}
d.Phone_Number = val.Phone_Number;
db.SaveChanges();
}
}
这将导致“违反多重性约束”。关系“comp1690模型驱动程序1”的角色“驱动程序”具有1或0..1的多重性
如何向数据库添加值:
public void CreateOne(Driver val)
{
using (var db = new COMP1690Entities())
{
foreach(DriverQualification q in val.DriverQualifications)
{
q.Fk_Qualifications_Id = q.Qualification.Id;
q.Qualification = null;
}
db.Drivers.Add(val);
db.SaveChanges();
}
}
驾驶员型号:
public partial class Driver
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Driver()
{
this.DriverQualifications = new HashSet<DriverQualification>();
this.DriverTrainings = new HashSet<DriverTraining>();
}
public int Id { get; set; }
public string Phone_Number { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<DriverQualification> DriverQualifications { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<DriverTraining> DriverTrainings { get; set; }
}
驾驶员资格模型:
public partial class DriverQualification
{
public int Id { get; set; }
public Nullable<System.DateTime> Expiry_Date { get; set; }
public int Fk_Driver_Id { get; set; }
public int Fk_Qualifications_Id { get; set; }
public virtual Driver Driver { get; set; }
public virtual Qualification Qualification { get; set; }
}
非常确定您的问题与EF上下文加载/跟踪实体的方式有关 此代码:
using (var db = new COMP1690Entities())
{
Driver d = db.Drivers.Where((dr) => dr.Id == val.Id).Include("DriverQualifications.Qualification").FirstOrDefault();
d.DriverQualifications.Clear();
foreach (DriverQualification q in val.DriverQualifications)
{
q.Fk_Qualifications_Id = q.Qualification.Id;
q.Qualification = null;
d.DriverQualifications.Add(q);
}
d.Phone_Number = val.Phone_Number;
db.SaveChanges();
}
是否有下列情况:
1加载驱动程序及其所有驱动程序资格和每个资格
2清除当前驱动程序的资格
3循环通过传入的资格
4将新资格添加到当前驱动程序
我认为这个问题与2和4有关。即使你清除了资格证书,它们仍然是EF上下文的参考。当您到达4并再次尝试包含这些时,您将看到多重性错误
我不完全确定这是否能解决您的问题,因为我以前从未尝试过这种方法,但我很好奇,如果可以解决您的问题,您是否要循环查看资格列表并在上下文中将其状态手动设置为“删除”
因此,不是:
d.DriverQualifications.Clear();
在foreach循环中执行此操作:
db.Entry(d).State = System.Data.Entity.EntityState.Deleted;
再一次…不能保证这会起作用,但我认为您必须这样做,才能在初始get请求期间处理附加到上下文的实体。在处理EF和引用DriverQualification->Qualification时使用引用,而不是FKs。事实上,我通常建议不要将FK添加到实体中,而是在实体配置中使用阴影属性EF Core或.Map,以避免访问它们。您面临的问题是EF仍在跟踪引用特定资格的DriverQualification实体,因此将资格设置为null并更新FK实际上不起作用 因此,您要传回一个驱动程序,希望重新加载该驱动程序实体,并根据传入的驱动程序更新其资格 假设传入的驱动程序来自客户端web应用程序等,并且已被修改,我们无法信任它或它的引用数据,因此最好将其重新加载,而不是将其重新附加到上下文 编辑:我建议使用ViewModel而不是传递实体,即使您不想信任它。传递实体的主要风险是,在更新时可能会重新附加/使用实体或引用的实体。我不得不再次检查这个答案,因为我认为我在获得最新资格证书时违反了这个规则!:例如,将实体图传递到客户端浏览器也会暴露出更多有关域的信息。即使不显示各种列/fks/引用数据,客户端也可以使用调试工具查看这些数据。同时,网络上的数据量也超过了可能需要的数据量。Automapper可以快速转换实体以查看模型,也可以使用IQueryable。ProjectTo/编辑 为了清晰起见,我重命名了一些变量。。即val=>updatedDriver
using (var context = new COMP1690Entities())
{
var updatedQualificationIds = updatedDriver.DriverQualifications.Select(dq => dq.Qualification.Id).ToList();
// Get the updated qualification entities from the DB.
var updatedQualifications = context.Qualifications.Where(q => updatedQualificationIds.Contains(q.Id)).ToList();
var driver = context.Drivers.Where(d => d.Id == updatedDriver.Id)
.Include(d => d.DriverQualifications)
.Include("DriverQualifications.Qualification").Single();
var driverQualificationsToRemove = driver.DriverQualifications
.Where(dq => !updatedQualificationIds.Contains(udq.Qualification.Id));
foreach(var driverQualification in driverQualificationsToRemove)
driver.DriverQualifications.Remove(driverQualification);
var driverQualificationsToAdd = updatedDriverQualifications
.Except(driver.DriverQualifications.Select(dq => dq.Qualification),
new LamdaComparer((q1,q2) => q1.Id == q2.Id))
.Select(q => new DriverQualification { Qualification = q })
.ToList();
driver.DriverQualifications.AddRange(driverQualificationsToAdd);
driver.PhoneNumber = updatedDriver.PhoneNumber;
context.SaveChanges();
}
这假设我们希望删除不再与驱动程序关联的资格关联,并添加任何尚未关联的新资格。保留任何不变的资格
你能找到的LamdaComparer
基本上,为了避免引用/关键问题,请坚持更新引用并完全忽略FKs。对于需要进行大量原始更新的实体/上下文,我将仅声明FKs并放弃添加性能参考。感谢您的输入,但设置entitystate时也会出现相同的错误,虽然您的代码将整个驱动程序对象设置为删除状态,而不仅仅是列表,但您是对的……当我对其进行伪编码时,并没有真正考虑到这一部分。我会看看是否可以重新创建。很抱歉,回复太晚了,我会马上尝试,只是关于使用实例而不是Fk的第一点,我使用了Fk,因为这些对象已经存在,我不想在创建DriverQualification时复制它们,基本上,DriverQualification指向已经存在的限定,如果我在DriverQualification->Qualification on insertion中有一个对象,它将在数据库中复制。如果您将上下文中的限定与DriverQualification关联,EF将更新适用的FK。如果您关联了一个新的或上下文不知道的限定实例,则会得到重复项,上下文会将其视为一个新实例,这可能会在重复密钥场景中运行。