449 lines
15 KiB
C#
449 lines
15 KiB
C#
using System.Collections.Concurrent;
|
||
using JNPF;
|
||
using JNPF.Common.Core.Manager;
|
||
using JNPF.Common.Extension;
|
||
using JNPF.Common.Manager;
|
||
using JNPF.DependencyInjection;
|
||
using Microsoft.Extensions.Caching.Memory;
|
||
using SqlSugar;
|
||
using Tnb.Core;
|
||
using Tnb.Vengine.Domain;
|
||
|
||
namespace Tnb.Vengine.DataAccess;
|
||
|
||
/// <summary>
|
||
///
|
||
/// </summary>
|
||
public class DataAccess : IDataAccess, ITransient, IDisposable
|
||
{
|
||
private const int MAX_PAGE_SIZE = 1000;
|
||
private static ISqlSugarClient? _db;
|
||
private readonly ICacheManager _cache;
|
||
|
||
protected ISqlSugarClient Db
|
||
{
|
||
get
|
||
{
|
||
if (_db == null)
|
||
{
|
||
ConnectionStringsOptions conn = App.GetConfig<ConnectionStringsOptions>("ConnectionStrings", true);
|
||
//var dbType = App.Configuration.GetConnectionString("DbType");
|
||
//var dbConn = App.Configuration.GetConnectionString("DbConn");
|
||
_db = SugarHelper.CreateSugarClient(conn.ConfigId, conn.DBType, conn.ConnectString);
|
||
}
|
||
return _db;
|
||
}
|
||
}
|
||
|
||
private readonly IUserManager _user;
|
||
|
||
/// <summary>
|
||
/// 全局缓存
|
||
/// </summary>
|
||
private static readonly ConcurrentDictionary<string, ISqlSugarClient> DbCache = new();
|
||
|
||
/// <summary>
|
||
/// 构造
|
||
/// </summary>
|
||
public DataAccess(IUserManager currentUser, ICacheManager cache)
|
||
{
|
||
_user = currentUser;
|
||
_cache = cache;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 释放
|
||
/// </summary>
|
||
public void Dispose()
|
||
{
|
||
foreach (KeyValuePair<string, ISqlSugarClient> item in DbCache)
|
||
{
|
||
item.Value.Dispose();
|
||
}
|
||
DbCache.Clear();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取 ISqlSugarClient
|
||
/// </summary>
|
||
public ISqlSugarClient GetSqlSugar(string? dbCode = null)
|
||
{
|
||
if (string.IsNullOrEmpty(dbCode) || dbCode == DbConsts.DefaultDbCode)
|
||
{
|
||
return Db;
|
||
}
|
||
if (DbCache.ContainsKey(dbCode))
|
||
{
|
||
return DbCache[dbCode];
|
||
}
|
||
|
||
VmodelLink dblink = GetVmodelLink(dbCode);
|
||
ThrowIf.IsNull(dblink, $"没有此数据库{dbCode}连接信息");
|
||
ISqlSugarClient sugar = SugarHelper.CreateSugarClient(dblink.dbCode, dblink.dbType, dblink.dbConnection);
|
||
if (sugar.Ado.IsValidConnection())
|
||
{
|
||
DbCache[dbCode] = sugar;
|
||
}
|
||
else
|
||
{
|
||
sugar.Dispose();
|
||
throw new Exception($"无法连接到数据库{dbCode}");
|
||
}
|
||
return DbCache[dbCode];
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取 DbLink
|
||
/// </summary>
|
||
public VmodelLink GetVmodelLink(string dbCode)
|
||
{
|
||
VmodelLink model = Db.Queryable<VmodelLink>().First(a => a.dbCode == dbCode);
|
||
return model;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取 Vmodel 的缓存键
|
||
/// </summary>
|
||
public string GetVmodelCacheKey(string id)
|
||
{
|
||
return $"tnb.vmodel:{id}";
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取 Vmodel, 为空时不抛异常
|
||
/// </summary>
|
||
public async Task<Vmodel?> TryGetVmodelAsync(string id, bool loadNavigate = false)
|
||
{
|
||
Vmodel vm = await Db.Queryable<Vmodel>().FirstAsync(a => a.id == id && a.deleted == 0);
|
||
if (vm != null && loadNavigate)
|
||
{
|
||
await LoadVmodelNavigateAsync(vm);
|
||
}
|
||
return vm;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取 Vmodel, 为空时抛异常
|
||
/// </summary>
|
||
public async Task<Vmodel> GetVmodelAsync(string id, bool loadNavigate = false)
|
||
{
|
||
var key = GetVmodelCacheKey(id);
|
||
var vm = await _cache.GetAsync<Vmodel>(key);
|
||
if (vm == null)
|
||
{
|
||
vm = await Db.Queryable<Vmodel>().FirstAsync(a => a.id == id && a.deleted == 0);
|
||
ThrowIf.IsNull(vm, $"找不到id={id}的模型");
|
||
if (loadNavigate)
|
||
{
|
||
await LoadVmodelNavigateAsync(vm);
|
||
}
|
||
await _cache.SetAsync(id, vm, TimeSpan.FromMinutes(10));
|
||
}
|
||
return vm;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取 Vmodel, 为空时不抛异常
|
||
/// </summary>
|
||
public async Task<Vmodel?> TryGetVmodelAsync(string areaCode, string vmCode, bool loadNavigate = false)
|
||
{
|
||
Vmodel vm = await Db.Queryable<Vmodel>().FirstAsync(a => a.areaCode == areaCode && a.vmCode == vmCode && a.deleted == 0);
|
||
if (vm != null && loadNavigate)
|
||
{
|
||
await LoadVmodelNavigateAsync(vm);
|
||
}
|
||
|
||
return vm;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取 Vmodel, 为空时抛异常
|
||
/// </summary>
|
||
public async Task<Vmodel> GetVmodelAsync(string areaCode, string vmCode, bool loadNavigate = false)
|
||
{
|
||
Vmodel vm = await Db.Queryable<Vmodel>().FirstAsync(a => a.areaCode == areaCode && a.vmCode == vmCode && a.deleted == 0);
|
||
ThrowIf.IsNull(vm, $"找不到areaCode={areaCode}, vmCode={vmCode}的模型");
|
||
if (loadNavigate)
|
||
{
|
||
await LoadVmodelNavigateAsync(vm);
|
||
}
|
||
|
||
return vm;
|
||
}
|
||
|
||
///// <summary>
|
||
///// 获取 Vmodel
|
||
///// </summary>
|
||
//public async Task<Vmodel?> GetVmodelAsync(string tableName, string? dbCode)
|
||
//{
|
||
// Vmodel vm = await _db.Queryable<Vmodel>().FirstAsync(a => a.tableName == tableName && a.dbCode == dbCode && a.deleted == 0);
|
||
// return vm;
|
||
//}
|
||
|
||
/// <summary>
|
||
/// 加载模型的导航属性
|
||
/// </summary>
|
||
/// <param name="vm"></param>
|
||
/// <returns></returns>
|
||
private async Task LoadVmodelNavigateAsync(Vmodel vm)
|
||
{
|
||
Dictionary<string, Vmodel> dictVm = new();
|
||
List<string> vmids = vm.navProps.Select(a => a.vmid).Distinct().ToList();
|
||
List<Vmodel> ls = await Db.Queryable<Vmodel>().Where(a => vmids.Contains(a.id)).ToListAsync();
|
||
Dictionary<string, Vmodel> navs = ls.ToDictionary(a => a.id);
|
||
foreach (VmNavProp navProp in vm.navProps)
|
||
{
|
||
navProp.naviModel = navs.GetOrDefault(navProp.vmid);
|
||
//if (!dictVm.ContainsKey(navProp.vmid))
|
||
//{
|
||
// var navModel = await GetVmodelAsync(navProp.vmid);
|
||
// dictVm.Add(navProp.vmid, navModel);
|
||
//}
|
||
//navProp.naviModel = dictVm[navProp.vmid];
|
||
}
|
||
}
|
||
/// <summary>
|
||
/// 查询数据 默认方法
|
||
/// </summary>
|
||
public async Task<VmPagedOutput> QueryDataAsync(Vmodel vm, VmQueryInput input)
|
||
{
|
||
//var sw = Stopwatch.StartNew();
|
||
ISqlSugarClient db = GetSqlSugar(vm.dbCode);
|
||
ISugarQueryable<object> query = db.Queryable<object>().AS(vm.tableName, VmQueryParser.MAIN_ALIES);
|
||
VmQueryParser parser = new(this, vm, input);
|
||
parser.ParseQueryInput();
|
||
await parser.LoadNavigateAsync();
|
||
// 处理导航属性联表
|
||
List<JoinInfoParameter> joins = parser.GetJoinInfos();
|
||
_ = query.AddJoinInfo(joins);
|
||
|
||
// 处理查询参数条件
|
||
List<IConditionalModel> wheres = parser.GetConditionalModels();
|
||
wheres.Add(parser.GetKeywordConditional());
|
||
_ = query.Where(wheres);
|
||
|
||
// 处理排序字段
|
||
_ = query.OrderBy(parser.GetOrderByModels());
|
||
|
||
//处理输出字段
|
||
List<SelectModel> selects = parser.GetSelectModels();
|
||
_ = query.Select(selects);
|
||
//JNPF.Logging.Log.Debug($"解析查询参数耗时:{sw.ElapsedMilliseconds}ms");
|
||
//sw.Restart();
|
||
|
||
//查询数据
|
||
VmPagedOutput result = new();
|
||
int skip = input.pnum > 0 ? (input.pnum - 1) * input.psize : 0;
|
||
int take = input.psize == 0 ? MAX_PAGE_SIZE : input.psize;
|
||
if (input.pnum > 0) { result.total = await query.CountAsync(); }
|
||
List<Dictionary<string, object>> ls = await query.Skip(skip).Take(take).ToDictionaryListAsync();
|
||
//JNPF.Logging.Log.Debug($"查询数据耗时:{sw.ElapsedMilliseconds}ms");
|
||
//sw.Restart();
|
||
|
||
for (int i = 0; i < ls.Count; i++)
|
||
{
|
||
result.items.Add(new DObject());
|
||
}
|
||
await CombineOutputAsync(ls, result.items, parser);
|
||
//JNPF.Logging.Log.Debug($"组装返回结果耗时:{sw.ElapsedMilliseconds}ms");
|
||
|
||
return result;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 组装子模型对象
|
||
/// </summary>
|
||
private async Task CombineOutputAsync(List<Dictionary<string, object>> src, List<DObject> dest, VmQueryParser parser)
|
||
{
|
||
foreach (VmNavigate nav in parser.Navigates.Values)
|
||
{
|
||
// 加载主表字段
|
||
if (nav.path == VmQueryParser.MAIN_ALIES || nav.navConfig.navType == eNavigateType.None)
|
||
{
|
||
foreach (VmSelectProp prop in nav.selects)
|
||
{
|
||
for (int i = 0; i < src.Count; i++)
|
||
{
|
||
dest[i].Add(prop.code, src[i].GetOrDefault(prop.code));
|
||
}
|
||
}
|
||
}
|
||
else if (nav.navConfig.navType == eNavigateType.OneToOne)
|
||
{
|
||
foreach (VmSelectProp prop in nav.selects)
|
||
{
|
||
string key = prop.asName.Replace(VmQueryParser.PROP_SEPERATE, VmQueryParser.NAVI_SEPERATE);
|
||
for (int i = 0; i < src.Count; i++)
|
||
{
|
||
dest[i].Add(key, src[i].GetOrDefault(prop.asName));
|
||
}
|
||
}
|
||
}
|
||
else if (nav.navConfig.navType == eNavigateType.OneToMany)
|
||
{
|
||
object[] ids = new object[] { "in" };
|
||
ids = ids.Union(dest.Select(a => a.GetOrDefault(nav.navConfig.refProp)).Where(a => a != null)).ToArray();
|
||
VmQueryInput input = new()
|
||
{
|
||
q = new DObject(nav.navConfig.fkProp, ids)
|
||
};
|
||
nav.wheres.ForEach(a =>
|
||
{
|
||
input.q.Add(a.code, a.value);
|
||
});
|
||
input.o = string.Join(',', nav.selects.Select(a => a.code));
|
||
List<DObject> childs = (await QueryDataAsync(nav.navConfig.naviModel!, input)).items;
|
||
for (int i = 0; i < dest.Count; i++)
|
||
{
|
||
string? fkValue = dest[i][nav.navConfig.refProp]?.ToString();
|
||
dest[i][nav.pathCode] = childs.Where(a => a[nav.navConfig.fkProp]?.ToString() == fkValue).ToList();
|
||
}
|
||
//for (int i = 0; i < src.Count; i++)
|
||
//{
|
||
// //dest[i].Add(selectVm.pathCode, new List<DObject>());
|
||
// if (src[i].ContainsKey(refProp))
|
||
// {
|
||
// VmQueryInput input = new VmQueryInput();
|
||
// input.q = new DObject(fkProp, src[i][refProp]);
|
||
// nav.wheres.ForEach(a =>
|
||
// {
|
||
// input.q.Add(a.code, a.value);
|
||
// });
|
||
// input.o = string.Join(',', nav.selects.Select(a => a.code));
|
||
// dest[i][nav.pathCode] = (await QueryDataAsync(nav.navConfig.naviModel, input)).items;
|
||
// }
|
||
//}
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 新增数据 默认方法
|
||
/// </summary>
|
||
public async Task<dynamic> CreateDataAsync(Vmodel vm, VmCreateInput input)
|
||
{
|
||
ISqlSugarClient db = GetSqlSugar(vm.dbCode);
|
||
ThrowIf.When(input.data == null && input.items == null, "新增数据时,data和items不可同时为空");
|
||
//新增一条数据
|
||
if (input.data != null)
|
||
{
|
||
VmDbProp pkey = vm.GetPrimary();
|
||
DObject model = vm.ToCreateEntity(input.data, _user);
|
||
if (pkey.csType is "int" or "long")
|
||
{
|
||
long id = await db.Insertable(model).AS(vm.tableName).ExecuteReturnBigIdentityAsync();
|
||
if ((long)input.data[pkey.code] != id)
|
||
{
|
||
input.data[pkey.code] = id;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
_ = await db.Insertable(model).AS(vm.tableName).ExecuteCommandAsync();
|
||
}
|
||
return input.data;
|
||
}
|
||
//批量新增数据
|
||
else
|
||
{
|
||
VmDbProp pkey = vm.GetPrimary();
|
||
List<DObject> lst = new();
|
||
foreach (DObject item in input.items!)
|
||
{
|
||
lst.Add(vm.ToCreateEntity(item, _user));
|
||
}
|
||
if (pkey.csType == "int")
|
||
{
|
||
List<int> ids = await db.Insertable(lst).AS(vm.tableName).ExecuteReturnPkListAsync<int>();
|
||
for (int i = 0; i < input.items.Count; i++)
|
||
{
|
||
input.items[i][pkey.code] = ids[i];
|
||
}
|
||
}
|
||
else if (pkey.csType == "long")
|
||
{
|
||
List<long> ids = await db.Insertable(lst).AS(vm.tableName).ExecuteReturnPkListAsync<long>();
|
||
for (int i = 0; i < input.items.Count; i++)
|
||
{
|
||
input.items[i][pkey.code] = ids[i];
|
||
}
|
||
}
|
||
else
|
||
{
|
||
_ = await db.Insertable(lst).AS(vm.tableName).ExecuteCommandAsync();
|
||
}
|
||
return input.items;
|
||
}
|
||
//else
|
||
//{
|
||
// input.data = vm.GetDefaultDObject();
|
||
// var model = vm.PropToField(input.data);
|
||
// num = await db.Insertable(model).AS(vm.tableName).ExecuteCommandAsync();
|
||
// return input.data;
|
||
//}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新数据 默认方法
|
||
/// </summary>
|
||
public async Task<dynamic> UpdateDataAsync(Vmodel vm, VmUpdateInput input)
|
||
{
|
||
ISqlSugarClient db = GetSqlSugar(vm.dbCode);
|
||
VmDbProp pk = vm.GetPrimary();
|
||
int num = 0;
|
||
//修改一条数据
|
||
if (input.data != null)
|
||
{
|
||
DObject model = vm.ToUpdateEntity(input.data, _user);
|
||
if (!model.ContainsKey(pk.field))
|
||
{
|
||
throw new Exception($"更新数据时主键({pk.code})不可为空");
|
||
}
|
||
//if (!model.ContainsKey(pk.field) && input.id != null)
|
||
//{
|
||
// model.Add(pk.field, input.id);
|
||
//}
|
||
num = await db.Updateable(model).AS(vm.tableName).WhereColumns(pk.field).ExecuteCommandAsync();
|
||
}
|
||
//批量修改数据
|
||
else if (input.items != null)
|
||
{
|
||
List<DObject> lst = new();
|
||
foreach (DObject item in input.items)
|
||
{
|
||
DObject model = vm.ToUpdateEntity(item, _user);
|
||
if (model.ContainsKey(pk.field))
|
||
{
|
||
lst.Add(model);
|
||
}
|
||
}
|
||
num = await db.Updateable(lst).AS(vm.tableName).WhereColumns(pk.field).ExecuteCommandAsync();
|
||
}
|
||
return num;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 删除数据 默认方法
|
||
/// </summary>
|
||
public async Task<int> DeleteDataAsync(Vmodel vm, VmDeleteInput input)
|
||
{
|
||
ISqlSugarClient db = GetSqlSugar(vm.dbCode);
|
||
VmDbProp pk = vm.GetPrimary();
|
||
int num = 0;
|
||
List<Dictionary<string, object>> ids = new();
|
||
if (input.id != null)
|
||
{
|
||
ids.Add(new DObject(pk.field, input.id));
|
||
}
|
||
else if (input.ids != null)
|
||
{
|
||
ids.AddRange(input.ids.Select(a => new DObject(pk.field, a)));
|
||
}
|
||
if (ids.Count > 0)
|
||
{
|
||
num = await db.Deleteable<object>().AS(vm.tableName).WhereColumns(ids).ExecuteCommandAsync();
|
||
}
|
||
|
||
return num;
|
||
}
|
||
} |