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

时间:2014-07-10 02:02:22

标签: c# entity-framework-6

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

我正在尝试更新父地图实体,包括从其子标签集合中删除项目。虽然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代码更新Map:

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来手动刷新我的关系。

2 个答案:

答案 0 :(得分:2)

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

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

  1. 将实体图表重新附加到上下文以便跟踪
  2. 设置图表中每个实体的状态
  3. 在根映射实体上将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();
    }
    

    为此,您的标记类型需要实现IEquatable<Tag>以允许集合上的Except操作正常工作(因为它是引用类型)。

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

    E.g。

       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 :(得分:1)

问题在于您是手动管理集合还是从上下文中分离出来,这是存储库模式的副作用,这就是为什么许多人说这是一种反模式。

  1. 尝试删除新列表和构造函数,并使ICollection虚拟化。
  2. 因为标记的ID与地图上的标记匹配,而不是文字
  3. 您的更新地图附有地图,看看附加标签是否解决了问题。
  4. 我不知道,因为我在上下文中进行配置,但您只需要在其中一个实体上映射多对多的关系。
  5. 我认为你所有的问题都是因为你正在使用存储库模式,我总是避免使用它,因为它会产生更多的问题,这只是我的看法,很多专家都不同意。