Linq to SQL: порядок выполнения при вызове SubmitChanges () - PullRequest
20 голосов
/ 15 января 2009

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

Глядя на операторы SQL, возвращаемые DataContenxt.GetChangeText (), я могу видеть, что INSERT выполняются перед DELETE (даже если я вызывал DeleteAllOnSubmit () перед Add).

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

Спасибо.

Ответы [ 5 ]

18 голосов
/ 31 июля 2009

Да, по какой-то причине 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();
    }
3 голосов
/ 18 сентября 2011

Лучшее решение, которое не требует заранее знать, какие объекты изменились, - это использовать частичные методы 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 голосов
/ 29 июня 2009

У меня была такая же проблема. Я придумал способ обойти эту проблему, который мне подходит: я вручную запускаю 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()
1 голос
/ 29 июня 2009

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

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

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

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;
}
0 голосов
/ 31 июля 2009

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