【问题标题】:No new many to many connections are made in the database when saving an object in EF在 EF 中保存对象时,不会在数据库中建立新的多对多连接
【发布时间】:2013-08-08 17:19:43
【问题描述】:

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

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

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;
    }
}

save 方法只是调用上下文的 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;
    }
}

【问题讨论】:

  • 您是否对所有这些操作使用单一上下文?
  • 是的,我是。我使用 Ninject 将相同的上下文实例应用于我们所有的控制器。

标签: c# linq entity-framework


【解决方案1】:

你要知道这条线...

context.Entry(product).State = EntityState.Modified;

...对关系的状态没有影响。它只是将被传递到Entry 的实体product 标记为Modified,即标量属性Product.Name 被标记为已修改,仅此而已。发送到数据库的 SQL UPDATE 语句只会更新 Name 属性。它不会在多对多链接表中写入任何内容。

您可以更改与该行的关系的唯一情况是外键关联,即具有在模型中作为属性公开的外键的关联。

现在,多对多关系永远不会是外键关联,因为您无法在模型中公开外键,因为外键位于模型中没有对应实体的链接表中。多对多关系始终是独立的关联。

除了对关系状态条目的直接操作(这是相当高级的并且需要深入到ObjectContext)之外,只能使用实体框架的更改跟踪来添加或删除独立关联。此外,您必须考虑到标签可能已被用户删除,这要求必须删除链接表中的关系条目。要跟踪此类更改,您必须首先从数据库中加载给定产品的所有现有相关标签。

要将所有这些放在一起,您必须更改 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,因为我不确定product.ProductTags 集合中的标签是否附加到@987654334 @ 实例与否。通过使用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);

【讨论】:

  • 非常感谢您的回答。我将 SingleOrDefault() 更改为 .SingleOrDefault(p => p.Id == product.Id) 否则会引发异常。其余代码功能,但问题是 productInDb 已经应用了我要添加的标签。所以这两个 Any 方法总是返回 !true。是否有可能我过早地将产品放入上下文中,从而使该代码无法正常工作?
  • @Chris:对不起,SingleOrDefault 有误,我已经更正了。我不完全理解你的其他问题。如果数据库中的标签已经与productInDb 相关,并且您尝试在 UI 中再次添加它,则代码实际上应该对该标签不执行任何操作,因此 Any 调用 should 返回true 和来自productInDb.ProductTagsRemoveAdd 都不应该发生在这个集合中。我误解你的意思了吗?
  • 嗨,斯劳马。该关系实际上不存在于数据库中,但它存在于上下文中。因此,当我在 SingleOrDefault 方法之后立即中断并查看产品时。我刚刚尝试添加的标签就在那里,但它没有将它添加到数据库中。当我打电话保存时也没有。因此,您建议的代码没有到达 .Add 方法,因为它认为标签已经存在。
  • @Chris:这可能意味着包含标签的产品已经附加到上下文之前加载productInDb(在这种情况下,EF 将返回已附加的对象) .也许标签附在InsertAndOrUse 中?如果是,EF 可能会自动附加产品,因为标签已添加到其ProductTags 集合(关系修复)。您可以将此InsertAndOrUse 方法添加到您的问题中吗?
  • 我刚刚经历了一个类似的练习,我真的希望这比这个答案中解释的当前推荐的方法更容易处理:(
猜你喜欢
  • 2013-04-24
  • 2023-04-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-04-01
  • 1970-01-01
相关资源
最近更新 更多