当向系统输入新的Post
时,需要实例化一些Tags
并将其与Post
实例相关联。其中一些Tags
已经存在于数据库中,其他则不会并且需要插入。
一个例子:
var post = new Post {
Slug = "hello-world",
Title = "Hello, World!",
Content = "this is my first post.",
Tags = new List<Tag>()
};
var tag = new Tag { Name = "introduction" };
post.Tags.Add(tag);
当数据库中不存在关联的Tag
时,我可以依赖对DbSet<T>.Add
的简单调用来将post和关联的标记插入到db中。
但是,尝试插入包含已存在于db中的关联标记的帖子会导致标记表上发生主键冲突。
为了解决这个问题,我尝试将Attach
每个标记添加到数据库上下文中,当数据库中已存在标记时,该数据库上下文会超级工作,否则会引发以下内部异常的异常:< / p>
INSERT语句与FOREIGN KEY约束冲突&#34; FK_dbo.TagPosts_dbo.Tags_Tag_Name&#34;。冲突发生在 数据库&#34; EF.Domain.BlogDb&#34;,表&#34; dbo.Tags&#34;,列 &#39;名称&#39 ;.声明已经终止。&#34;}
我想仅在需要时将与帖子关联的标签插入数据库。我怎样才能做到这一点?
答案 0 :(得分:2)
你的困境非常有趣。如果您不使用通用存储库,但知道要附加的实体的主键的特定存储库,则可以使用如下代码:
var tagExists = Tags.Any(t => t.Name == tag.Name);
或
var tag = Tags.Find(tag.Name);
但是在您的情况下,我们需要一种更通用的方法,例如获取Repository类使用的实体的主键,而不管实体的类型。为实现这一点,我在DbContext类上创建了两个扩展方法:
public static IList<string> GetPrimaryKeyNames<TEntity>(this DbContext context)
where TEntity : class
{
var objectContext = ((IObjectContextAdapter)context).ObjectContext;
var set = objectContext.CreateObjectSet<TEntity>();
return set.EntitySet.ElementType
.KeyMembers
.Select(k => k.Name)
.ToList();
}
public static IList<object> GetPrimaryKeyValues<TEntity>(this DbContext context, TEntity entity)
where TEntity : class
{
var valueList = new List<object>();
var primaryKeyNames = context.GetPrimaryKeyNames<TEntity>();
foreach(var propertyInfo in entity.GetType().GetProperties())
{
if (primaryKeyNames.Contains(propertyInfo.Name))
{
valueList.Add(propertyInfo.GetValue(entity));
}
}
return valueList;
}
利用这些方法,您可以更改Repository类的Attach方法,如下所示:
public void Attach(TEntity entity)
{
var storeEntity = _context.Set<TEntity>().Find(
_context.GetPrimaryKeyValues(entity).ToArray());
if (storeEntity != null)
{
_context.Entry(storeEntity).State = EntityState.Detached;
_context.Set<TEntity>().Attach(entity);
}
}