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

时间:2018-01-15 23:38:07

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

如果有人能指引我直截了当,非常感谢!!!

对任何重新开始道歉,我看过很多例子,但没有任何解决MVC模式的例子。

我的困境是 MVC Controller 的实现,它可以处理多个请求和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);
});

但无法完全确定&#34;相应地修改&#34;意思是......现在我发现自己受到StackOverflow的支配...... 非常感谢每个人的时间和考虑。非常感谢!!!

1 个答案:

答案 0 :(得分:2)

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

  

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

这是你的第一次误解。这就是网络和互联网的运作方式。 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等年代,它不是问题。

  

其中一些儿童记录可能有自己的子女等。在任何事情发生之前,经营者必须完成整个过程。

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

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

  

尝试在Controller中实现示例后,我不断得到可怕的:

     
    

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

  
     

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

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

  

顺便说一句 - 我这里没有使用存储库,但如果解决方案需要存储库,那么我就是这个想法。

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

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

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

一个解决方案:Saga /流程经理

最后但并非最不重要的一点是,如果您有长时间运行的交易,其中数据可以按任何给定的顺序和任何给定时间进行,那么手头有一种模式。

它被称为流程经理/传奇。传奇或流程管理器是一个长期运行的过程。例如,现在可以发生一个操作,下一个操作可以在2分钟后发生。第三个甚至可能需要一个小时。

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

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

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

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

现在,只有在您的酒店和航班确认后,您的假期才会被提交&#34;承诺&#34;。例如,如果酒店公司拒绝您的请求,您就无法去度假。然后,您可以发出另一个命令(寻找另一家酒店)或取消您的假期。取消假期后,您还需要取消航班。

这相当于交易中的回滚。

在一个传奇故事中,您只需删除已注册的记录或将&#34;度假故事&#34;在&#34;取消&#34;状态。