Linq to SQL и параллелизм с шаблоном хранилища Rob Conery - PullRequest
3 голосов
/ 11 мая 2009

Я реализовал DAL, используя вращение Роба Конери на шаблоне хранилища (из проекта MVC Storefront), где я сопоставляю объекты базы данных с объектами домена с помощью Linq и использую Linq для SQL, чтобы фактически получить данные.

Все это прекрасно работает, давая мне полный контроль над формой моих доменных объектов, которые я хочу, но у меня возникла проблема с параллелизмом, о которой я подумал, я бы хотел спросить здесь. У меня работает параллелизм, но решение кажется, что оно может быть неправильным (только одно из этих неприятных ощущений).

Основной шаблон:

private MyDataContext _datacontext
private Table _tasks;

public Repository(MyDataContext datacontext)
{
    _dataContext = datacontext;
}

public void GetTasks()
{
    _tasks = from t in _dataContext.Tasks;

    return from t in _tasks
        select new Domain.Task
        {
            Name = t.Name,
            Id = t.TaskId,
            Description = t.Description                              
        };
}

public void SaveTask(Domain.Task task)
{
    Task dbTask = null;

    // Logic for new tasks omitted...

    dbTask = (from t in _tasks
        where t.TaskId == task.Id
        select t).SingleOrDefault();

    dbTask.Description = task.Description,
        dbTask.Name = task.Name,

    _dataContext.SubmitChanges();
} 

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

Затем я обновляю задачи из этой сохраненной таблицы и сохраняю то, что обновил

Это работает - я получаю исключения конфликтов изменений, когда возникают нарушения параллелизма, как я хочу.

Однако мне просто кричит, что я пропустил трюк.

Есть ли лучший способ сделать это?

Я посмотрел на метод .Attach в текстовом тексте, но, похоже, для этого требуется сохранение исходной версии аналогично тому, что я уже делаю.

Я также знаю, что я мог бы избежать всего этого, покончив с объектами домена и позволяя сгенерированным Linq to SQL объектам полностью подниматься по моему стеку - но мне это не нравится так же, как и то, как я обрабатываю параллелизм.

Ответы [ 2 ]

1 голос
/ 02 июля 2009

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

Я использую метод .Attach() для текста данных и столбец TimeStamp. Это нормально работает в первый раз, когда вы сохраняете конкретный первичный ключ обратно в базу данных, но я обнаружил, что datacontext выдает System.Data.Linq.DuplicateKeyException «Невозможно добавить объект с ключом, который уже используется».

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

Пример кода ниже, мне интересно, пропустил ли я какие-то трюки - параллелизм довольно фундаментален, поэтому обручи, через которые я прыгаю, кажутся немного чрезмерными.

Надеюсь, что приведенное ниже окажется полезным, или кто-то может указать мне на лучшую реализацию!

private Dictionary<int, Payment> _attachedPayments;

public void SavePayments(IList<Domain.Payment> payments)
    {
        Dictionary<Payment, Domain.Payment> savedPayments =
            new Dictionary<Payment, Domain.Payment>();

        // Items with a zero id are new
        foreach (Domain.Payment p in payments.Where(p => p.PaymentId != 0))
        {
            // The list of attached payments that works around the linq datacontext  
            // duplicatekey exception
            if (_attachedPayments.ContainsKey(p.PaymentId)) // Already attached
            {
                Payment dbPayment = _attachedPayments[p.PaymentId];                    
                // Just a method that maps domain to datacontext types
                MapDomainPaymentToDBPayment(p, dbPayment, false);
                savedPayments.Add(dbPayment, p);
            }
            else // Attach this payment to the datacontext
            {
                Payment dbPayment = new Payment();
                MapDomainPaymentToDBPayment(p, dbPayment, true);
                _dataContext.Payments.Attach(dbPayment, true);
                savedPayments.Add(dbPayment, p);
            }
        }

        // There is some code snipped but this is just brand new payments
        foreach (var payment in newPayments)
        {
            Domain.Payment payment1 = payment;
            Payment newPayment = new Payment();
            MapDomainPaymentToDBPayment(payment1, newPayment, false);
            _dataContext.Payments.InsertOnSubmit(newPayment);
            savedPayments.Add(newPayment, payment);
        }

        try
        {
            _dataContext.SubmitChanges();
            // Grab the Timestamp into the domain object
            foreach (Payment p in savedPayments.Keys)
            {
                savedPayments[p].PaymentId = p.PaymentId;
                savedPayments[p].Timestamp = p.Timestamp;
                _attachedPayments[savedPayments[p].PaymentId] = p;
            }
        }
        catch (ChangeConflictException ex)
        {
            foreach (ObjectChangeConflict occ in _dataContext.ChangeConflicts)
            {
                Payment entityInConflict = (Payment) occ.Object;

                // Use the datacontext refresh so that I can display the new values
                _dataContext.Refresh(RefreshMode.OverwriteCurrentValues, entityInConflict);
                _attachedPayments[entityInConflict.PaymentId] = entityInConflict;

            }
            throw;
        }

    }
0 голосов
/ 11 мая 2009

Я хотел бы взглянуть на попытку использовать метод .Attach, передавая «оригинальные» и «обновленные» объекты, таким образом достигая истинной оптимистической проверки параллелизма из LINQ2SQL. В этом IMO предпочтительнее использовать метки версии или даты / времени либо в объектах DBML, либо в объектах вашего домена. Я не уверен, как MVC допускает эту идею сохранения «оригинальных» данных, однако ... я пытался исследовать леса валидации в надежде, что он хранит "оригинальные" данные ... но я подозреваю, что это так же хорошо, как и самый последний пост (и / или неудачная проверка). Так что эта идея может не сработать.

Еще одна сумасшедшая идея, которая у меня была, заключалась в следующем: переопределить GetHashCode () для всех ваших доменных объектов, где хеш представляет уникальный набор данных для этого объекта (за исключением, конечно, идентификатора). Затем, либо вручную, либо с помощью помощника, который хэширует скрытое поле в форме HTML POST и отправляет его обратно на уровень обслуживания с обновленным объектом домена - выполните проверку параллелизма на уровне службы или уровне данных (сравнивая исходный уровень). хеш с недавно извлеченным хешем объекта домена), но помните, что вам нужно самостоятельно проверять и повышать исключения параллелизма . Приятно использовать функции DMBL, но идея абстрагирования уровня данных состоит в том, чтобы не зависеть от особенностей конкретной реализации и т. Д. Поэтому, кажется, полный контроль над оптимистической проверкой параллелизма над объектами вашего домена на уровне сервисов (например) как хороший подход ко мне.

...