首页 > 数据库 >CodeFirst同时映射多种数据库及分表优化设计

CodeFirst同时映射多种数据库及分表优化设计

时间:2023-02-23 17:13:39浏览次数:50  
标签:CodeFirst Task 映射 List whereExpression var 分表 Expression public

开发环境:Windows 10专业版、Visual Studio Code、.Net6、SqlSugar5.x、Navicat

根据需求,对数据处理时,部分用到Sqlite进行增删改查、部分用到MySql进行增删改查

问题1:对于程序集中的模型对象映射需要根据对应连接进行初始化处理

问题2:对于需要延迟进行模型初始化的特殊处理(根据字段自动分表数据模型)

1、通过ConfigId来区分数据库连接

扩展IoC容器,自定义SqlSugar的注入,一方面是代码复用,另外就是简化配置与代码可读性

builder.Services.SetupSqlSugar(options =>
{
    options.connectionConfigs = new List<ConnectionConfig>
    {
        new ConnectionConfig
        {
            ConnectionString = builder.Configuration.GetConnectionString("Sqlite"),
            DbType = DbType.Sqlite,
            ConfigId = "sqlite",
            InitKeyType = InitKeyType.Attribute,
            IsAutoCloseConnection = true
        },
        new ConnectionConfig
        {
            ConnectionString = builder.Configuration.GetConnectionString("MySql"),
            DbType = DbType.MySql,
            ConfigId = "mysql",
            ConfigureExternalServices = new ConfigureExternalServices()
            {
                SplitTableService = new DefaultSplitTableService()
            },
            InitKeyType = InitKeyType.Attribute,
            IsAutoCloseConnection = true
        }
    };
});

SetupSqlSugar函数原型:public static IServiceCollection SetupSqlSugar(this IServiceCollection services, Action<SqlSugarOptions> setup)

SqlSugarOptions是一个包装了ConnectionConfig的对象:

public class SqlSugarOptions
{
    public List<ConnectionConfig> connectionConfigs { get; set; }
}

对于IoC容器的扩展方法主要是使用asp.netcore中常用的配置模式,将配置对象填充需要的值后放置备用的思路,具体使用

services.AddOptions();
services.Configure(setup);

2、实现简单仓储模式

现实简单仓储模式的目的就是应对增删改查有足够灵活、全面的方法实现,这点上面SqlSugar简直是太贴心已经有比较好的实现(基本在实际应用中没有遇到短板)接口ISimpleClient与实现SimpleClient

namespace SqlSugar
{
    public interface ISimpleClient<T> where T : class, new()
    {
        SimpleClient<ChangeType> Change<ChangeType>() where ChangeType : class, new();

        RepositoryType ChangeRepository<RepositoryType>() where RepositoryType : ISugarRepository;

        IDeleteable<T> AsDeleteable();

        IInsertable<T> AsInsertable(List<T> insertObjs);

        IInsertable<T> AsInsertable(T insertObj);

        IInsertable<T> AsInsertable(T[] insertObjs);

        ISugarQueryable<T> AsQueryable();

        ISqlSugarClient AsSugarClient();

        ITenant AsTenant();

        IUpdateable<T> AsUpdateable(List<T> updateObjs);

        IUpdateable<T> AsUpdateable(T updateObj);

        IUpdateable<T> AsUpdateable();

        IUpdateable<T> AsUpdateable(T[] updateObjs);

        int Count(Expression<Func<T, bool>> whereExpression);

        bool Delete(Expression<Func<T, bool>> whereExpression);

        bool Delete(T deleteObj);

        bool Delete(List<T> deleteObjs);

        bool DeleteById(dynamic id);

        bool DeleteByIds(dynamic[] ids);

        T GetById(dynamic id);

        List<T> GetList();

        List<T> GetList(Expression<Func<T, bool>> whereExpression);

        List<T> GetPageList(Expression<Func<T, bool>> whereExpression, PageModel page);

        List<T> GetPageList(Expression<Func<T, bool>> whereExpression, PageModel page, Expression<Func<T, object>> orderByExpression = null, OrderByType orderByType = OrderByType.Asc);

        List<T> GetPageList(List<IConditionalModel> conditionalList, PageModel page);

        List<T> GetPageList(List<IConditionalModel> conditionalList, PageModel page, Expression<Func<T, object>> orderByExpression = null, OrderByType orderByType = OrderByType.Asc);

        T GetSingle(Expression<Func<T, bool>> whereExpression);

        T GetFirst(Expression<Func<T, bool>> whereExpression);

        bool Insert(T insertObj);

        bool InsertOrUpdate(T data);

        bool InsertOrUpdate(List<T> datas);

        bool InsertRange(List<T> insertObjs);

        bool InsertRange(T[] insertObjs);

        int InsertReturnIdentity(T insertObj);

        long InsertReturnBigIdentity(T insertObj);

        long InsertReturnSnowflakeId(T insertObj);

        List<long> InsertReturnSnowflakeId(List<T> insertObjs);

        T InsertReturnEntity(T insertObj);

        bool IsAny(Expression<Func<T, bool>> whereExpression);

        bool Update(Expression<Func<T, T>> columns, Expression<Func<T, bool>> whereExpression);

        bool UpdateSetColumnsTrue(Expression<Func<T, T>> columns, Expression<Func<T, bool>> whereExpression);

        bool Update(T updateObj);

        bool UpdateRange(List<T> updateObjs);

        bool UpdateRange(T[] updateObjs);

        Task<int> CountAsync(Expression<Func<T, bool>> whereExpression);

        Task<bool> DeleteAsync(Expression<Func<T, bool>> whereExpression);

        Task<bool> DeleteAsync(T deleteObj);

        Task<bool> DeleteAsync(List<T> deleteObjs);

        Task<bool> DeleteByIdAsync(dynamic id);

        Task<bool> DeleteByIdsAsync(dynamic[] ids);

        Task<T> GetByIdAsync(dynamic id);

        Task<List<T>> GetListAsync();

        Task<List<T>> GetListAsync(Expression<Func<T, bool>> whereExpression);

        Task<List<T>> GetPageListAsync(Expression<Func<T, bool>> whereExpression, PageModel page);

        Task<List<T>> GetPageListAsync(Expression<Func<T, bool>> whereExpression, PageModel page, Expression<Func<T, object>> orderByExpression = null, OrderByType orderByType = OrderByType.Asc);

        Task<List<T>> GetPageListAsync(List<IConditionalModel> conditionalList, PageModel page);

        Task<List<T>> GetPageListAsync(List<IConditionalModel> conditionalList, PageModel page, Expression<Func<T, object>> orderByExpression = null, OrderByType orderByType = OrderByType.Asc);

        Task<T> GetSingleAsync(Expression<Func<T, bool>> whereExpression);

        Task<T> GetFirstAsync(Expression<Func<T, bool>> whereExpression);

        Task<bool> InsertAsync(T insertObj);

        Task<bool> InsertOrUpdateAsync(T data);

        Task<bool> InsertOrUpdateAsync(List<T> datas);

        Task<bool> InsertRangeAsync(List<T> insertObjs);

        Task<bool> InsertRangeAsync(T[] insertObjs);

        Task<int> InsertReturnIdentityAsync(T insertObj);

        Task<long> InsertReturnBigIdentityAsync(T insertObj);

        Task<long> InsertReturnSnowflakeIdAsync(T insertObj);

        Task<List<long>> InsertReturnSnowflakeIdAsync(List<T> insertObjs);

        Task<T> InsertReturnEntityAsync(T insertObj);

        Task<bool> IsAnyAsync(Expression<Func<T, bool>> whereExpression);

        Task<bool> UpdateSetColumnsTrueAsync(Expression<Func<T, T>> columns, Expression<Func<T, bool>> whereExpression);

        Task<bool> UpdateAsync(Expression<Func<T, T>> columns, Expression<Func<T, bool>> whereExpression);

        Task<bool> UpdateAsync(T updateObj);

        Task<bool> UpdateRangeAsync(List<T> updateObjs);

        Task<bool> UpdateRangeAsync(T[] updateObjs);
    }
}

基于这个方便设计一个仓储基类

public interface IRepositoryBase<T> : ISimpleClient<T>
    where T : class, new()
{
}

public class RepositoryBase<T> : SimpleClient<T>, IRepositoryBase<T>
    where T : class, new()
{
    public RepositoryBase(ISqlSugarClient context) : base(context) { }
}

3、实现SetupSqlSugar扩展方法

SqlSugar中有提供一个TenantAttribute的Attribute,考虑到SqlSugar有可能使用此Attribute进行其他的处理,稳妥起见实现一个CodeFirstAttribute,通过反射枚举此Attribute来进行对应数据库的InitTables来进行数据库与对象表的自动建立

public static class SqlSugarSetup
{
    public static IServiceCollection SetupSqlSugar(this IServiceCollection services, Action<SqlSugarOptions> setup)
    {
        services.AddOptions();
        services.Configure(setup);

        return services.AddSingleton<ISqlSugarClient, SqlSugarScope>(provider =>
        {
            var logger = provider.GetRequiredService<ILoggerFactory>().CreateLogger("SqlSugar");
            var options = provider.GetRequiredService<IOptions<SqlSugarOptions>>().Value;
            // 将ConnectionConfig列表中未指定ConfigId值设置为"default", 与CodeFirstAttribute进行关联
            foreach (var config in options.connectionConfigs.Where(config =>
                config.ConfigId == null)) config.ConfigId = "default";
            var sugarScope = new SqlSugarScope(options.connectionConfigs);

            foreach (var item in options.connectionConfigs)
            {
                logger.LogInformation($"DbType: {item.DbType}, ConnectionString: {item.ConnectionString}");
            }
#if DEBUG
            sugarScope.Aop.OnLogExecuted = (sql, args) =>
            {
                logger.LogInformation(sql, args);
            };
#endif
            sugarScope.Aop.OnError = ex =>
            {
                logger.LogError(ex.InnerException?.Message);
            };

            sugarScope.DbMaintenance.CreateDatabase();

            List<Assembly> assemblies = AppDomain.CurrentDomain.GetCurrentAppAssemblies();
            var types = assemblies.SelectMany(x => x.GetExportedTypes())
                .Where(x => !x.IsAbstract && x.IsClass && x.IsDefined(typeof(CodeFirstAttribute)));

            foreach (var config in options.connectionConfigs)
            {
                var sugar = sugarScope.AsTenant().GetConnectionScope(config.ConfigId);
                var configuredTypes = types.Where(x =>
                    x.GetCustomAttribute<CodeFirstAttribute>()?.ConfigId.Equals(config.ConfigId, StringComparison.OrdinalIgnoreCase))
                    .ToArray();

                sugar.CodeFirst.InitTables(configuredTypes);
            }

            return sugarScope;
        });
    }
}

其中CodeFirstAttribute实现

[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class CodeFirstAttribute: Attribute
{
    /// <summary>
    /// 配置ID
    /// 用于区分不同的连接
    /// </summary>
    public string ConfigId { get; set; }

    public CodeFirstAttribute(string configId = "default")
    {
        ConfigId = configId;
    }
}

主要的作用是在反射扫描程序集时枚举CodeFirstAttribute属性,并放入集合为下一步的逻辑处理提供支持

List<Assembly> assemblies = AppDomain.CurrentDomain.GetCurrentAppAssemblies();
var types = assemblies.SelectMany(x => x.GetExportedTypes())
    .Where(x => !x.IsAbstract && x.IsClass && x.IsDefined(typeof(CodeFirstAttribute)));

依据数据库的ConfigId来对实体映射到相应的数据库

foreach (var config in options.connectionConfigs)
{
    var sugar = sugarScope.AsTenant().GetConnectionScope(config.ConfigId);
    var configuredTypes = types.Where(x =>
        x.GetCustomAttribute<CodeFirstAttribute>()?.ConfigId.Equals(config.ConfigId, StringComparison.OrdinalIgnoreCase))
        .ToArray();

    sugar.CodeFirst.InitTables(configuredTypes);
}

4、将租户模式集成到简单仓储模式

所使用的配置集合时,sqlite的配置是首个元素、mysql处于下一个元素,通过调试可以了解到注入到仓储接口中的ISqlSugarClient为集合中首个元素的实列

在实际的使用过程中不可能保证到哪个Connection放在首位,鉴于这类情况,为了保证简单仓库兼容租户模式的使用,需要将ISqlSugarClient定位到对应的对象context.AsTenant().GetConnectionScope(ConfigId)

至此,不同类型数据库租户加简单仓储模式已经完成,各个类型的实体和相应的数据库进行映射,自动维护表结构

5、根据表字段自动分表、自动联表操作

在MySql数据库中存在一个数据较大的表,为了优化操作考虑用分表来优化设计,对后期的操作提供性能上的优势;SqlSugar支持以时间分表(内置)、自定义分表的功能,我们这里需要进行分表的字段是非时间性的字段,所以选用自定义分表的功能;

  • 需要在配置数据库时指定ConnectionConfig.ConfigureExternalServices.SplitTableService

    ConfigureExternalServices = new ConfigureExternalServices()
    {
        SplitTableService = new DefaultSplitTableService()
    }
    
  • 在需要自动分表的实体模型上面加上[SplitTable(SplitType._Custom01)]属性

  • 在分表字段上面加上[SplitField]属性

另外,需要实现一个继承ISplitTableService接口的服务,依次实现接口方法

public class DefaultSplitTableService : ISplitTableService
{
    public List<SplitTableInfo> GetAllTables(ISqlSugarClient db, EntityInfo EntityInfo, List<DbTableInfo> tableInfos)
    {
        var splitType = EntityInfo.Type.GetCustomAttribute<SplitTableAttribute>()?.SplitType;
        var tableName = EntityInfo.DbTableName;

        List<SplitTableInfo> result = new List<SplitTableInfo>();
        if (splitType == null || splitType != SplitType._Custom01) return result;

        foreach (var tableInfo in tableInfos)
        {
            if (tableInfo.Name.Contains(tableName, StringComparison.OrdinalIgnoreCase))
            {
                SplitTableInfo info = new SplitTableInfo();
                info.TableName = tableInfo.Name;
                result.Add(info);
            }
        }

        return result;
    }

    public object GetFieldValue(ISqlSugarClient db, EntityInfo entityInfo, SplitType splitType, object entityValue)
    {
        var splitColumn = entityInfo.Columns.FirstOrDefault(x =>
            x.PropertyInfo.GetCustomAttribute<SplitFieldAttribute>() != null);
        var value = splitColumn.PropertyInfo.GetValue(entityValue, null);

        return value;
    }

    public string GetTableName(ISqlSugarClient db, EntityInfo EntityInfo)
    {
        throw new NotImplementedException();
    }

    public string GetTableName(ISqlSugarClient db, EntityInfo EntityInfo, SplitType type)
    {
        throw new NotImplementedException();
    }

    public string GetTableName(ISqlSugarClient db, EntityInfo entityInfo, SplitType splitType, object fieldValue)
    {
        return entityInfo.DbTableName + "_" + fieldValue;
    }
}

GetTableName中返回最终生成分表名的规则.

对于有自动分表功能的表增删改查的过程中需要联表支持,需要在IUpdateableIDeleteableIInsertableISugarQueryable调用方法SplitTable(Func<List<SplitTableInfo>, IEnumerable<SplitTableInfo>> getTableNamesFunc)

6、验证自动分表

当前数据库中不存在workinfo_自动生成测试的表,调用接口测试

使用Navicat查看数据库中表

根据新增的数据

查看并验证数据

SqlSugar ORM 5.X 官网 (donet5.com)

标签:CodeFirst,Task,映射,List,whereExpression,var,分表,Expression,public
From: https://www.cnblogs.com/linxmouse/p/17148724.html

相关文章