3

リポジトリ パターン (MVC Storefront プロジェクトから) で Rob Conery のスピンを使用して DAL を実装しました。ここでは、Linq を使用してデータベース オブジェクトをドメイン オブジェクトにマップし、Linq to SQL を使用して実際にデータを取得します。

これはすべて素晴らしく機能しており、必要なドメイン オブジェクトの形状を完全に制御できますが、ここで質問したいと思っていた同時実行性の問題にぶつかりました。私は並行処理を行っていますが、解決策は間違っているように感じます (これらのぎこちない感情の 1 つにすぎません)。

基本的なパターンは次のとおりです。

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

したがって、その実装では、ドメイン タスクへのマッピングが原因で同時実行追跡​​が失われました。元のタスクを取得した時点でのタスクのデータコンテキスト リストであるプライベート テーブルを格納することで、元のタスクを取得します。

次に、この保存されたテーブルからタスクを更新し、更新したものを保存します

これは機能しています - 同時実行違反があると、必要に応じて変更競合の例外が発生します。

しかし、私はトリックを逃したと叫ぶだけです。

これを行うより良い方法はありますか?

datacontext の .Attach メソッドを見てきましたが、既に行っているのと同様の方法で元のバージョンを保存する必要があるようです。

また、ドメイン オブジェクトを廃止し、Linq to SQL で生成されたオブジェクトをスタックのずっと上に置くことで、これをすべて回避できることも知っていますが、同時実行の処理方法が嫌いなのと同じくらい嫌いです。

4

2 に答える 2

1

私はこれを実行し、次の解決策を見つけました。それは私(そしてもっと重要なことに、私のテスター!)が考えることができるすべてのテストケースで機能します。

.Attach()datacontextのメソッドと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;
        }

    }
于 2009-07-02T07:46:17.433 に答える