如何在具有并发控制的 .NET 和 EF 应用程序中设计持久层?

Posted

技术标签:

【中文标题】如何在具有并发控制的 .NET 和 EF 应用程序中设计持久层?【英文标题】:How to design the persistence layer in a .NET and EF application with concurrency control? 【发布时间】:2016-09-12 19:50:44 【问题描述】:

I have read this post 和我认为很清楚的理论。我有一个 DAL,它只有在数据库中添加、获取、更新和删除信息的方法。

所以我想我有一个应用程序,其中有客户、订单和客户类型。客户类型有一个百分比来设置对一种客户类型的折扣。

业务层向 DAL 请求客户端类型以了解折扣。 业务层创建带有价格的订单并根据客户类型应用折扣。 业务层向 DAL 发送添加新订单的命令,发送新订单。

在代码中我可以这样:

达尔:

public async getClientType(long paramIDClientType)

    using(Entities myDbContext = new Entities())
    
        return await myDbContext.ClientTypes.Where(x=> x.IDType == paramIDClientType).SingleOrDefault();
    


public async addOrder(Orders paramNewOrder)

    using(Entities myDbContext = new Entities())
    
        myDbContext.Orders.Local.Add(paramNewOrder);
        await myDbContext.SaveChangesAsync();
    

业务层:

public void addOrderToClient(CLients paramClient)

    ClientTypes myType = myDAL.getClientType(paramClient.IDClient);

    ORder myNewOrder = myNewOder();
    myNewOrder.IDClient = paramClientIdCLient;
    myNewOder.Amount = 300;
    myNewOrder.Discount = myType.Discount;
    myNewOder.Total = nyNewOrder.Total - myNewOder.Amount * myNewOder.Discount;

    myDAL.AddOrder(nyNewOrder);

但是在这种情况下我的并发性有问题,因为我想确保我使用正确的折扣,所以我想避免一个类型的客户端的折扣在过程中被另一个用户更改添加新订单。

如果我使用乐观并发,我的 ClientTypes 表中必须有一个时间戳列,但这并不能解决我的问题,因为在我的 DAL 层的 addOrder 方法中,我只将新订单作为参数传递,所以该方法没有时间戳值让业务层去检查客户端的类型是否发生了变化,以确保使用的折扣是正确的。

所以我正在考虑这个解决方案:

public async addOrder(Orders paramNewOrder)

    using(Entities myDbContext = new Entities())
    
        string sql = "select ct.* from ClientTypes as ct, CLients as c"
            + " where ct.IdType = c.IdType and c.IdType = " + paramNewOrder.IdCLient;

        ClientTypes myClientType = await myDbContext.CLientTypeSqlQuery<CLientTypes>(sql).SingleOrDefaultAsync();

        if(paramNewOrder.Discount != myCLientType)
        
            throw new Exception("Discount incorrect.");
        

        paramNewOrder.Total = paramNewOrder.Amount - paramNewOrder.Amount * myClientType.Discount;

        myDbContext.Orders.Local.Add(paramNewOrder);
        await myDbContext.SaveChangesAsync();
    

这是我的业务层,但是使用EF来获取数据,所以我认为这个解决方案合并了DAL abd业务层。这是真的?如果这是真的,我想这不是一个好的解决方案。但是,我该如何控制并发呢?

谢谢。

【问题讨论】:

【参考方案1】:

是的,乐观并发控制并不能帮助您防止插入错误的新订单,因为您没有提交ClientType。如果折扣同时更改,则仅更新 ClientType 会引发异常。

但要仔细考虑要求。在修改后毫秒使用正确的折扣真的很重要吗?如果是这样,您必须寻找锁定机制。否则,只需在最后一刻获取当前折扣,进行计算并提交订单。

您可以在映射到Order 的插入操作的存储过程中实现锁定/计算/插入机制。 EF can map CUD actions to stored procedures..

【讨论】:

这真的是一个简单的例子,也许很简单。但是想象一下,一个用户正在将订单从开放更新为付费,我想避免第二个用户向订单中添加新商品。可能会有一个带有未支付价格的项目的已支付订单。我希望这个例子更清楚。 这不是更清楚(你已经很清楚了)但它更好:) 也许在这种情况下,如果你添加一个项目,你也应该总是更新订单,所以如果订单被修改。否则,同样在这里,您需要某种形式的锁定。 但这是订单中的“假”更新,因为不需要,所以它是一个选项,但我仍然想知道这个假更新或悲观并发更好。 我的意见?悲观并发更难实现(需要存储过程)并且更难扩展,因为锁定需要更长的时间,所以死锁总是迫在眉睫。使用 EF 时,乐观并发要容易得多。但是虚假更新不是不言自明的,因此应该在代码中详细记录。

以上是关于如何在具有并发控制的 .NET 和 EF 应用程序中设计持久层?的主要内容,如果未能解决你的问题,请参考以下文章

面向具有 EF6 和 Identity 的完整框架的 ASP.NET Core

具有身份和 EF 的 3 层应用程序

ASP.NET 5 和 EF - 用 LINQ 替换子列表

具有 ASP.NET Core 3.0 和 EF Core 的多租户应用程序

EF Core 并发控制

与 MVC 3、.NET 4.5 和 EF 6 并行获取数据