繁体   English   中英

EF6子实体未以多对多关系更新

[英]EF6 Child entities not updating in many-to-many relationship

我有两个多对多关系的实体:一个可以有很多标签的地图(许多地图可以使用一个标签)。

我正在尝试更新父Map实体,包括从其子标签集合中删除项目。 虽然Map实体尊重数据库中的更改,但对标签集合的更改从未受到尊重(除了最初创建它们之外)。 我有什么想法我做错了吗?

在数据库中有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来手动刷新我的关系。

查看UpdateMap中的代码,看起来您正处于断开连接的场景中?

如果是这样,在断开连接的方案中重新附加实体有两个步骤:

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

在根映射实体上将EntityState设置为Modified将将对象图中的所有实体附加到上下文。 但是,它会将所有子标记实体标记为未更改状态。 这与您的注释相对应,即对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();
}

为此,您的Tag类型将需要实现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. 尝试删除新列表和构造函数,并使ICollection虚拟化。
  2. 因为标签的id与地图上的标签匹配,而不是文本
  3. 您的更新地图附加地图,看看附加标签是否解决了问题。
  4. 我不知道,因为我在上下文中进行配置,但您只需要在其中一个实体上映射多对多的关系。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM