我有两个多对多关系的实体:一个可以有很多标签的地图(许多地图可以使用一个标签)。
我正在尝试更新父地图实体,包括从其子标签集合中删除项目。虽然Map实体尊重数据库中的更改,但对标签集合的更改从未受到尊重(除了最初创建它们之外)。我有什么想法我做错了吗?
在数据库中有3个表:
实体类:
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来手动刷新我的关系。
答案 0 :(得分:2)
在UpdateMap中查看您的代码,看起来您正在处于断开连接的场景中?
如果是这样,在断开连接的场景中重新附加实体有两个步骤:
在根映射实体上将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)
问题在于您是手动管理集合还是从上下文中分离出来,这是存储库模式的副作用,这就是为什么许多人说这是一种反模式。
我认为你所有的问题都是因为你正在使用存储库模式,我总是避免使用它,因为它会产生更多的问题,这只是我的看法,很多专家都不同意。