12

我使用了很多模型来连接数据库,在我使用 C# 和实体框架的上一个项目中,我为数据库连接创建了静态类,但是我在打开和关闭连接时遇到了问题,因为当超过 10-15 时出现错误请求聚集在一起,我通过更改连接到数据库的方法解决了这个问题,我现在根据请求连接并删除了所有静态方法和类。

现在我想知道,

建立连接的最佳模型是什么?

  1. 我应该在每次查询后关闭它并在使用之前打开它还是...?
  2. 静态类中的连接是很好的模型(我不需要每次都创建它)?
  3. 这个问题有好的设计模式吗?
  4. 所有这些都是针对同一个问题 建立数据库连接的最佳方法是什么(静态、抽象、每个请求……)?

例如,我在一个 sms sender web 面板上工作,我应该每秒发送 100K sms,这些 sms 与其他人一起收集并制作一个包,每个包有 1~20 sms 然后我需要每秒发送 5K~100K 个包,当我发送一个包裹,我应该执行以下步骤:

  1. 将单个短信更新为已送达或未送达
  2. 如果交付减少用户帐户表中的用户余额,则更新用户余额
  3. 更新用户表中的短信发送计数
  4. 更新手机号码表中的短信发送次数
  5. 更新发件人号码表中的短信发送次数
  6. 更新包表中已发送和失败短信的包
  7. 更新包以了解线程如何在包表中发送此包
  8. 更新线程表,了解该线程发送了多少条短信以及失败了多少条
  9. 在 AccountDocument 表中添加此交易的帐户凭证

所有步骤和许多其他事情,如日志、用户界面和监控小部件,都应该这样做,我需要数据库连接来完成每一个事务。

现在,连接数据库的最佳模型是什么?通过人工请求或线程请求或每个事务..

4

3 回答 3

8

回答您的问题:

  1. 关闭它。.NET 在后台为您提供连接池。

  2. 创造它。每次都使用 using (Connection conn = new ....) - 这样,您将充分利用 .NET 池机制。

  3. 您可以使用 .NET ThreadPool(或您自己的自定义线程池),将 ThreadPool 定义为仅并行使用 10 个线程,并将工作项一个接一个地入队。这样,同一时间不会使用超过 10 个连接 + 它可能会更快地工作。更多关于自定义线程池:自定义线程池实现

  4. 每个实例。


这是我对架构的建议:

  1. 为待发送的 SMS 创建一个数据库表(队列)。

  2. 每行将包含短信所需的所有信息+当前状态。

  3. 创建一个工作进程,也许是一个 Windows 服务,它将不断地对该表进行采样 - 比如说,每 5 秒。它将选择 TOP ~20 条状态为“待发送”的短信(应表示为 int)。并将状态更新为“正在发送”

  4. 每条短信都将使用 Windows 服务端的自定义线程池发送出去。

  5. 在该过程结束时,所有已处理的短信状态都将使用 CTE 更新为“完成”(通用表表达式 - 您可以发送一个 cte,其中包含刚刚处理的所有短信行 ID 以进行“批量更新”到“完成”状态)。

  6. 您可以使状态更新存储过程与“getpending”相同。这样,您可以选择更新而没有锁定并使数据库工作得更快。

  7. 这样,您可以运行不止一个处理器服务(但随后您将不得不松开 nolock)。

记住要避免尽可能多的锁定。

顺便说一句,这也很好,因为您只需在待处理的 SMS 表中添加一行,就可以从系统中的任何位置发送 SMS。

还有一件事,我不建议为此使用实体框架,因为它在幕后发生了太多事情。完成此类任务所需的只是调用 3-4 个存储过程,仅此而已。也许看看Dapper-dot-NET - 它是一个非常轻量级的 MicroDal 框架,在大多数情况下比 EF(实体框架)快 10 倍以上

于 2013-05-13T06:54:49.283 回答
7

1. Should i close it after every query?

.Net 会为你做这件事,所以让它处理它,这是一个垃圾收集器任务。所以不要费心手动处理你的对象,这是 Jon Skeet 的一个很好的答案:https ://stackoverflow.com/a/1998600/544283 。但是,您可以使用该using(IDisposable){ }语句来强制 GC 完成它的工作。这是一篇关于资源重新分配的好文章:http: //www.codeproject.com/Articles/29534/IDisposable-What-Your-Mother-Never-Told-You-About

2. A connection in static class is good?

永远不要使数据上下文静态!数据上下文不是线程安全的或并发安全的。

3. Is there a good design pattern for this problem?

正如 Belogix 提到的依赖注入和工作单元模式很棒,实际上实体框架本身就是一个工作单元。不过,DI 和 UoW 有点被高估了,如果这是您第一次处理 IoC 容器,那么实施起来并不容易,如果您要走这条路,我会推荐 Ninject。另一件事是,如果您不打算运行测试,您实际上并不需要 DI,这些模式的美妙之处在于解耦,因此您可以毫不费力地进行测试和模拟。

简而言之:如果您要针对您的代码运行测试,请选择这些模式。如果没有,我将为您提供一个示例,说明如何在您想要的服务之间共享您的数据上下文。这是你第四个问题的答案。

4. What is the best method for making database connection (static, per request)?

您的上下文服务:

public class FooContextService {
    private readonly FooContext _ctx;

    public FooContext Context { get { return _ctx; } }

    public FooContextService() {
        _ctx = new FooContext();
    }
}

其他服务:

public class UnicornService {
    private readonly FooContext _ctx;

    public UnicornService(FooContextService contextService) {
        if (contextService == null)
            throw new ArgumentNullException("contextService");

        _ctx = contextService.Context;
    }

    public ICollection<Unicorn> GetList() {
        return _ctx.Unicorns.ToList();
    }
}

public class DragonService {
    private readonly FooContext _ctx;

    public DragonService(FooContextService contextService) {
        if (contextService == null)
            throw new ArgumentNullException("contextService");

        _ctx = contextService.Context;
    }

    public ICollection<Dragon> GetList() {
        return _ctx.Dragons.ToList();
    }
}

控制器:

public class FantasyController : Controller {
    private readonly FooContextService _contextService = new FooContextService();

    private readonly UnicornService _unicornService;
    private readonly DragonService _dragonService;

    public FantasyController() {
        _unicornService = new UnicornService(_contextService);
        _dragonService = new DragonService(_contextService);
    }

    // Controller actions
}

第二个想法(几乎是一个编辑):如果您需要您的上下文不为您的实体创建代理,因此也没有延迟加载,您可以重载您的上下文服务,如下所示:

public class FooContextService {
    private readonly FooContext _ctx;

    public FooContext Context { get { return _ctx; } }

    public FooContextService() : this(true) { }

    public FooContextService(bool proxyCreationEnabled) {
        _ctx = new FooContext();
        _ctx.Configuration.ProxyCreationEnabled = proxyCreationEnabled;
    }
}

笔记:

  • 如果您将启用代理创建设置为 false,您将不会有开箱即用的延迟加载。
  • 如果你有 api 控制器,你不想处理任何完整的对象图。

编辑:

先读一些:

完成这个:

(_context as IObjectContextAdapter).ObjectContext.Connection.Open();

这是一篇关于管理连接和事务的好文章。

实体框架通过 Connection 属性公开 EntityConnection。读作:public sealed class EntityConnection : DbConnection

管理连接的注意事项:(取自上一个链接)

  • 如果在操作之前对象上下文尚未打开,则对象上下文将打开连接。如果对象上下文在操作期间打开了连接,那么它总是会在操作完成时关闭连接。
  • 如果手动打开连接,对象上下文不会关闭它。调用CloseDispose将关闭连接。
  • 如果对象上下文创建了连接,则在释放上下文时始终会释放连接。
  • 在长时间运行的对象上下文中,您必须确保在不再需要上下文时将其释放。

希望能帮助到你。

于 2013-05-15T02:55:37.847 回答
5

我认为每个请求的规模最好。使用线程安全的连接池,并使连接范围与工作单元一致。让负责事务行为和工作单元的服务检查连接,使用它,并在工作单元提交或回滚时将其返回到池中。

更新:

10-12 秒提交状态更新?你做错了什么。您所写的问题不足以提供合适的答案。

纳斯达克的每日交易量为 1.3B 笔交易,每天 8 小时的交易量约为每秒 45K 笔交易。您的交易量是纳斯达克交易量的 2 倍。如果你想用一台机器来做,我会说纳斯达克正在使用不止一台服务器。

我还想知道您是否可以不使用 ACID 更新该状态。毕竟,星巴克不使用两阶段提交。也许更好的解决方案是使用带有阻塞队列的生产者/消费者模式来在发送这些状态后更新这些状态。

于 2013-04-09T12:59:28.383 回答