使用 ASP.NET Core 2.0 MVC/EF 的共享 DbContext 事务

Shared DbContext Transaction using ASP.NET Core 2.0 MVC/EF

如果有人可以让我明白这一点,非常感谢!!!

对于任何重述,我深表歉意,我看过很多示例,但没有任何解决 MVC 模式的示例。

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

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

除非我完全离开,否则我想不通。我 运行 进入这些:

据我了解,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的来源。这是我在控制器中遇到障碍的地方。

我无法从 IDbContextT运行saction 派生的 MyDbContext.Database.BeginTransaction( );和 System.Transactions 东西。

因为没有 .DependentClone() 用于 运行 使用 IDbContextT运行saction 创建的动作.

我错过了什么,一个界面?我无法将它们放在一起......

然后我运行进入这个,看起来更直接一点:

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

T运行sactionScope无关,第一行好像是拯救:

“您还可以跨多个上下文实例共享一个 t运行saction。”

尝试在 Controller 中实现该示例后,我不断遇到可怕的事情:

“InvalidOperationException: The specified transaction is not associated with the current connection. Only transactions associated with the current connection may be used.”

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

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

我正在使用 .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);
});

但无法准确理解 "modify accordingly" 的意思...现在我发现自己任由 Whosebug 摆布... 非常感谢大家的时间和考虑。非常感谢!!!

TL;DR:saga 模式可以帮助您解决没有 ambient/database 相关事务的问题。 Sagas 也是交易,但以不同的方式,更像是一个状态机。

My dilemma is an implementation of a MVC Controller that can process multiple requests and Db operations, but never commit anything until the operator has finished the requisite processes.

这是你的第一个误会。这就是 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 等年龄段,这不是问题。

Some of these Child records may have children of their own, etc. The operator must get through the whole process before ANYTHING is committed.

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

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

After attempting to implement the example in a Controller, I continuously get the dreaded:

“InvalidOperationException: The specified transaction is not associated with the current connection. Only transactions associated with the current connection may be used.”

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

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

BTW – I am not using a Repository here, but if a solution requires a repository, I am all in on that idea.

好吧,您正在使用存储库。 EntityFramework 是工作单元和存储库模式的实现。 DbContext 是工作单元,而 DbSet<T> 属性是存储库。

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

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

一个解决方案:Saga / Process Managers

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

它叫做进程管理器/sagas。传奇或流程管理器是一个很长的 运行 流程。例如,现在可以进行一项操作,2 分钟后进行下一项操作。第三个甚至可能需要一个小时。

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

然后过了一段时间,第二个命令来了,对它执行了一些操作。甚至后来的第三个命令。只有当 saga 处于特定条件时,它才能作为一个整体提交。

想象一下,您想去度假。现在您要预订机票和酒店。

  • 第一个操作可能是选择目的地酒店。
  • 第二个操作是订机票。
  • 现在您必须等到您的预订被确认。航空公司需要获取你的支付数据,询问支付公司,等待确认货币交易
  • 当您的航班确定后,您想预订酒店房间。
  • 酒店需要等待支付提供商确认付款。一旦确认,它就会确认您的酒店房间预订。

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

这相当于事务回滚。

在 saga 中,您只需删除已注册的记录或将 "vacation saga" 置于 "canceled" 状态。