代码之家  ›  专栏  ›  技术社区  ›  David Hall

Linq到SQL和Rob Conry存储库模式的并发性

  •  3
  • David Hall  · 技术社区  · 16 年前

    基本模式是:

    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列表。

    这是可行的——当存在并发冲突时,我会引发更改冲突异常,正如我所希望的那样。

    有更好的方法吗?

    我已经研究了datacontext上的.Attach方法,但这似乎需要以与我已经在做的类似的方式存储原始版本。

    2 回复  |  直到 16 年前
        1
  •  1
  •   David Hall    15 年前

    我正在使用 .Attach() 方法,以及时间戳列。这在您第一次将特定主键保存回数据库时效果很好,但我发现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;
            }
    
        }
    
        2
  •  0
  •   Matt Kocaj    16 年前

    我会考虑尝试利用 .Attach 方法,通过传递“原始”和“更新”对象,从而从LINQ2SQL实现真正的乐观并发检查。此IMO优先于在DBML对象或域对象中使用版本或日期戳。但是,我不确定MVC是如何允许保留“原始”数据的。。我一直在尝试调查验证脚手架,希望它能存储“原始”数据。。但我怀疑它和最近的帖子(和/或验证失败)一样好。所以这个想法可能行不通。

    我的另一个疯狂想法是:覆盖所有域对象的GetHashCode(),其中哈希表示该对象的唯一数据集(当然减去ID)。然后,手动或使用助手将该散列隐藏在HTML POST表单的隐藏字段中,并将其与更新的域对象一起发送回服务层-在服务层或数据层中执行并发性检查(通过将原始散列与新提取的域对象的散列进行比较),但请注意 . 使用DMBL函数很好,但是将数据层抽象出来的想法并不依赖于特定实现的特性等。因此,完全控制服务层中域对象的乐观并发检查(例如)对我来说似乎是一个很好的方法。