EF6子实体在多对多关系中没有更新

本文关键字:更新 关系 实体 EF6 | 更新日期: 2023-09-27 18:12:27

我有两个多对多关系的实体:一个Map可以有多个Tag(一个Tag反过来可以被多个Map使用)。

我正在尝试更新父Map实体,包括从其子标签集合中删除项。当Map实体响应数据库中的更改时,对Tags集合的更改从不响应(除了它们的初始创建)。知道我哪里做错了吗?

数据库中有3个表:

  • 标记
  • MapTags

实体类:

public class Map
{
    public Map()
    {
        Tags = new List<Tag>();
    }
    public string Id { get; set; }
    ...
    public ICollection<Tag> Tags { get; set; }
}
public class Tag
{
    public Tag()
    {
        Maps = new List<Map>();
    }
    public int Id { get; set; }
    public string Text { get; set; }
    public ICollection<Map> Maps { get; set; }
}

和EF6的映射:

public class MapMap : EntityTypeConfiguration<Map>
{
    public MapMap()
    {
        // Primary Key
        this.HasKey(t => new { t.Id });
        // Properties
        this.Property(t => t.Id)
            .IsRequired()
            .HasMaxLength(32);
        ...
        this.ToTable("Map");
        this.Property(t => t.Id).HasColumnName("Id");
        ...
        // Relationships
        this.HasMany(m => m.Tags)
            .WithMany(t => t.Maps)
            .Map(m =>
            {
                m.MapLeftKey("MapId");
                m.MapRightKey("MapTagId");
                m.ToTable("MapTags");
            });
    }
}
public class TagMap : EntityTypeConfiguration<Tag>
{
    public TagMap()
    {
        // Primary Key
        this.HasKey(t => new { t.Id });
        // Properties
        this.Property(t => t.Id)
            .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
        this.Property(t => t.Text)
            .IsRequired()
            .HasMaxLength(256);
        // Table & Column Mappings
        this.ToTable("Tag");
        this.Property(t => t.Id).HasColumnName("Id");
        this.Property(t => t.Text).HasColumnName("Text");
        // Relationships
        this.HasMany(t => t.Maps)
            .WithMany(m => m.Tags)
            .Map(m =>
            {
                m.MapLeftKey("TagId");
                m.MapRightKey("MapId");
                m.ToTable("MapTags");
            });
    }
}

更新地图标签的代码:

map.Tags = new List<Tag>();
foreach (string item in data.tags)
{
    Tag tag = MapRepository.FindTagByText(item);
    if (tag == null)
    {
        try
        {
            tag = WebMapRepository.CreateTag(new Tag()
                {
                    Text = item
                });
        }
        catch (DbEntityValidationException ex)
        {
            DisplayValidationErrors(ex, "Tag [" + item + "] validation errors:");
            throw; // Abort
        }
    }
    map.Tags.Add(tag);
}

和更新地图的DAL代码:

public static Map UpdateMap(Map map)
{
    using (MapContext context = new MapContext())
    {
        context.Maps.Attach(map);
        context.Entry(map).State = EntityState.Modified;
        context.SaveChanges();
        return GetMap(map.Id);
    }
}

解决方案虽然我更喜欢更优雅的解决方案,但现在我只是直接运行SQL手动刷新我的关系。

EF6子实体在多对多关系中没有更新

在UpdateMap中查看您的代码,看起来您正在断开连接的场景中工作?

如果是,在断开连接的场景中重新连接实体有两个步骤:

  1. 将实体图重新附加到上下文,以便跟踪
  2. 设置图形
  3. 中每个实体的状态

在根Map实体上设置EntityState为Modified将把对象图中的所有实体附加到上下文。但是,它会用Unchanged状态标记所有子Tag实体。这将对应于您的评论,即对Map实体的更改正在持久化,但对Tag集合的更改没有。

这个问题的解决方案很大程度上取决于应用程序的体系结构。一种可能的解决方案是从数据库中获取相应的Map实体,然后根据更新后的地图遍历其对象图,并相应地绘制其状态。

下面的示例显示了被查询的映射实体集合中的标签,这些标签不在断开连接的映射的标签集合中,它们与映射的关系被删除:

public static Map UpdateMap(Map map)
{
    // get map in its current state
    var previousMap = context.Maps
       .Where(m => m.Id == map.Id)
       .Include(m => m.Tags)
       .Single();
    // work out tags deleted in the updated map
    var deletedTags = previousMap.Tags.Except(map.Tags).ToList();
    // remove the references to removed tags
    deletedTags.ForEach(t => previousMap.Tags.Remove(t));
    // .. deal with added tags
    // very similar code to deleted so not showing
    context.SaveChanges();
}

要做到这一点,你的标签类型需要实现IEquatable<Tag>,以允许Except操作在集合上正确工作(因为它是一个引用类型)。

注意我使用HashSets而不是在Lists的问题,但这只是一个实现细节。

   public class Tag : IEquatable<Tag>
    {
        public Tag()
        {
            Maps = new HashSet<Map>();
        }
        public int Id { get; set; }
        public string Text { get; set; }
        public virtual ISet<Map> Maps { get; private set; }
        public bool Equals(Tag other)
        {
            if (ReferenceEquals(null, other)) return false;
            if (ReferenceEquals(this, other)) return true;
            return Id == other.Id;
        }
        public override bool Equals(object obj)
        {
            if (ReferenceEquals(null, obj)) return false;
            if (ReferenceEquals(this, obj)) return true;
            if (obj.GetType() != GetType()) return false;
            return Equals((Tag) obj);
        }
        public override int GetHashCode()
        {
            return Id;
        }
        public static bool operator ==(Tag left, Tag right)
        {
            return Equals(left, right);
        }
        public static bool operator !=(Tag left, Tag right)
        {
            return !Equals(left, right);
        }
    }

我将把测试项目放到GitHub上。

你的问题是你正在手动管理集合,并且与上下文分离,这是存储库模式的副作用,这就是为什么许多人说它是反模式的原因。

  1. 尝试删除新的列表和构造函数,并使iccollection为虚拟的。
  2. 因为标签有一个id匹配地图上的标签通过id而不是通过文本
  3. 你的更新地图正在附加地图,看看附加标签是否解决了问题。
  4. 我不知道,因为我在上下文中做了配置,但是你应该只需要在一个实体上映射多对多关系。

我认为你所有的问题都是因为你在使用存储库模式,我总是避免使用它,因为它产生的问题比它解决的问题更多,这只是我的观点,许多专家不同意。