Подтвердить что ты не робот

Linq to SQL: порядок выполнения при вызове SubmitChanges()

У меня есть две связанные таблицы базы данных, которые в упрощенной форме выглядят так:

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() и получаю сообщение о том, что мое уникальное ограничение было нарушено.

Изучая инструкции SQL, возвращаемые DataContenxt.GetChangeText(), я вижу, что INSERT выполняются до DELETE (даже если я назвал DeleteAllOnSubmit() перед добавлением).

В чем причина этого поведения и как его исправить или обмануть?

Спасибо.

4b9b3361

Ответ 1

Да, по какой-то причине 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();
    }

Ответ 2

У меня была такая же проблема. Я придумал хак, чтобы обойти его, что, похоже, работает для меня: я вручную выполняю SQL для каждого потенциально проблемного удаления в наборе изменений перед вызовом функции SubmitChanges():

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()

Ответ 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);
    }
}

Ответ 4

Другим решением является привязка изменений к другому контексту данных в правильном порядке. Однако недостатком этого решения является то, что любые объекты, все еще ссылающиеся на остальную часть вашего приложения, которые были из исходного контекста данных, теперь будут недействительными: (

Я пробовал небольшую вариацию, создавая второй временный контекст данных для фиксации. Но я не могу понять, как получить исходный контекст данных в чистое состояние.

Это очень расстраивает: (

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

Вместо того, чтобы просто выбросить всех детей и воссоздать их, подумайте о том, чтобы приложить немного усилий и сделать это в два этапа: добавьте элементы, которые еще не добавлены, и удалите те, которые ранее добавлены, но больше не нужны, Например, на сайте 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))