我的代码有问题,我尝试保存两个对象之间的多对多连接,但由于某种原因它没有被保存。
我们使用代码优先的方法来创建我们的数据库,在我们的数据库中,我们有以下实体,这个问题是关于:
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<ProductTag> ProductTags { get; set; }
}
public class ProductTag
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Product> Products { get; set; }
}
自动创建了表 ProductTagProducts,这当然只是两者之间的连接表。
现在创建产品工作正常。我们只需运行以下命令,它就会在 ProductTagProducts 表中创建连接:
Product.ProductTags.Add(productTag);
为了确保数据库中没有重复的任务,我们自己处理了它的保存。 productTag 始终包含具有现有 ID 的产品标签。
当我们要编辑同一个或另一个产品时,就会出现问题。产品已有标签。我们使用以下过程来保存它:
List<ProductTag> productTags = new List<ProductTag>();
string[] splittedTags = productLanguagePost.TagList.Split(',');
foreach (string tag in splittedTags) {
ProductTag productTag = new ProductTag();
productTag.Name = tag;
productTags.Add(productTagRepository.InsertAndOrUse(productTag));
}
我们用逗号分隔标签,这就是从 HTML 元素接收标签的方式。然后我们为它定义一个新的实体,并使用 InsertAndOrUse 来判断标签是否已经存在。如果标签已经存在,则返回相同的实体,但填充了ID,如果不存在,则将标签添加到数据库中,然后也返回带有ID的实体。我们创建一个新列表以确保产品中没有重复的 Id(我已经尝试将其直接添加到产品的现有标签列表中,结果相同)。
product.ProductTags = productTags;
productRepository.InsertOrUpdate(product);
productRepository.Save();
然后我们将列表设置为 ProductTags 并让存储库处理插入或更新,当然会进行更新。为了以防万一,这是 InsertOrUpdate 函数:
public void InsertOrUpdate(Product product) {
if (product.Id == default(int)) {
context.Products.Add(product);
} else {
context.Entry(product).State = EntityState.Modified;
}
}
保存方法只是调用上下文的 SaveChanges 方法。当我编辑产品并添加另一个标签时,它不会保存新标签。但是,当我在保存函数上设置断点时,我可以看到它们都在那里:
当我打开新添加的标签“Oeh-la-la”时,我什至可以通过它回顾产品:
但是当保存发生时,所有其他值都成功,ProductTagProducts 表中没有建立任何连接。也许这真的很简单,但我现在一无所知。真心希望别人能给个亮眼的。
提前致谢。
编辑:根据要求,ProductTag 的 InsertAndOrUse 方法。它调用的InsertOrUpdate方法和上面完全一样。
public ProductTag InsertAndOrUse(ProductTag productTag)
{
ProductTag resultingdProductTag = context.ProductTags.FirstOrDefault(t => t.Name.ToLower() == productTag.Name.ToLower());
if (resultingdProductTag != null)
{
return resultingdProductTag;
}
else
{
this.InsertOrUpdate(productTag);
this.Save();
return productTag;
}
}
最佳答案
你要知道这一行...
context.Entry(product).State = EntityState.Modified;
...对关系的状态没有影响。它只是将实体 product
标记为 Modified
传递到 Entry
,即标量属性 Product.Name
被标记作为修改,没有别的。发送到数据库的 SQL UPDATE
语句仅更新 Name
属性。它不会将任何内容写入多对多链接表。
您可以更改与该行的关系的唯一情况是外键关联,即具有公开为模型中的属性的外键的关联。
现在,多对多关系永远不是外键关联,因为您不能在模型中公开外键,因为外键位于链接表中,而在您的模型中没有对应的实体。多对多关系始终是独立的关联。
除了直接操作关系状态条目(这是相当高级的并且需要深入到 ObjectContext
)之外,只能使用 Entity Framework 的更改跟踪来添加或删除独立的关联。此外,您必须考虑到标签可能已被用户删除,这要求必须删除链接表中的关系条目。要跟踪此类更改,您必须首先从数据库中加载给定产品的所有现有相关标签。
要将所有这些放在一起,您必须更改 InsertOrUpdate
方法(或引入新的专用方法):
public void InsertOrUpdate(Product product) {
if (product.Id == default(int)) {
context.Products.Add(product);
} else {
var productInDb = context.Products.Include(p => p.ProductTags)
.SingleOrDefault(p => p.Id == product.Id);
if (productInDb != null) {
// To take changes of scalar properties like Name into account...
context.Entry(productInDb).CurrentValues.SetValues(product);
// Delete relationship
foreach (var tagInDb in productInDb.ProductTags.ToList())
if (!product.ProductTags.Any(t => t.Id == tagInDb.Id))
productInDb.ProductTags.Remove(tagInDb);
// Add relationship
foreach (var tag in product.ProductTags)
if (!productInDb.ProductTags.Any(t => t.Id == tag.Id)) {
var tagInDb = context.ProductTags.Find(tag.Id);
if (tagInDb != null)
productInDb.ProductTags.Add(tagInDb);
}
}
}
我在上面的代码中使用了 Find
因为我不确定你的代码片段(缺少 InsertAndOrUse
的确切代码)如果 product.ProductTags
集合是否附加到 context
实例。通过使用 Find
,无论它们是否附加,它都应该可以工作,这可能会以加载标签的数据库往返为代价。
如果 product.ProductTags
中的所有标签都已附加,您可以替换...
var tagInDb = context.ProductTags.Find(tag.Id);
if (tagInDb != null)
productInDb.ProductTags.Add(tagInDb);
...只是通过
productInDb.ProductTags.Add(tag);
或者如果不能保证它们都已附加并且您想避免往返数据库(因为您确定标签至少存在于数据库中,无论是否附加)您可以将代码替换为:
var tagInDb = context.ProductTags.Local
.SingleOrDefault(t => t.Id == tag.Id);
if (tagInDb == null) {
tagInDb = tag;
context.ProductTags.Attach(tagInDb);
}
productInDb.ProductTags.Add(tagInDb);
关于c# - 在 EF 中保存对象时,不会在数据库中建立新的多对多连接,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17989910/