Linq to SQL and concurrency with Rob Conery repository pattern

前端 未结 2 352
深忆病人
深忆病人 2021-01-14 04:49

I have implemented a DAL using Rob Conery\'s spin on the repository pattern (from the MVC Storefront project) where I map database objects to domain objects using Linq and u

2条回答
  •  爱一瞬间的悲伤
    2021-01-14 04:58

    I worked through this and found the following solution. It works in all the test cases I (and more importantly, my testers!) can think of.

    I am using the .Attach() method on the datacontext, and a TimeStamp column. This works fine for the first time that you save a particular primary key back to the database but I found that the datacontext throws a System.Data.Linq.DuplicateKeyException "Cannot add an entity with a key that is already in use."

    The work around for this I created was to add a dictionary that stored the item I attach the first time around and then every subsequent time I save I reuse that item.

    Example code is below, I do wonder if I've missed any tricks - concurrency is pretty fundamental so the hoops I'm jumping through seem a little excessive.

    Hopefully the below proves useful, or someone can point me towards a better implementation!

    private Dictionary _attachedPayments;
    
    public void SavePayments(IList payments)
        {
            Dictionary savedPayments =
                new Dictionary();
    
            // 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;
            }
    
        }
    

提交回复
热议问题