Entity Framework Code First Existing Database中的Many-to-Many关系如何使用SetEntityState.Unchanged?

3

我正在使用Entity Framework 6 Code First编写代码与现有数据库交互,其中涉及到许多对多关系。在选择、插入和删除方面,这些关系都能够正常工作。但是,我遇到了一个问题,即EF会为现有的一对多关系添加额外的插入操作。

数据库结构如下图所示: enter image description here

DbContext:

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
       Database.SetInitializer<MainContext>(null);

       modelBuilder.Entity<DocumentType>()
                .HasMany(u => u.DocumentStatuses)
                .WithMany()
                .Map(m =>
                {
                    m.MapLeftKey("DOCUMENT_TYPE_ID");
                    m.MapRightKey("DOCUMENT_STATUS_ID");
                    m.ToTable("DOCUMENT_TYPES_DOCUMENT_STATUSES");
                });

       base.OnModelCreating(modelBuilder);
    } 

DTOs:

    [Table("DOCUMENT_TYPES")]
    public class DocumentType
    {
        [Key]
        [Column("DOCUMENT_TYPE_ID")]
        public int? Id { get; set; }

        [Required]
        [Column("TYPE_NAME")]
        public string TypeName { get; set; }

        [Required]
        [Column("IS_ACTIVE")]
        public bool IsActive { get; set; }

        [Display(Name = "Updated By")]
        [Column("LAST_UPDATED_BY")]
        public string LastUpdatedBy { get; set; }

        [Display(Name = "Updated Date")]
        [Column("LAST_UPDATED_DATE")]
        public DateTimeOffset? LastUpdatedDate { get; set; }

        public virtual List<DocumentStatus> DocumentStatuses { get; set; } 

        public DocumentType()
        {
            DocumentStatuses = new List<DocumentStatus>();
        }
    }

[Table("DOCUMENT_STATUSES")]
public class DocumentStatus
{
    [Key]
    [Column("DOCUMENT_STATUS_ID")]
    public int? Id { get; set; }

    [Required]
    [Column("STATUS_NAME")]
    public string StatusName { get; set; }

    [Required]
    [Column("IS_COMPLETE")]
    public bool IsComplete { get; set; }

    [Required]
    [Column("IS_ACTIVE")]
    public bool IsActive { get; set; }

    [Display(Name = "Updated By")]
    [Column("LAST_UPDATED_BY")]
    public string LastUpdatedBy { get; set; }

    [Display(Name = "Updated Date")]
    [Column("LAST_UPDATED_DATE")]
    public DateTimeOffset? LastUpdatedDate { get; set; }
}

仓库更新:

    public bool Update(DocumentType entity, string updatedBy)
            {
                DateTimeOffset updatedDateTime = DateTimeOffset.Now;

                entity.LastUpdatedBy = updatedBy;
                entity.LastUpdatedDate = updatedDateTime;

                using (var db = new MainContext())
                {
                    db.DocumentTypes.Add((DocumentType)entity);
                    db.Entry(entity).State = EntityState.Modified;

                    foreach (var item in entity.DocumentStatuses)
                    {
                        if (item.Id != null)
                            db.Entry(item).State = EntityState.Unchanged;
                    }

                    db.SaveChanges();
                }

                return true;
            }

循环包含以下内容:
    if (item.Id != null)
     db.Entry(item).State = EntityState.Unchanged;

防止新记录被添加,但EF仍然尝试插入一个现有的多对多记录的重复到DOCUMENT_TYPES_DOCUMENT_STATUSES中。

单元测试:

        [TestMethod()]
        public void UpdateTest()
        {
            DocumentTypeRepository documentTypeRepository = new DocumentTypeRepository();
            DocumentType type = NewDocumentType(true);
            DocumentType typefromDb;
            string updatedBy = "DocumentTypeRepositoryTests.UpdateTest";
            bool actual;

            type.IsActive = true;
            type.TypeName = RandomValues.RandomString(18);
            type.DocumentStatuses.Add(DocumentStatusRepositoryTests.NewDocumentStatus(true));

            actual = documentTypeRepository.Update(type, updatedBy);
            Assert.AreEqual(true, actual);

            typefromDb = documentTypeRepository.GetById((int)type.Id);
            Assert.AreEqual(type.DocumentStatuses.Count, typefromDb.DocumentStatuses.Count);
        }

当多对多表已经存在时,如何将其设置为 EntityState.Unchanged?

2个回答

1
尝试替换:
db.DocumentTypes.Add((DocumentType)entity);
db.Entry(entity).State = EntityState.Modified;

使用

db.Entry(entity).State = entity.Id == null 
    ? EntityState.Added
    : EntityState.Modified;

在使用分离的实体进行CRUD操作时,不使用DbSet操作,仅操纵实体状态条目会更容易。至少,这会减少错误数量。

我之前在using语句上方有一行代码检查id: if (entity.Id == null) throw new InvalidOperationException("DocumentTypeId cannot be null on updates.");为了让示例更简洁,我将其删除了。有趣的是,通过您的示例,我绕过了错误,但它并没有保存添加到列表中的任何新状态,只保留了旧状态。 - Josh
关键部分是删除 .Add。 - Frank
我已经添加了我的单元测试,当我向列表中添加一个新的状态项并更新(使用新代码)时,它不会将新项添加到多对多表中。因此,最后一个断言失败,因为它只有第一个状态而不是两个。 - Josh
@Josh Frank是正确的,但事情比看起来复杂得多。我在一个答案中解释了它。评论是不够的。 - Gert Arnold

1

这里需要注意两点:

  1. 当你向上下文中添加一个实体时,整个实体拥有的对象图都会被标记为“已添加”。

  2. 在任何关联中,这也将标记关联为新的。然而,在1:n或1:1关联中,当其中一端的状态发生变化时,关联的状态也会发生变化,而在多对多关联中,有一部分关联是隐藏的,并且始终保持“已添加”的状态。(在幕后,多对多关联是一个1:n:1关联,其中n部分在您的代码中不可见)。

因此,这个声明...

db.DocumentTypes.Add(entity);

导致关联项获得“已添加”状态,随后...
db.Entry(entity).State = EntityState.Modified;

这不再改变。

对于现有的DocumentType,您必须确保其状态永远不会更改为Added,这可以通过检查其Id值来完成,与您对DocumentStatus所做的操作相同。

此外,您还需要更改设置DocumentStatuses状态的方式:

foreach (var item in entity.DocumentStatuses)
{
    db.Entry(item).State = item.Id != null
                               ? EntityState.Unchanged
                               : EntityState.Added;
}

我也曾这样想并尝试过。但是,如果您查看我的单元测试,您会发现我不是在添加新状态,而是在与现有状态添加新的1:n:1关系。因此,项目的EntityState状态将具有一个ID,因此它始终是EntityState.Unchanged,这是有道理的,因为它已经存在。 - Josh
我看不到那里发生了什么,但至少这是前进的方向。它在我现在测试的简单多对多中有效。 - Gert Arnold
在将“DocumentType”及其状态附加到上下文后,您必须删除一个项目,否则更改跟踪器将永远不会意识到曾经有一个要删除的项目。 - Gert Arnold
我开始认为最好的方法是将其创建为ReadOnly Collection,并创建一种不同的方式来添加/删除这些1:n:1关系。 - Josh
谢谢尝试。我将把列表转换为ReadOnlyCollection,并通过一组单独/专用的方法来管理这些关联。 - Josh
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接