实体框架6使用隐式插入和删除自定义多对多子

时间:2017-03-08 18:59:23

标签: c# entity-framework entity-framework-6

我有一个带有子(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而不显式处理添加/更新?

1 个答案:

答案 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;
    }
}