3

我已经使用 Rob Conery 对存储库模式(来自 MVC Storefront 项目)的旋转实现了一个 DAL,其中我使用 Linq 将数据库对象映射到域对象,并使用 Linq to 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();
} 

因此,通过该实现,由于映射到域任务,我失去了并发跟踪。我通过存储私有表来取回它,这是我在获取原始任务时的任务数据上下文列表。

然后我从这个存储的表中更新任务并保存我更新的内容

这是有效的 - 当存在并发冲突时,我会收到更改冲突异常,就像我想要的那样。

然而,它只是对我尖叫,我错过了一个把戏。

有没有更好的方法来做到这一点?

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

我也知道我可以通过取消域对象并让 Linq to SQL 生成的对象一直在我的堆栈中来避免这一切——但我不喜欢这一点,就像我不喜欢我处理并发的方式一样。

4

2 回答 2

1

我解决了这个问题并找到了以下解决方案。它适用于我(更重要的是,我的测试人员!)能想到的所有测试用例。

.Attach()在 datacontext 和 TimeStamp 列上使用该方法。这在您第一次将特定主键保存回数据库时工作正常,但我发现数据上下文抛出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 回答
0

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

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

于 2009-05-11T06:15:21.913 回答