【问题标题】:Shared DbContext Transaction using ASP.NET Core 2.0 MVC/EF使用 ASP.NET Core 2.0 MVC/EF 的共享 DbContext 事务
【发布时间】:2018-01-15 23:38:06
【问题描述】:

如果有人能告诉我这一点,非常感谢!!!

抱歉,我看到了很多例子,但没有任何解决 MVC 模式的例子。

我的困境是 MVC 控制器 的实现,它可以处理多个请求和 Db 操作,但在操作员完成必要的流程之前永远不会提交任何内容。

例如,添加一条新的父记录并返回记录信息,并带有生成的 Id PrimaryKey。现在操作员可以通过传递必要的 ForiegnKey 来添加子记录。其中一些子记录可能有自己的子记录等。操作员必须在提交任何内容之前完成整个过程。

除非我完全关闭,否则我无法弄清楚这一点。我遇到了这些:

How to make a transaction in asp.net-core 2.0?

Alternative to TransactionScope of System.Transaction assembly in .net core framework

据我了解,System.Transactions 目前在 .Net Core 2.0.x 中可用。正如 3568202 中指出的那样:

https://docs.microsoft.com/en-us/dotnet/api/system.transactions.transactionscope?view=netcore-2.0

https://docs.microsoft.com/en-us/dotnet/framework/data/transactions/writing-a-transactional-application

https://docs.microsoft.com/en-us/dotnet/framework/data/transactions/managing-concurrency-with-dependenttransaction 其中有一个我玩过的 WorkerThread 示例。

但该示例并未准确显示 Transactions.Current 的来源。这是我在控制器中遇到障碍的地方。

我无法从 MyDbContext.Database.BeginTransaction() 派生的 IDbContextTransaction 中找出转换;和 System.Transactions 的东西。

因为使用 IDbContextTransaction 创建的事务没有 .DependentClone()

我错过了什么,一个接口?我不能把它放在一起......

然后我遇到了这个,看起来更简单一点:

https://docs.microsoft.com/en-us/ef/core/saving/transactions#cross-context-transaction-relational-databases-only

TransactionScope无关,第一行好像是救命:

“您还可以跨多个上下文实例共享事务。”

尝试在 Controller 中实现示例后,我不断感到恐惧:

“InvalidOperationException:指定的事务不是 与当前连接相关联。仅关联交易 可以使用当前连接。”

另外,最重要的是,在我所有的细读中,它似乎表明这并不多,如果使用注入的上下文可以实现这一点???!!!咦,什么,为什么?

如何使用正确的“使用”模式???

我正在使用 .Net Core 2.0.5 和 VS 2017 15.5.3

顺便说一句——我在这里没有使用存储库,但如果解决方案需要存储库,我会全力以赴。

这是我正在查看的基本设置

public class MyDbContext : DbContext
{
    public MyDbContext(DbContextOptions<MyDbContext> options) : base(options)
    { }

    public DbSet<Source> Sources { get; set; }
    public DbSet<Comment> Comments { get; set; }
    public DbSet<DwsFileInfo> DwsFileInfo { get; set; }
}

然后在Startup.ConfigureServices

services.AddDbContext<MyDbContext>(options =>
   options.UseSqlServer(Configuration.GetConnectionString("MyConnection")));

然后是控制器:

public class MyController : Controller
{
    private MyDbContext MyDbContext { get; set; }
    private static IDbContextTransaction RootTransaction { get; set; }
    //private DependentTransaction DependentTransaction { get; set; }
    //private TransactionScope ScopedTransaction { get; set; }
    public CommentsController(MyDbContext context)
    {
        MyDbContext = context;
        if (RootTransaction == null)
        {
            RootTransaction = MyDbContext.Database.BeginTransaction();
        }
        else
        {
            MyDbContext.Database.UseTransaction(RootTransaction.GetDbTransaction());
        }
    }

    /// <summary>
    /// Not sure I really need this?!?
    /// </summary>
    ~CommentsController()
    {
        if (RootTransaction != null && RootTransaction.GetDbTransaction() != null)
        {
            RootTransaction.Rollback();
        }
        RootTransaction.Dispose();
        MyDbContext.Dispose();
    }

    public IActionResult Add(object recordinfo)
    {
        // DB operations
    }

    public IActionResult Add2(object recordinfo)
    {
        // DB operations
    }

    public IActionResult Edit(object recordinfo)
    {
        // Db Operations
    }

    /// <summary>
    /// something here to manage, instead of in the constructor 
    /// or relying on the destructor??
    /// </summary>
    private void ManageTransaction()
    {
        //commit
        //rollback
        //dispose
    }
}

我已经使用以下方法将一些特定于应用程序的信息注入到上下文中:

app.Use(next => context =>
{
    string path = context.Request.Path;
    //modify context accordingly per path
    return next(context);
});

但无法准确定义“相应修改”的含义......现在我发现自己受 StackOverflow 的摆布...... 非常感谢大家的时间和考虑。非常感谢!!!

【问题讨论】:

  • 抱歉,没有时间阅读所有内容,但要预先提供一个:不要使用静态字段,它不是线程安全的,您可能会遇到异常或不受欢迎的行为当多个请求同时发生时。您应该使用依赖注入(作用域或瞬态)
  • Transaction 是一个在整个(dugh)事务期间应该是活动的对象。当您发送响应时,您的控制器可能会与您的对象一起被处置,因此一种选择是将它们存储在静态服务器端集合中。问题是您还需要保持与数据库的整个连接,因为事务已绑定到它(这就是您收到该错误的原因)。为什么不重构其背后的逻辑并一次发送所有数据,而不是打开一个事务并逐项进行呢?

标签: asp.net-core .net-core asp.net-core-mvc relational-database entity-framework-core


【解决方案1】:

TL;DR:saga 模式可帮助您在没有环境/数据库相关事务的情况下解决问题。 Sagas 也是交易,但以不同的方式,更像是一个状态机。

我的困境是 MVC 控制器 的实现,它可以处理多个请求和 Db 操作,但在操作员完成必要的流程之前永远不会提交任何内容。

这是你的第一个误解。这就是 Web 和 Internet 的工作方式。 Http 是无状态的。您不能使用 Http 存储状态,并且单个请求所需的所有数据都应与请求本身一起发送。

或者您更改设计以允许保留父记录,然后在后续请求中添加子对象。或者让您的客户一起发送它们:

Json 示例:

{
    // EF core assigns a new key when the key is null or has the default value
    parentId: 0,
    name: "Parent",
    children: [{
        name: "Child 1",
    },{
        name: "Child 2",
    }]
}

现在您在一个请求中拥有所有数据,并且可以在一个操作中处理它。在 jQuery、Angular 等时代,这不是问题。

其中一些子记录可能有自己的子记录等。操作员必须在提交任何内容之前完成整个过程。

您的想法就像开发桌面应用程序时一样。但是 Web 不是桌面应用程序,您不能通过 http 作为无状态协议来传输状态。

您必须调整您的设计并使用对网络更友好的流程。或者,如果这是绝对要求,只需创建一个桌面应用程序;)

尝试在 Controller 中实现示例后,我不断感到恐惧:

“InvalidOperationException:指定的事务不是 与当前连接相关联。仅关联交易 可以使用当前连接。”

另外,最重要的是,在我所有的细读中,它似乎表明这并不多,如果使用注入的上下文可以实现这一点???!!!咦,什么,为什么?

不是你想要的方式,不是事务范围。 TransactionScope 旨在执行多个数据库操作以保证一致性。这并不意味着数据将在任何可能的时间异步传入和延迟。

顺便说一句——我在这里没有使用存储库,但如果解决方案需要存储库,我会全力以赴。

您正在使用存储库。 EntityFramework 是工作单元和存储库模式的实现。 DbContext 是工作单元,而DbSet&lt;T&gt; 属性是存储库。

但人们通常倾向于将 EF Core 抽象到它自己的存储库后面。

你想要的方式行不通,因为 DbContext 必须是作用域的(或者你有内存泄漏的风险)。此外 DbContext 不是线程安全的,因此只能由单个线程安全使用。这就是为什么它默认为作用域,试图从另一个线程访问它只会抛出一个无效的操作异常。

一种解决方案:Saga / 流程管理器

最后但并非最不重要的一点是,如果您有长时间运行的事务,其中数据可以在任何给定顺序和任何给定时间出现,那么手头就有一个模式。

它被称为进程管理器/sagas。 saga 或进程管理器是一个长期运行的进程。例如,现在可以进行一项操作,2 分钟后进行下一项操作。第三个甚至可能需要一个小时。

saga 的行为类似于状态机。它将接收一条消息(命令)以执行某个操作,然后将其状态持久化(到数据库、内存、分布式缓存、会话,任何适合您的场景的东西)。

然后在某个未定义的时间之后,第二个命令来并对其执行一些操作。甚至后来还有第三条命令。只有当 saga 处于特定状态时,它才能作为一个整体提交。

想象一下,你想去度假。现在您要预订航班和酒店。

  • 第一个操作可能是选择目的地酒店。
  • 第二个操作是订机票。
  • 现在您必须等到您的预订得到确认。航空公司需要获取您的付款数据,询问付款公司并等待资金交易得到确认
  • 确认航班后,您想预订酒店房间。
  • 酒店需要等到付款提供商确认付款。一旦确认,即确认您的酒店房间预订。

只有在您的酒店和航班都得到确认后,您的假期才能“承诺”。例如,如果酒店公司拒绝了您的请求,您就不能去度假。然后您可以发出另一个命令(寻找另一家酒店)或取消您的假期。当您取消假期时,您还需要取消您的航班。

这等于事务回滚。

在传奇中,您只需删除已注册的记录或将“假期传奇”置于“已取消”状态。

【讨论】:

  • 感谢您的详细解释和示例。注意:我不打算使用静态,只是在我知道我是唯一的用户时尝试查看不同的对象状态。是的,我使用 Json/Ajax 进行几乎所有的客户端交互,但我陷入了这样做的可能性。 docs.microsoft.com/en-us/ef/core/saving/… 呢?它声明“您还可以跨多个上下文实例共享事务”。 ??虽然例子不是MVC?
  • 另外,这到底是怎么回事:docs.microsoft.com/en-us/dotnet/api/… “对于需要跨多个函数调用或多个线程调用使用相同事务的应用程序,您还应该使用 TransactionScope 和 DependentTransaction 类。对于有关编写事务应用程序的更多信息,请参阅编写事务应用程序。”还是我们在这里谈论更多的客户端/服务器桌面?那为什么在 .NET Core 中呢?
  • .NET Core 也可用于编写不能通过 http 运行的控制台/服务器应用程序。即创建处理消息的后台工作人员。 ASP.NET 核心!= .NET 核心
  • 我在控制台应用程序和 .Net Core 上玩了一些,应该在那里说 ASP.NET Core,道歉......在掉进这个啮齿动物的洞之后,我进行了更深的潜水只是探索运行时对象,然后向下钻取以查看实际存在的内容。就像底层的 ADO.NET 东西和 TransactionManager。在这样做时,我偶然发现了这个:docs.microsoft.com/en-us/ef/core/api/… 和 GetDbTransaction().Connection。尤其是 EnlistTransaction(System.Transactions.Transaction) !?
  • 我想问题是在使用 EF 时不要搞砸那些东西,或者如果你想要那种级别的控制,就直接使用 System.Data?虽然 http 上下文是无状态的,但我个人不认为客户端/数据库交互应该是,但这是另一个讨论......另一个问题是 ADO.NET 如何利用 System.Transactions?还是高于我的技能水平;-)
猜你喜欢
  • 2019-08-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-03-22
  • 2020-05-02
  • 1970-01-01
  • 1970-01-01
  • 2011-08-27
相关资源
最近更新 更多