Skip to content

ORM扩展

ORM扩展是对数据库操作的扩展,使不同的类和方法可以使用统一的事务。实现了服务间的低耦合、高内聚。ORM扩展默认使用ado.net实现,也可以使用特定的ORM框架。目前实现的ORM扩展有Entity Framework和Dapper,也可以自行扩展自己的喜欢的ORM框架。

使用ORM扩展

首先引入组件,通过nuget引用:

sh
dotnet add package AsDI.ORM.Extend --version 2.0.0

使用ORM扩展执行简单的SQL

对于简单的、仅需要执行SQL的情况,当使用ORM扩展时,仅需要使用接口,并在接口上添加标识 [Repository] ,例如:

C#
 [Repository]
 public interface IUserRepository
 {
 }

当需要执行某一个SQL时,只需要增加一个方法,并传入参数即可,例如:

C#
[Repository]
public interface IUserRepository
{
    [NativeExecute("update sys_user set DeptName=@deptName where DepartmentId=@id", SqlType=SqlType.DML)]
    void UpdateDeptName(string id, string deptName);
}

//在Service中,注入并调用此方法
[Service]
public class UserService: IUserService
{
    private readonly IUserRepository repository;
    public UserService(IUserRepository repository)
    {
        //注入Repository
        this.repository = repository;
    }

    public void OnDepartmentUpdate([ParameterOf(0)] DepartmentDTO departmentDTO)
    {
        //调用方法
        repository.UpdateDeptName(departmentDTO.Id, departmentDTO.DeptName);
    }
}

接口的实现

有时候,执行一个SQL可能无法满足需要,可能有比较复杂的逻辑,需要自定义实现。如上例,可以直接继承 IUserRepository 接口。但因为继承了接口,接口里的所有方法都要实现,如果不想实现,可以直接抛出 NotImplementedException,如下:

C#
[Repository]
public interface IUserRepository
{
    [NativeExecute("update sys_user set DeptName=@deptName where DepartmentId=@id", SqlType = SqlType.DML)]
    void UpdateDeptName(string id, string deptName);

    List<UserEntity> GetList(string deptUser);

}

[Service]
public class UserRepository : IUserRepository
{
    public void UpdateDeptName(string id, string deptName)
    {
        //直接使用NativeExecute的SQL,可以不用实现
        throw new NotImplementedException();
    }

    public List<UserEntity> GetList(string deptUser)
    {
        //实现自己特有的逻辑
        return Execute("select * from sys_user where DepartmentId=@deptUser", new { deptUser });
    }

}

但更推荐的方式是将需要实现的方法和不需要实现的方法分离,如扩展 IUserRepository,,如下:

C#
[Repository]
public interface IUserRepository : IUserRepositoryImpl
{
    // 这里写一些不需要实现的接口方法


    [NativeExecute("update sys_user set DeptName=@deptName where DepartmentId=@id", SqlType = SqlType.DML)]
    void UpdateDeptName(string id, string deptName);
}

public interface IUserRepositoryImpl
{
    // 这里写一些需要实现的接口方法

    public List<UserEntity> GetList(string deptUser);
}

[Service]
public class UserRepositoryImpl : IUserRepositoryImpl
{
    public List<UserEntity> GetList(string deptUser)
    {
        return Execute("select * from sys_user where DepartmentId=@deptUser", new { deptUser });
    }
}

在net8.0以上版本,接口可以直接实现,更推荐的是直接实现,而不需要扩展,例如:

C#
[Repository]
public interface IUserRepository
{
    [NativeExecute("update sys_user set DeptName=@deptName where DepartmentId=@id", SqlType = SqlType.DML)]
    void UpdateDeptName(string id, string deptName);

    // 直接实现,不需要继承(仅net8.0及以上版本支持)
    List<UserEntity> GetList(string deptUser)
    {
        return Execute("select * from sys_user where DepartmentId=@deptUser", new { deptUser });
    }
}

使用全局事务

当一个服务需要调用多个Repository时,可以使用全局事务。全局事务使得在这个服务里调用的所有的Repository的操作,都会在一个事务内执行,而仅需要在方法上标识 [AutoTrans] 即可,这个标识可以写在实现的方法上,也可以写在接口的方法上,如下:

C#
[Service]
public class ApiInfoService : IApiInfoService
{
    private readonly IApiInfoRepository repository;
    public ApiInfoService(IApiInfoRepository repository)
    {
        this.repository = repository;
    }
    [AutoTrans]
    public override IEnumerable<ApiInfoDTO> Insert(IEnumerable<ApiInfoDTO> dtos)
    {
        repository.DeleteAll();
        return base.Insert(dtos);
    }
}

AutoTrans有两个属性 RollBackFor 和 ContinueFor ,当仅需要对特定的异常进行回滚时,设置 RollBackFor 即可, 如果当某个异常不回滚时,设置 ContinueFor 即可。

自定义ORM扩展

如果想使用原来的ORM,仅需要只实现 IDbExecutor , 比如使用Dapper,可以实现如下:

C#
[Service(1)]
public class DapperExecutor : IDbExecutor
{
    private static readonly AsyncLocal<IDbConnection> conn = new();
    private static readonly AsyncLocal<IDbTransaction> transaction = new();
    [Value("AsDI.Database.ConnectionString", Require = false)]
    private string _connectionString;
    [Value("AsDI.Database.Driver", Require = false)]
    private string _providerName;
    public IDbConnection Connection
    {
        get
        {
            if (conn.Value != null)
            {
                return conn.Value;
            }

            if (!string.IsNullOrEmpty(_providerName) && !string.IsNullOrEmpty(_connectionString))
            {
                var type = Type.GetType(_providerName);
                if (type != null)
                {
                    conn.Value = (IDbConnection)Activator.CreateInstance(type, _connectionString);
                }
            }

            if (conn.Value == null)
            {
                conn.Value = AsDIContext.New<IDbConnection>();
            }

            if (conn.Value == null || TargetAnalyzer.FinalTarget(conn.Value) == null)
            {
                throw new ArgumentNullException("Connection can't intial , please check config");
            }
            return conn.Value;
        }
    }

    public bool BeginTrans()
    {
        if (transaction.Value != null)
        {
            return false;
        }
        else
        {
            Connection.Open();
            transaction.Value = Connection.BeginTransaction();
            return true;
        }
    }

    public void Commit()
    {
        try
        {
            transaction.Value?.Commit();
        }
        finally
        {
            if (conn.Value?.State != ConnectionState.Closed)
            {
                conn.Value?.Close();
            }
            conn.Value.Dispose();
            conn.Value = null;
            transaction.Value = null;
        }
    }

    public void RollBack()
    {
        try
        {
            transaction.Value?.Rollback();
        }
        finally
        {
            if (conn.Value?.State != ConnectionState.Closed)
            {
                conn.Value?.Close();
            }
            conn.Value.Dispose();
            conn.Value = null;
            transaction.Value = null;
        }
    }

    public void Dispose()
    {
        if (transaction.Value == null && conn.Value != null)
        {
            if (conn.Value?.State != ConnectionState.Closed)
            {
                conn.Value?.Close();
            }
            conn.Value.Dispose();
            conn.Value = null;
            transaction.Value = null;
        }
    }

    public int Modify(string sql, IDictionary<string, object> ps)
    {
        return Connection.Execute(sql, ps, transaction.Value);
    }

    public object Query(string sql, IDictionary<string, object> parameters, Type returnType)
    {
        RawType rawType = RawType.From(returnType);
        if (rawType.IsList)
        {
            var rtn = Connection.Query(rawType.ElementType, sql, parameters, transaction.Value);
            var real = Activator.CreateInstance(returnType);
            var addMethod = returnType.GetMethod("Add");
            foreach (var v in rtn)
            {
                addMethod.Invoke(real, new object[] { v });
            }
            return real;
        }
        else if (rawType.IsArray)
        {
            var rtn = Connection.Query(rawType.ElementType, sql, parameters, transaction.Value);
            var real = Array.CreateInstance(rawType.ElementType ?? typeof(object), rtn.Count());
            int i = 0;
            foreach (var v in rtn)
            {
                real.SetValue(v, i);
                i++;
            }
            return real;
        }
        else
        {
            var value = Connection.QueryFirstOrDefault(returnType, sql, parameters, transaction.Value);
            return value;
        }
    }

    public T Query<T>(string sql, IDictionary<string, object> parameters)
    {
        return (T)Query(sql, parameters, typeof(T));
    }

}

当然Dapper已经上线,可以直接使用nuget引入:

sh
dotnet add package AsDI.ORM.Extend.Dapper --version 2.0.0

沪ICP备2025119739号