在EF中保存对象时,数据库中没有新的多对多连接

时间:2013-08-01 09:17:39

标签: 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;
    }
}

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

1 个答案:

答案 0 :(得分:6)

你必须知道这一行......

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

...对关系的状态没有影响。它只是将实体product标记为Entry Modified,即标量属性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);