c# - 在 EF 中保存对象时,不会在数据库中建立新的多对多连接

标签 c# linq entity-framework

我的代码有问题,我尝试保存两个对象之间的多对多连接,但由于某种原因它没有被保存。

我们使用代码优先的方法来创建我们的数据库,在我们的数据库中,我们有以下实体,这个问题是关于:

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 方法。当我编辑产品并添加另一个标签时,它不会保存新标签。但是,当我在保存函数上设置断点时,我可以看到它们都在那里: enter image description here

当我打开新添加的标签“Oeh-la-la”时,我什至可以通过它回顾产品: enter image description here

但是当保存发生时,所有其他值都成功,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/

相关文章:

c# - 如何获取委托(delegate)调用的结果列表?

c# - 为什么我会收到 System.OutOfMemoryException

c# - 在 ASP.NET Core 中设置 Entity Framework

c# - LINQ 改变本地 "let"变量

c# - Entity Framework 内存未释放

mysql - EF Code First 数据注释和继承

c# - 在 C# 中使用 WIA 生成照片质量扫描

c# - ScaleTransform 中的 DoubleAnimation

c# - 使用自然语言处理在 .Net 框架中进行简历解析

c# - 如何使用linq将excel文件上传到数据库?