Nhibernate:谁负责非 Web 应用程序中的事务管理

发布于 2024-11-26 14:42:03 字数 256 浏览 2 评论 0原文

在 Web 应用程序中,工作单元负责事务管理。

但是 Windows 应用程序呢?

据我所知,存储库是我的数据访问层和业务层之间的连接器。 它隐藏了我的业务层的所有数据访问内容。

利用这个事实让我想到将所有交易内容放入存储库中。

但我读到,在存储库上使用 Commit/RollBack 方法违反了存储库的意图。

我问自己,谁负责非 Web 应用程序中的事务管理,以及如何从业务层隐藏事务/Nhibernate 内容?

Well in a web application a unit of work is responsible for the transaction management.

But what about a windows application?

As far as I know the repository is the connector between my data access layer and my business layer.
It hides all the data access stuff from my business layer.

Using this fact let me think of taking all the transaction stuff into the repository.

But I read that having Commit/RollBack methods on the repository is violating the repository's intent.

I ask myself who is responsible for transaction management in a non web application and how do I hide the transaction/Nhibernate stuff from the business layer?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

画中仙 2024-12-03 14:42:03

一般的答案是“无论谁实例化 ISession 都应该处置它。如果事务尚未提交,则这实际上是回滚。”

我已经成功地使用命令模式来定义我想要对工作单元执行的操作。假设我们有一个 Person 实体,我们可以做的事情之一就是更改一个人的名字。让我们从实体开始:

public class Person
{
    public virtual int Id { get; private set; }
    public virtual string Name { get; private set; }

    public virtual void ChangeName(string newName)
    {
        if (string.IsNullOrWhiteSpace(newName))
        {
            throw new DomainException("Name cannot be empty");
        }

        if (newName.Length > 20)
        {
            throw new DomainException("Name cannot exceed 20 characters");
        }

        this.Name = newName;
    }
}

定义一个简单的 POCO 命令,如下所示:

public class ChangeNameCommand : IDomainCommand
{
    public ChangeNameCommand(int personId, string newName)
    {
        this.PersonId = personId;
        this.NewName = newName;
    }

    public int PersonId { get; set; }
    public string NewName { get; set; }
}

...以及该命令的处理程序:

public class ChangeNameCommandHandler : IHandle<ChangeNameCommand>
{
    ISession session;

    public ChangeNameCommandHandler(ISession session)
    {
        // You could demand an IPersonRepository instead of using the session directly.
        this.session = session;
    }

    public void Handle(ChangeNameCommand command)
    {
        var person = session.Load<Person>(command.PersonId);
        person.ChangeName(command.NewName);
    }
}

目标是存在于会话/工作范围之外的代码可以执行如下操作:

public class SomeClass
{
    ICommandInvoker invoker;

    public SomeClass(ICommandInvoker invoker)
    {
        this.invoker = invoker;
    }

    public void DoSomething()
    {
        var command = new ChangeNameCommand(1, "asdf");
        invoker.Invoke(command);
    }
}

命令的调用意味着“在工作单元上执行此命令。”这就是我们在调用命令时希望发生的情况:

  1. 开始 IoC 嵌套作用域(“工作单元”作用域)
  2. 启动 ISession 和事务(这可能是第 3 步的一部分)
  3. 解析 IHandleIHandle<来自 IoC 范围的 ChangeNameCommand>
  4. 将命令传递给处理程序(域执行其工作)
  5. 提交事务
  6. 结束 IoC 范围(工作单元)

下面是一个使用 Autofac 作为 IoC 容器:

public class UnitOfWorkInvoker : ICommandInvoker
{
    Autofac.ILifetimeScope scope;

    public UnitOfWorkInvoker(Autofac.ILifetimeScope scope)
    {
        this.scope = scope;
    }

    public void Invoke<TCommand>(TCommand command) where TCommand : IDomainCommand
    {
        using (var workScope = scope.BeginLifetimeScope("UnitOfWork")) // step 1
        {
            var handler = workScope.Resolve<IHandle<TCommand>>(); // step 3 (implies step 2)
            handler.Handle(command); // step 4

            var session = workScope.Resolve<NHibernate.ISession>();
            session.Transaction.Commit(); // step 5

        } // step 6 - When the "workScope" is disposed, Autofac will dispose the ISession.
          // If an exception was thrown before the commit, the transaction is rolled back.
    }
}

注意:我在此处展示的 UnitOfWorkInvoker违反了 SRP - 这是UnitOfWorkFactoryUnitOfWorkInvoker 合而为一。在我的实际实现中,我把它们分解了。

The general answer is "Whoever instantiates the ISession should dispose of it. If the transaction has not been committed, this is effectively a rollback."

I've had success by using the command pattern to define an operation that I want to perform on a unit of work. Say we have a Person entity and one of the things we can do is change a person's name. Let's start with the entity:

public class Person
{
    public virtual int Id { get; private set; }
    public virtual string Name { get; private set; }

    public virtual void ChangeName(string newName)
    {
        if (string.IsNullOrWhiteSpace(newName))
        {
            throw new DomainException("Name cannot be empty");
        }

        if (newName.Length > 20)
        {
            throw new DomainException("Name cannot exceed 20 characters");
        }

        this.Name = newName;
    }
}

Define a simple POCO Command like this:

public class ChangeNameCommand : IDomainCommand
{
    public ChangeNameCommand(int personId, string newName)
    {
        this.PersonId = personId;
        this.NewName = newName;
    }

    public int PersonId { get; set; }
    public string NewName { get; set; }
}

...and a Handler for the command:

public class ChangeNameCommandHandler : IHandle<ChangeNameCommand>
{
    ISession session;

    public ChangeNameCommandHandler(ISession session)
    {
        // You could demand an IPersonRepository instead of using the session directly.
        this.session = session;
    }

    public void Handle(ChangeNameCommand command)
    {
        var person = session.Load<Person>(command.PersonId);
        person.ChangeName(command.NewName);
    }
}

The goal is that code that exists outside of a Session/Work scope can do something like this:

public class SomeClass
{
    ICommandInvoker invoker;

    public SomeClass(ICommandInvoker invoker)
    {
        this.invoker = invoker;
    }

    public void DoSomething()
    {
        var command = new ChangeNameCommand(1, "asdf");
        invoker.Invoke(command);
    }
}

The invocation of the command implies "do this command on a unit of work." This is what we want to happen when we invoke the command:

  1. Begin an IoC nested scope (the "Unit of Work" scope)
  2. Start an ISession and Transaction (this is probably implied as part of step 3)
  3. Resolve an IHandle<ChangeNameCommand> from the IoC scope
  4. Pass the command to the handler (the domain does its work)
  5. Commit the transaction
  6. End the IoC scope (the Unit of Work)

So here's an example using Autofac as the IoC container:

public class UnitOfWorkInvoker : ICommandInvoker
{
    Autofac.ILifetimeScope scope;

    public UnitOfWorkInvoker(Autofac.ILifetimeScope scope)
    {
        this.scope = scope;
    }

    public void Invoke<TCommand>(TCommand command) where TCommand : IDomainCommand
    {
        using (var workScope = scope.BeginLifetimeScope("UnitOfWork")) // step 1
        {
            var handler = workScope.Resolve<IHandle<TCommand>>(); // step 3 (implies step 2)
            handler.Handle(command); // step 4

            var session = workScope.Resolve<NHibernate.ISession>();
            session.Transaction.Commit(); // step 5

        } // step 6 - When the "workScope" is disposed, Autofac will dispose the ISession.
          // If an exception was thrown before the commit, the transaction is rolled back.
    }
}

Note: The UnitOfWorkInvoker I've shown here is violating SRP - it is a UnitOfWorkFactory, a UnitOfWork, and an Invoker all in one. In my actual implementation, I broke them out.

骄傲 2024-12-03 14:42:03

当我使用存储库时,它们包含在一个工作单元中。工作单元跟踪存储库的更改并处理事务管理。

为什么在 Web 应用程序中使用工作单元来处理事务管理是有效的,而不是在 Windows 应用程序中?如果它是一个 N 层应用程序,您的业务层实际上将在两者之间共享。

When I use repositories, they are contained within a unit of work. The unit of work tracks changes to the repositories and handles transaction management.

Why would it be valid to use a unit of work to handle transaction management in a web application and not in a windows application? If it's an N-Tier application, your business layer would actually be shared between both.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文