我有一个带有子(LoanApplicationQualificationTypes)的父对象(LoanApplication),它是一个自定义的多对多表。我有自定义的原因是它有两个需要填充的审计列(ModifiedBy,ModifiedDate)。
为了让从子集合中添加或删除的子节点正确地保存在数据库中,我必须显式处理。
以下是代码(通过删除与问题密切相关的其他属性进行简化)。
家长(多对多的一部分):
[Serializable]
[Table("LoanApplication")]
public class LoanApplication : BaseDomainModelWithId, ILoanApplication
{
[Key]
[Column("LoanApplicationId")]
public override int? Id { get; set; }
[ForeignKey("LoanApplicationId")]
public virtual ICollection<LoanApplicationQualificationTypes> LoanApplicationQualificationTypes { get; set; }
IReadOnlyCollection<ILoanApplicationQualificationTypes> ILoanApplication.LoanApplicationQualificationTypes
{
get
{
var loanApplicationQualificationTypes = new List<ILoanApplicationQualificationTypes>();
if (LoanApplicationQualificationTypes == null) return loanApplicationQualificationTypes;
loanApplicationQualificationTypes.AddRange(LoanApplicationQualificationTypes);
return loanApplicationQualificationTypes.AsReadOnly();
}
set
{
foreach (var item in value)
{
LoanApplicationQualificationTypes.Add((LoanApplicationQualificationTypes)item);
}
}
}
public LoanApplication() : base()
{
LoanApplicationQualificationTypes = new List<LoanApplicationQualificationTypes>();
}
}
public interface ILoanApplication : IDomainModel, ILoanApplicationBase, IKeyIntId
{
IReadOnlyCollection<ILoanApplicationQualificationTypes> LoanApplicationQualificationTypes { get; set; }
}
多对多的对象部分:
[Serializable]
[Table("QualificationType")]
public class QualificationType : IQualificationType
{
[Key]
[Column("QualificationTypeId")]
public override int? Id { get; set; }
[Required]
public string TypeName { get; set; }
[Required]
public bool IsActive { get; set; }
public virtual string ModifiedBy { get; set; }
public virtual DateTimeOffset? ModifiedDate { get; set; }
public QualificationType() : { }
}
自定义多对多:
[Serializable]
[Table("LoanApplicationQualificationTypes")]
public class LoanApplicationQualificationTypes : ILoanApplicationQualificationTypes
{
[Key]
[Column(Order = 1)]
public int? LoanApplicationId { get; set; }
[ForeignKey("LoanApplicationId")]
public virtual LoanApplication LoanApplication { get; set; }
ILoanApplication ILoanApplicationQualificationTypes.LoanApplication
{
get
{
return this.LoanApplication;
}
set
{
this.LoanApplication = (LoanApplication)value;
}
}
[Required]
[Key]
[Column(Order = 2)]
public int QualificationTypeId { get; set; }
[ForeignKey("QualificationTypeId")]
public virtual QualificationType QualificationType { get; set; }
IQualificationType ILoanApplicationQualificationTypes.QualificationType
{
get
{
return this.QualificationType;
}
set
{
this.QualificationType = (QualificationType)value;
}
}
public virtual string ModifiedBy { get; set; }
public virtual DateTimeOffset? ModifiedDate { get; set; }
public LoanApplicationQualificationTypes() { }
}
LoanApplication Repository中的更新方法:
public bool Update(ILoanApplication entity)
{
using (var db = new MainContext())
{
entity.ModifiedDate = DateTime.UtcNow;
entity.ModifiedBy = UserOrProcessName;
// Add / Remove LoanApplicationQualificationTypes and populate audit columns
if (entity.LoanApplicationQualificationTypes?.Count > 0)
{
var existingItems = db.LoanApplicationQualificationTypes.Where(q => q.LoanApplicationId == entity.Id.Value).ToList();
var newItems = entity.LoanApplicationQualificationTypes.Where(q => existingItems.All(e => e.QualificationTypeId != q.QualificationTypeId));
var deletedItems = existingItems.Where(q => entity.LoanApplicationQualificationTypes.All(e => e.QualificationTypeId != q.QualificationTypeId));
foreach (var newItem in newItems)
{
newItem.ModifiedBy = UserOrProcessName;
newItem.ModifiedDate = DateTime.UtcNow;
db.LoanApplicationQualificationTypes.Add((LoanApplicationQualificationTypes)newItem);
}
foreach (var deletedItem in deletedItems)
{
db.LoanApplicationQualificationTypes.Remove((LoanApplicationQualificationTypes)deletedItem);
}
// Need to clear to avoid duplicate objects
((LoanApplication)entity).LoanApplicationQualificationTypes.Clear();
}
db.Entry(entity).State = EntityState.Modified;
db.SaveChanges();
}
return true;
}
有没有办法实现Update而不显式处理添加/更新?
答案 0 :(得分:1)
我理解它的方式,问题是如何在不明确检测添加/删除链接的情况下将(潜在)修改应用于链接表。我还假设链接的其他部分必须存在。
可以通过以下操作顺序进行操作:
首先将实际实体从数据库加载到上下文中,包括链接:
var dbEntity = db.LoanApplication
.Include(e => e.LoanApplicationQualificationTypes)
.FirstOrDefault(e => e.Id == entity.Id);
这将允许更改跟踪器为您稍后确定正确的添加/更新/删除链接操作。
然后应用原始主数据更改:
db.Entry(dbEntity).CurrentValues.SetValues(entity);
dbEntity.ModifiedDate = DateTime.UtcNow;
dbEntity.ModifiedBy = UserOrProcessName;
最后,将链接替换为传入实体中的链接。为了避免指向不同对象的导航属性引用(特别是为了防止EF尝试为关系的其他对象创建新记录),不要直接使用传入对象,而是创建仅设置了FK属性的存根对象:
dbEntity.LoanApplicationQualificationTypes = entity.LoanApplicationQualificationTypes
.Select(e => new LoanApplicationQualificationTypes
{
LoanApplicationId = e.LoanApplicationId,
QualificationTypeId = e.QualificationTypeId,
ModifiedDate = DateTime.UtcNow,
ModifiedBy = UserOrProcessName,
})
.ToList();
就是这样。此时,更改跟踪器具有在您调用db.SaveChanges()
时生成正确命令的所有必要信息。
有一点需要提及。如果您此时查看db.ChangeTracker.Entries
,您可能会注意到所有旧链接都标记为Deleted
,所有旧链接都标记为Added
且没有Modified
条目。别担心。 EF非常智能,可以使用相同的PK将Deleted
+ Added
对转换为单个更新命令。
整个方法:
public bool Update(ILoanApplication entity)
{
using (var db = new MainContext())
{
var dbEntity = db.LoanApplication
.Include(e => e.LoanApplicationQualificationTypes)
.FirstOrDefault(e => e.Id == entity.Id);
if (dbEntity == null) return false;
db.Entry(dbEntity).CurrentValues.SetValues(entity);
dbEntity.ModifiedDate = DateTime.UtcNow;
dbEntity.ModifiedBy = UserOrProcessName;
dbEntity.LoanApplicationQualificationTypes = entity.LoanApplicationQualificationTypes
.Select(e => new LoanApplicationQualificationTypes
{
LoanApplicationId = e.LoanApplicationId,
QualificationTypeId = e.QualificationTypeId,
ModifiedDate = DateTime.UtcNow,
ModifiedBy = UserOrProcessName,
})
.ToList();
db.SaveChanges();
return true;
}
}