Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UnitOfWork能不支持事务嵌套 #262

Closed
yus1977 opened this issue Apr 1, 2020 · 20 comments
Closed

UnitOfWork能不支持事务嵌套 #262

yus1977 opened this issue Apr 1, 2020 · 20 comments

Comments

@yus1977
Copy link

yus1977 commented Apr 1, 2020

UnitOfWork能不能支持能事务嵌套,或者类似Spring中事务Support模式一样。多个方法都使用同一个UnitOfWork,并且都Commit,但只有最后一次是真正的提交到数据库。

@2881099
Copy link
Collaborator

2881099 commented Apr 1, 2020

不知道你的项目类型,现在是如何传播事务对象的?

如果使用相同的事务,并且在使用 asp.net core,用 AddScoped 注入一下能解决吗?

@2881099
Copy link
Collaborator

2881099 commented Apr 1, 2020

```csharp
//第一步:
public class UnitOfWorkRepository<TEntity, TKey> : BaseRepository<TEntity, TKey>
{
  public UnitOfWorkRepository(IFreeSql fsql, IUnitOfWork uow) : base(fsql, null, null) 
  {
    this.UnitOfWork = uow;
  }
}
public class UnitOfWorkRepository<TEntity> : BaseRepository<TEntity, int>
{
  public UnitOfWorkRepository(IFreeSql fsql, IUnitOfWork uow) : base(fsql, null, null)
  {
    this.UnitOfWork = uow;
  }
}

//第二步:
public void ConfigureServices(IServiceCollection services)
{
  services.AddSingleton<IFreeSql>(fsql);
  services.AddScoped<FreeSql.IUnitOfWork>(sp => fsql.CreateUnitOfWork());

  services.AddScoped(typeof(IBaseRepository<>), typeof(UnitOfWorkRepository<>));
  services.AddScoped(typeof(BaseRepository<>), typeof(UnitOfWorkRepository<>));

  //批量注入程序集内的所有自建仓储类,可以根据自己需要来修改
  Assembly[] assemblies = new [] { typeof(XxxRepository).Assembly };
  if (assemblies?.Any() == true)
      foreach (var asse in assemblies)
        foreach (var repo in asse.GetTypes().Where(a => a.IsAbstract == false && typeof(UnitOfWorkRepository).IsAssignableFrom(a)))
          services.AddScoped(repo);
}

@2881099
Copy link
Collaborator

2881099 commented Apr 1, 2020

并且都Commit,但只有最后一次是真正的提交到数据库。

包装一个 UnitOfWorkManager 类

class UnitOfWorkManager
{

     int _counter;
     IFreeSql _fsql;

     public UnitOfWorkManager(IFreeSql fsql)
     {
          _counter = 0;
          _fsql = fsql;
     }

     public IUnitOfWork UnitOfWork { get; private set; }
     public void Begin()
     {
          if (_uow = null) 
               UnitOfWork = _fsql.CreateUnitOfWork();
          _counter ++;
     }
      public void Rollback()
      {
           UnitOfWork.Rollback();
      }
      public void Commit()
      {
            if (--counter == 0)
                 UnitOfWork.Commit();
      }
}

@2881099
Copy link
Collaborator

2881099 commented Apr 1, 2020

主要看解决什么问题,上面两个方向即兴想的。UnitOfWorkManager可能比较扯。

还可以看看 Aop 能不能解决一些问题。

@yus1977
Copy link
Author

yus1977 commented Apr 2, 2020

我自己已包装了一个,我想能不能框架就提供,就不用自己包装了^^

@yus1977
Copy link
Author

yus1977 commented Apr 2, 2020

` public static class MyFreeSqlExtensions
{
///


/// 创建基于仓储功能的工作单元,务必使用 using 包含使用
///

///
///
public static IMyRepositoryUnitOfWork CreateMyUnitOfWork(this IFreeSql that)
{
return new MyRepositoryUnitOfWork(that);
}
}

/// <summary>
/// 自定义的仓储工作单元,通过事务计数器,可以实现多次提交只有最后一次有效
/// </summary>
public interface IMyRepositoryUnitOfWork : IRepositoryUnitOfWork
{
}

/// <summary>
/// 自定义的仓储工作单元,通过事务计数器,可以实现多次提交只有最后一次有效
/// </summary>
public class MyRepositoryUnitOfWork : UnitOfWork, IMyRepositoryUnitOfWork
{

    public MyRepositoryUnitOfWork(IFreeSql fsql) : base(fsql)
    {
    }

    public IBaseRepository<TEntity, Guid> GetGuidRepository<TEntity>(Expression<Func<TEntity, bool>> filter = null, Func<string, string> asTable = null) where TEntity : class
    {
        var repo = new GuidRepository<TEntity>(_fsql, filter, asTable);
        repo.UnitOfWork = this;
        return repo;
    }

    public IBaseRepository<TEntity, TKey> GetRepository<TEntity, TKey>(Expression<Func<TEntity, bool>> filter = null) where TEntity : class
    {
        var repo = new DefaultRepository<TEntity, TKey>(_fsql, filter);
        repo.UnitOfWork = this;
        return repo;
    }

    public IBaseRepository<TEntity> GetRepository<TEntity>(Expression<Func<TEntity, bool>> filter = null) where TEntity : class
    {
        var repo = new DefaultRepository<TEntity, int>(_fsql, filter);
        repo.UnitOfWork = this;
        return repo;
    }

    private int tranCount = 0;
    /// <summary>
    /// 事务计数器
    /// </summary>
    public int TranCount
    {
        get => tranCount;
        set
        {
            tranCount = value;
            if (tranCount < 0) TranCount = 0;
        }
    }

    /// <summary>
    /// 禁用工作单元
    /// <exception cref="Exception"></exception>
    /// <para></para>
    /// 若已开启事务(已有Insert/Update/Delete操作),调用此方法将发生异常,建议在执行逻辑前调用
    /// </summary>
    public new void Close()
    {
        //事务计数器归位时,对可以关闭
        if(tranCount == 0)            
            base.Close();
    }

    /// <summary>
    /// 开启工作单元
    /// </summary>
    public new void Open()
    {
        base.Open();
        //事务计数器加1
        tranCount++;
    }

    /// <summary>
    /// 提交事务,通过事务计数器,可以实现多次提交只有最后一次有效
    /// </summary>
    public new void Commit()
    {
        //如果事务计数器为0则表示当前工作单元最后一个提交的
        if (Enable && tranCount <= 1)
        {
            base.Commit();
        }
        tranCount--;
    }

    /// <summary>
    /// 回滚事务
    /// </summary>
    public new void Rollback()
    {
        tranCount = 0;
        base.Rollback();
    }
}`

@yus1977
Copy link
Author

yus1977 commented Apr 2, 2020

然后注入
services.AddScoped(s => s.GetService().CreateMyUnitOfWork());

services.AddScoped(typeof(IUnitOfWork), typeof(MyRepositoryUnitOfWork));

@yus1977
Copy link
Author

yus1977 commented Apr 2, 2020

只是框架能提供更好些,上面的代码只能简单的实现类似Spring的事务注解Supoort功能

@2881099
Copy link
Collaborator

2881099 commented Apr 2, 2020

new 重写的方法,不能再使用 BaseType 注入,不然没有效果

@yus1977
Copy link
Author

yus1977 commented Apr 2, 2020

哦, 没有注意到!

@yus1977
Copy link
Author

yus1977 commented Apr 2, 2020

要这样弄主要有两个原因。
1、我团队的伙伴有的之前用的是JAVA Spring,会把一些Spring的习惯带过来。
2、有时为了方便,会存在一个Service直接启用另是个Service的情况,而这两个Service都会在自己的方法结束时Commit,这样如果不做处理,事务就会提前提交。
比如:ServiceA.doA 和 ServiceB.doB,原本他们是使用自己的UnitOfWork来管理事务。
如果这样调用:
public void doA(){
...写入数据库1
.......
ServiceB.doB();
.............
.......写入数据库2
}
即使使用同一个UnitOfWork,在ServiceB.doB()执行完成后,因为doB()里执行了Commit,所以doB()里的与操作和 doA()里“写入数据库1”这部分会被提前提交数据库。 即使“写入数据库2”进行了回滚,数据还是已写入到数据库中。
3、这种使用方法,在JAVA Spring里不会有问题,因为Spring的事务注解默认是Support模式,在整个Scoped中,无论提交多少次,只有最后一次才会真正写入数据。

@yus1977
Copy link
Author

yus1977 commented Apr 2, 2020

4、一些之前使用 JAVA Spring的成员经常这样用,结果当前是Bug一片片的。因此对要对UnitOfWork的事务提交模式进行一下修改。

@2881099
Copy link
Collaborator

2881099 commented Apr 2, 2020

了解,看是继承实现,或者用上面发的UnitOfWorkManager,都可以

@2881099
Copy link
Collaborator

2881099 commented Apr 2, 2020

FreeSql.UnitOfWork 默认要手工触发 Commit 提交事务。所以个人觉得用 UnitofworkManager 比较好控制一点,可以在他的 Disponse 内判断执行 Commit,防止 counter 计数器不准确时候的问题。

@yus1977
Copy link
Author

yus1977 commented Apr 2, 2020

OK,我再多测试一下

@2881099
Copy link
Collaborator

2881099 commented Apr 25, 2020

@yus1977
Copy link
Author

yus1977 commented Apr 26, 2020

好的,我试试看。
幸苦了。

@yus1977
Copy link
Author

yus1977 commented Apr 26, 2020

这个修改会在1.4版本发布吗?

@2881099
Copy link
Collaborator

2881099 commented Apr 26, 2020

1.4预览版最新的有了,可以提前体验咯

1.4正式版,在这个月底发布

@2881099
Copy link
Collaborator

2881099 commented May 1, 2020

版本已发布

@2881099 2881099 closed this as completed May 1, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants