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

时间:2009-01-15 08:10:21

标签: .net sql linq linq-to-sql

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

Product(
  product_id,
  name
)

ProductSpecs(
  spec_id,
  product_id,
  name,
  value
)

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

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

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

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

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

此行为的原因是什么以及如何修复或解决此问题?

感谢。

5 个答案:

答案 0 :(得分:18)

是的,出于某种原因,Linq to 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();
    }

答案 1 :(得分:3)

我一直遇到同样的问题。我想出了一个黑客来解决它,这似乎对我有用:我在调用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()

答案 2 :(得分:3)

一个更好的解决方案,不需要事先知道哪些实体已经改变,就是使用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 :(得分:1)

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

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

非常令人沮丧:(

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

答案 4 :(得分:0)

不要只是丢掉所有的孩子并重新创建它们,而是考虑花一点力气,分两步完成:添加尚未添加的项目并删除那些预先添加但不再需要的项目。例如,在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))