代码之家  ›  专栏  ›  技术社区  ›  Alan Mendelevich

Linq到SQL:调用SubmitChanges()时的执行顺序

  •  21
  • Alan Mendelevich  · 技术社区  · 16 年前

    我有两个相关的数据库表,其简化形式如下

    Product(
      product_id,
      name
    )
    
    ProductSpecs(
      spec_id,
      product_id,
      name,
      value
    )
    

    外键通过product_id字段设置,ProductSpecs表对(product_id,name)对具有唯一约束。

    现在在我的ASP中。NET MVC应用程序,当用户编辑产品规格并保存数据时,我删除旧规格并将其全部作为新规格插入。

    为此,我首先调用DataContext。DeleteAllOnSubmit()并提供当前(旧)的ProductSpecs作为参数,然后我将新的规范添加到Product中。产品规格集合。

    然后我调用DataContext。SubmitChanges(),并得到一个错误,即我的唯一约束被违反了。

    通过查看DataContenxt返回的SQL语句。GetChangeText()我可以看到INSERT在DELETE之前执行(即使我在Add之前调用了DeleteAllOnSubmit())。

    这种行为的原因是什么,如何修复或解决?

    谢谢。

    5 回复  |  直到 16 年前
        1
  •  19
  •   Thomas Eyde    16 年前

    是的,出于某种原因,Linq到SQL将所有删除作为最后一件事来执行。这是无法改变的。

    或者可能有。我还没有研究codegen DataContext,看看我们是否可以覆盖其中的某些内容。

    您可以根据需要多次调用SubmitChanges()。当我需要首先删除时,我的解决方法是标记我的删除,调用SubmitChanges(),然后执行插入和更新,并再次调用SubmitChanges。

    您可以将所有内容包装在TransactionScope中:

        var deletables =
            from toDelete in db.NamedValues
            where toDelete.Name == knownValue.Name
            select toDelete;
    
        using (var scope = new TransactionScope())
        {
            db.NamedValues.DeleteAllOnSubmit(deletables);
            db.SubmitChanges();
    
            db.NamedValues.InsertOnSubmit(knownValue);
            db.SubmitChanges();
    
            scope.Complete();
        }
    
        2
  •  4
  •   Antony    13 年前

    一个不需要提前知道哪些实体发生了变化的更好的解决方案是使用DataContext的分部方法来拦截插入并首先推送删除。

    public partial class YourDataContext
    {
        List<EntityX> _deletedEntities = new List<EntityX>();
    
        partial void InsertEntityX(EntityX instance)
        {
            // Run deletes before inserts so that we don't run into any index violations
            var deletes =
                this.GetChangeSet().Deletes
                .Where(d => d.GetType().Equals(instance.GetType()))
                .Cast<EntityX>().ToList();
    
            var replaced = deletes.SingleOrDefault(d => d.UniqueKeyId == instance.UniqueKeyId);
    
            if (replaced != null)
            {
                DeleteEntityX(replaced);
                _deletedEntities.Add(replaced);
            }
    
            this.ExecuteDynamicInsert(instance);
        }
    
        partial void DeleteEntityX(EntityX instance)
        {
            if (_deletedEntities.Contains(instance))
            {
                _deletedEntities.Remove(instance);
                return;
            }
    
            this.ExecuteDynamicDelete(instance);
        }
    }
    
        3
  •  3
  •   Grokys    13 年前

    我也遇到了同样的问题。我想出了一个破解方法来解决这个问题,这似乎对我有效:在调用SubmitChanges()之前,我为更改集中的每个可能有问题的删除手动执行一些SQL:

    foreach (object o in m_Db.GetChangeSet().Deletes)
    {
        LibraryMember m = o as LibraryMember;
    
        if (m != null)
        {
            m_Db.ExecuteCommand("DELETE FROM LibraryMembers WHERE LibraryMemberId={0}", m.LibraryMemberId);
        }
    }
    
    m_Db.SubmitChanges()
    
        4
  •  1
  •   Grokys    16 年前

    另一种解决方案是以正确的顺序将更改附加到另一个数据上下文。然而,这种解决方案的缺点是,仍然从应用程序的其他部分引用的、来自原始数据上下文的任何实体现在都将无效:(

    我尝试了一个稍微的变化,为提交创建了第二个临时数据上下文。但我无法弄清楚如何将原始数据上下文恢复到干净的状态。

    这非常令人沮丧:(

    public void Save()
    {
        string connectionString = m_Db.Connection.ConnectionString;
        IList<object> deletes = m_Db.GetChangeSet().Deletes;
        IList<object> inserts = m_Db.GetChangeSet().Inserts;
        IList<object> updates = m_Db.GetChangeSet().Updates;
    
        m_Db.Dispose();
        m_Db = new MyDataContext(connectionString);
    
        Attach(m_Db, deletes);
        m_Db.SubmitChanges();
        Attach(m_Db, updates);
        m_Db.SubmitChanges();
        Attach(m_Db, inserts);
        m_Db.SubmitChanges();
    }
    
    void Attach(DataContext context, IList<object> items)
    {
        foreach (object o in items)
        {
            context.GetTable(o.GetType()).Attach(Clone(o));
        }
    }
    
    object Clone(object o)
    {
        object result = o.GetType().GetConstructor(Type.EmptyTypes).Invoke(null);
        foreach (PropertyInfo property in o.GetType().GetProperties())
        {
            if (property.PropertyType.IsValueType)
            {
                property.SetValue(result, property.GetValue(o, null), null);
            }
        }
        return result;
    }
    
        5
  •  0
  •   Jim Wooley    16 年前

    与其只是扔掉所有的孩子并重新创建它们,不如考虑付出更多的努力,分两步完成:添加尚未添加的项目,删除之前添加但不再需要的项目。例如,在ThinqLinq.com网站上,我有可以分配到类别的帖子。在编辑屏幕中,我提供了一个可以选择和取消选择的类别列表。当我收到包含所选类别列表的帖子时,我会使用以下方法删除和更新相应的记录:

    Dim selectedCats() As String = CType(ValueProvider("categories").RawValue, String())
    For Each catId In selectedCats.Except(From cp In currentPost.CategoryPosts _
                                          Select id = cp.CategoryID.ToString())
       'Add new categories
       currentPost.CategoryPosts.Add(New CategoryPost With {.CategoryID = CInt(catId)})
    Next
    
    'Delete removed categories
    dc.CategoryPosts.DeleteAllOnSubmit(From cp In currentPost.CategoryPosts _
                                       Where Not selectedCats.Contains(cp.CategoryID.ToString))
    
    推荐文章