inversionhourglass / Rougamo

Compile-time AOP component. Works with any method, whether it is async or sync, instance or static. Uses an aspectj-like pattern to match methods.
MIT License
393 stars 47 forks source link

对 Task 非 async 方法的拦截问题 #14

Closed 2881099 closed 2 years ago

2881099 commented 2 years ago
[Transactional]
public Task InsertTest()
{
    return Task.Delay(TimeSpan.FromSeconds(10)).ContinueWith(t => 
        _repoSong.InsertAsync(new Song()));
}

public class TransactionalAttribute : Rougamo.MoAttribute
{
    //...
}

感觉作者提供这么方便的组件。

上面 InsertTest 方法没有标记 async,当进入 OnExit 的时候会清除 _repo.UnitOfWork 状态,但是 _repo.InsertAsync 插入并未完成。不知道是否存在这种执行顺序问题?

2881099 commented 2 years ago

目的只发现这一个容易掉坑的问题,如果能解决就完美了。

示例已收录进文档:https://freesql.net/guide/unitofwork-manager.html

inversionhourglass commented 2 years ago

MoAttribute的生命周期对应被织入方法的生命周期,对于你描述的这种情况,是符合预期设定的。当然你遇到的这种场景,可以通过MethodContext.RealReturnType判断返回值是否为Task,然后通过ContinueWith的方式在Task结束时执行OnExit逻辑。

以你文档中的TransactionalAttribute为例:

[AttributeUsage(AttributeTargets.Method)]
public class TransactionalAttribute : Rougamo.MoAttribute
{
    // ...

    public override void OnExit(MethodContext context)
    {
        if (typeof(Task).IsAssignableFrom(context.RealReturnType))
        {
            ((Task)context.ReturnValue).ContinueWith(t => _OnExit());
        }
        else
        {
            _OnExit();
        }

        void _OnExit()
        {
            try
            {
                if (context.Exception == null) _uow.Commit();
                else _uow.Rollback();
            }
            finally
            {
                _uow.Dispose();
            }
        }
    }
}

补充说明一下,MethodContext.RealReturnType对于Task/Task<T>返回值但没有async语法的,其值就是Task/Task<T>本身,而如果使用了async语法,那么Task将对应typeof(void)Task<T>将对应typeof(T)

2881099 commented 2 years ago

感谢,明白了。建议在首页特别说明一下

代码应该是 context.ReturnValue = ...

inversionhourglass commented 2 years ago

好的,我后续将这种情况记录到REAMDE中。

如果你希望替换返回值,请使用context.ReplaceReturnValue(this, returnValue),直接通过context.ReturnValue设置返回值会跳过一些逻辑,这些逻辑在不同版本可能还会不一样。

inversionhourglass commented 2 years ago

对了,还有一点,context.ReplaceReturnValue(this, returnValue)只能在OnEntryOnSuccess中使用,OnExit中无法修改返回值

2881099 commented 2 years ago

这就矛盾了啊,因为上面你给的代码需要获取 context.ReturnValue,这个在 OnEntry 获取不到吧。

inversionhourglass commented 2 years ago

OnExit中可以获取返回值,但是不能修改。

OnEntry中应该叫设置返回值,应用场景比如异常参数检查返回固定返回值和缓存直接返回。

2881099 commented 2 years ago

好的,先解决 Task async,谢谢

2881099 commented 2 years ago

https://github.com/dotnetcore/FreeSql/wiki/DI-UnitOfWorkManager 谢谢,文档已更新,全面替换 castle.core

inversionhourglass commented 2 years ago

不客气,你们知名的开源项目愿意使用这样一个起步不久的项目,是我的荣幸,感谢使用

2881099 commented 2 years ago

本来只提供 UnitOfWorkManager 功能,就缺个动态代理。

之前一直没有推荐动态代理类库,他们比较喜欢 castle.core,整理出来的文档看起来太复杂 https://freesql.net/extra/aop-freesql-autofac.html

肉夹馍关注有一段时间了,今天试了一下太好用了,没有理由不推荐给大家。

inversionhourglass commented 2 years ago

刚发布了1.2.0版本,新增了ExMoAttribute,这个Attribute对有async语法和没有async语法的方法采用一致的生命周期,这个可能对你有用,TransactionalAttribute可以改成下面这样:

[AttributeUsage(AttributeTargets.Method)]
public class TransactionalAttribute : Rougamo.ExMoAttribute
{
    // ...

    public override void ExOnExit(MethodContext context)
    {
        try
        {
            if (context.Exception == null) _uow.Commit();
            else _uow.Rollback();
        }
        finally
        {
            _uow.Dispose();
        }
    }
}

还有一点提醒一下,OnExit/ExOnExit中通过MethodContext.Exception == null来判断方法是否抛出异常并不完全准确,如果在OnException/ExOnException中通过MethodContext.HandledException方法处理掉异常,那么OnExit/ExOnExitMethodContext.Exception就会返回null,如果希望在异常被处理的情况下依旧Rollback,可以将判断条件改为if (context.Exception == null) && !context.ExceptionHandled)

2881099 commented 2 years ago

谢谢提醒,上面够用了,非常好用