第三十三节:基于ShardingCore框架分库、分库分表实操
一. 只分库-实操
1. 目标
有三个DB,分别是ShardingDB_00、ShardingDB_01、ShardingDB_02,每个DB中都有两张表:Order、SysUser。
(1). Order表中的数据,按照用户区域字段Area来分库。
(2). SysUser表中的数据,也是按照用户区域字段Area来分库。
PS:Area字段是string类型,所以需要自己设计分库算法, 我们这里的算法为:先对字段求hashcode,然后对 3 取模,得到 0、1、2, 正好存放到xx_00、xx_01、xx_02 数据库中。
2. 实操步骤
(这里只分享核心代码,表实体代码、EFCore上下文代码 不再重复)
(1). 分库路由
因为分库仅提供默认路由,所以需要用户自行实现AbstractShardingOperatorVirtualDataSourceRoute抽象, 分库逻辑需要在重写的 ShardingKeyToDataSourceName 方法中实现。
分库原理:
A. 这里靠的是返回数据源的名称,返回00,则对应数据源名称为00,这个shardingkey则进入00数据源对应的DB
B. 00 指的是program中定义的名称,也就是这里的_dataSources
C. 至于 名称00 对应的DB的真实名称是什么,不是很关键,可以叫 ShardingDB_00 ,也可以叫 ShardingDB_AAAA
代码分享:
/// <summary> /// Order表按照用户区域字段Area来分库 /// 因为分库仅提供默认路由,所以需要用户自行实现AbstractShardingOperatorVirtualDataSourceRoute抽象 /// </summary> public class OrderVirtualDataSourceRoute : AbstractShardingOperatorVirtualDataSourceRoute<Order, string> { ////数据源名称--需要和program中的名称对应 //private readonly List<string> _dataSources = ["A", "B", "C"]; ////我们设置区域就是数据库 //public override string ShardingKeyToDataSourceName(object shardingKey) //{ // return shardingKey?.ToString() ?? string.Empty; //} //数据源名称--需要和program中的名称对应 private readonly List<string> _dataSources = ["00", "01", "02"]; /// <summary> /// 自定义分库的逻辑 /// </summary> /// <param name="shardingKey"></param> /// <returns>返回的是上述数据源名称中的某一个</returns> public override string ShardingKeyToDataSourceName(object shardingKey) { //1.对分库键求hashcode var hashCode = ShardingCoreHelper.GetStringHashCode(shardingKey?.ToString() ?? string.Empty); //2,因为分了3个库,所以对3取模,值可能为 0,1,2 var result1 = hashCode % 3; //3 求绝对值 var result2 = Math.Abs(result1); //4 因为上述的数据源名称00、01、02,所以借助PadLeft函数,在左侧加1个0,就组成00、01、02了 var finalResult = result2.ToString().PadLeft(2, '0'); return finalResult; //这里靠的是返回数据源的名称,返回00,则对应数据源名称为00,这个shardingkey则进入00数据源对应的DB //00 指的是program中定义的名称,也就是这里的_dataSources //至于 名称00 对应的DB的真实名称是什么,不是很关键,可以叫 ShardingDB_00 ,也可以叫 ShardingDB_AAAA //等价于上面的步骤 //return Math.Abs(ShardingCoreHelper.GetStringHashCode(shardingKey?.ToString() ?? string.Empty) % 3).ToString().PadLeft(2, '0'); } public override List<string> GetAllDataSourceNames() { return _dataSources; } public override bool AddDataSourceName(string dataSourceName) { if (_dataSources.Any(o => o == dataSourceName)) { return false; } _dataSources.Add(dataSourceName); return true; } public override Func<string, bool> GetRouteToFilter(string shardingKey, ShardingOperatorEnum shardingOperator) { var t = ShardingKeyToDataSourceName(shardingKey); switch (shardingOperator) { case ShardingOperatorEnum.Equal: { return tail => tail == t; } default: { return tail => true; } } } /// <summary> /// 分库字段设置 /// </summary> /// <param name="builder"></param> public override void Configure(EntityMetadataDataSourceBuilder<Order> builder) { builder.ShardingProperty(o => o.Area); } } /// <summary> /// SysUser表的分库,也是根据Area来分库 /// </summary> public class SysUserVirtualDataSourceRoute : AbstractShardingOperatorVirtualDataSourceRoute<SysUser, string> { //数据源名称--需要和program中的名称对应 private readonly List<string> _dataSources = ["00", "01", "02"]; /// <summary> /// 自定义分库的逻辑 /// </summary> /// <param name="shardingKey"></param> /// <returns>返回的是上述数据源名称中的某一个</returns> public override string ShardingKeyToDataSourceName(object shardingKey) { //1.对分库键求hashcode var hashCode = ShardingCoreHelper.GetStringHashCode(shardingKey?.ToString() ?? string.Empty); //2,因为分了3个库,所以对3取模,值可能为 0,1,2 var result1 = hashCode % 3; //3 求绝对值 var result2 = Math.Abs(result1); //4 因为上述的数据源名称00、01、02,所以借助PadLeft函数,在左侧加1个0,就组成00、01、02了 var finalResult = result2.ToString().PadLeft(2, '0'); return finalResult; //这里靠的是返回数据源的名称,返回00,则对应数据源名称为00,这个shardingkey则进入00数据源对应的DB //00 指的是program中定义的名称,也就是这里的_dataSources //至于 名称00 对应的DB的真实名称是什么,不是很关键,可以叫 ShardingDB_00 ,也可以叫 ShardingDB_AAAA //等价于上面的步骤 //return Math.Abs(ShardingCoreHelper.GetStringHashCode(shardingKey?.ToString() ?? string.Empty) % 3).ToString().PadLeft(2, '0'); } public override List<string> GetAllDataSourceNames() { return _dataSources; } public override bool AddDataSourceName(string dataSourceName) { if (_dataSources.Any(o => o == dataSourceName)) return false; _dataSources.Add(dataSourceName); return true; } public override Func<string, bool> GetRouteToFilter(string shardingKey, ShardingOperatorEnum shardingOperator) { var t = ShardingKeyToDataSourceName(shardingKey); switch (shardingOperator) { case ShardingOperatorEnum.Equal: return tail => tail == t; default: { return tail => true; } } } public override void Configure(EntityMetadataDataSourceBuilder<SysUser> builder) { builder.ShardingProperty(o => o.Area); } }
(2). DB字符串配置
{
"ConnectionStrings": {
"SQLServerStr1": "Server=47.101.xxx.xxx;Database=ShardingDB_00;User=sa;Password=xxxxx;TrustServerCertificate=True;",
"SQLServerStr2": "Server=47.101.xxx.xxx;Database=ShardingDB_01;User=sa;Password=xxxxx;TrustServerCertificate=True;",
"SQLServerStr3": "Server=47.101.xxx.xxx;Database=ShardingDB_02;User=sa;Password=xxxxx;TrustServerCertificate=True;",
"MySQLStr": ""
},
"AllowedHosts": "*"
}
(3). program中的注入代码
注意代码: serviceProvider.UseAutoTryCompensateTable(); //启动进行数据库和表补偿--(自行判断缺少的分片对象(包括分表或者分库),自动创建)
代码分享:
public class Program { public static readonly ILoggerFactory efLogger = LoggerFactory.Create(builder => { builder.AddFilter((category, level) => category == DbLoggerCategory.Database.Command.Name && level == LogLevel.Information).AddConsole(); }); public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllers(); builder.Services.AddEndpointsApiExplorer(); //注册OpenApi builder.Services.AddSwaggerGen(options => { var xmlFilename = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, xmlFilename)); }); var SQLServerStr1 = builder.Configuration.GetConnectionString("SQLServerStr1"); var SQLServerStr2 = builder.Configuration.GetConnectionString("SQLServerStr2"); var SQLServerStr3 = builder.Configuration.GetConnectionString("SQLServerStr3"); /* ShardingCore相关配置 1. AddShardingDbContext配置包含了原生的AddDbContext+AddShardingConfigure,无需单独注入原生DBContext 2. DefaultShardingDbContext继承了原生DBContext,随意具有原生的所有功能 */ builder.Services.AddShardingDbContext<MyDbContext>() .UseRouteConfig(op => { op.AddShardingDataSourceRoute<OrderVirtualDataSourceRoute>(); op.AddShardingDataSourceRoute<SysUserVirtualDataSourceRoute>(); }) .UseConfig(op => { #region 一些配置 【暂时注释,需要什么开启什么】 //{ // //当查询无法匹配到对应的路由是否抛出异常 true表示抛出异常 false表示返回默认值 // op.ThrowIfQueryRouteNotMatch = false; // //如果开启读写分离是否在savechange commit rollback后自动将dbcontext切换为写库 true表示是 false表示不是 // op.AutoUseWriteConnectionStringAfterWriteDb = false; // //创建表如果出现错误是否忽略掉错误不进行日志出输出 true表示忽略 false表示不忽略 // op.IgnoreCreateTableError = false; // //默认的系统默认迁移并发数,分库下会进行并发迁移 // op.MigrationParallelCount = Environment.ProcessorCount; // //补偿表并发线程数 补偿表用来进行最后一次补偿缺少的分片表 // op.CompensateTableParallelCount = Environment.ProcessorCount; // //默认最大连接数限制 如果出现跨分片查询需要开启n个链接,设置这个值会将x个链接分成每n个为一组进行同库串行执行 // op.MaxQueryConnectionsLimit = Environment.ProcessorCount; // //链接模式的选择系统自动 // op.ConnectionMode = ConnectionModeEnum.SYSTEM_AUTO; //} #endregion //如何通过字符串查询创建DbContext op.UseShardingQuery((conStr, builder) => { builder.UseSqlServer(conStr, o => o.UseCompatibilityLevel(120)).UseLoggerFactory(efLogger); }); //如何通过事务创建DbContext op.UseShardingTransaction((connection, builder) => { builder.UseSqlServer(connection, o => o.UseCompatibilityLevel(120)).UseLoggerFactory(efLogger); }); //添加默认数据源 op.AddDefaultDataSource("00", SQLServerStr1); //添加额外数据源--用于分库 op.AddExtraDataSource(sp => { return new Dictionary<string, string>() { { "01", SQLServerStr2 }, { "02", SQLServerStr3 }, }; }); }) .AddShardingCore(); /***************************************下面是管道相关***************************************************/ var app = builder.Build(); IServiceProvider serviceProvider = app.Services; if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } //启动进行数据库和表补偿--(自行判断缺少的分片对象(包括分表或者分库),自动创建) serviceProvider.UseAutoTryCompensateTable(); //初始化分库分表数据,可以放在这里,也可以定义接口初始化 app.UseRouting(); app.UseAuthorization(); app.MapControllers(); app.Run(); } }
3. 测试
(1). 启动代码,会自动生成对应的DB 和 Table。
(2). 插入测试
代码分享
/// <summary>
/// 插入数据
/// </summary>
/// <returns></returns>
[HttpPost]
public async Task<IActionResult> InsertData()
{
//数据1
string[] areas = ["A", "B", "C"];
List<SysUser> users = [];
for (int i = 0; i < 10; i++)
{
var uer = new SysUser() { Id = i.ToString(), Name = $"MyName{i}", Area = areas[i % 3] };
users.Add(uer);
}
//数据2
List<Order> orders = [];
var begin = new DateTime(2021, 1, 1, 3, 3, 3);
for (int i = 0; i < 300; i++)
{
var order = new Order()
{
Id = i.ToString(),
Payer = $"{i % 10}",
Money = 100 + new Random().Next(100, 3000),
OrderStatus = (OrderStatusEnum)(i % 4 + 1),
Area = areas[i % 3],
CreationTime = begin.AddDays(i)
};
orders.Add(order);
}
db.AddRange(orders);
db.AddRange(users);
int count = await db.SaveChangesAsync();
return Json(new { status = "ok", msg = $"插入成功,条数为{count}" });
}
结果查看
ShardingDB_00中Order表中的Area的值都是 B
ShardingDB_01中Order表中的Area的值都是 C
ShardingDB_02中Order表中的Area的值都是 A
(3). 其他测试
包括 删除、修改、join查询等等
代码分享
/// <summary> /// 根据player修改 /// </summary> /// <param name="playName"></param> /// <returns></returns> [HttpPost] public async Task<IActionResult> UpdateByPlayer(string playName) { var list = await db.Set<Order>().Where(o => o.Payer == playName).ToListAsync(); foreach (var item in list) { item.Money = 99999; } int count = await db.SaveChangesAsync(); return Json(new { status = "ok", msg = $"修改成功,条数为{count}" }); } /// <summary> /// 根据player删除 /// </summary> /// <param name="playName"></param> [HttpPost] public async Task<IActionResult> DelByPlayer(string playName) { var list = await db.Set<Order>().Where(o => o.Payer == playName).ToListAsync(); foreach (var item in list) { db.Remove(item); } var count = await db.SaveChangesAsync(); return Json(new { status = "ok", msg = $"修改成功,条数为{count}" }); } /// <summary> /// 删除所有数据 /// </summary> [HttpPost] public async Task<IActionResult> DelAll(string playName) { var count = await db.Set<Order>().ExecuteDeleteAsync(); return Json(new { status = "ok", msg = $"修改成功,条数为{count}" }); } /// <summary> /// 查询-单对象 /// </summary> [HttpPost] public async Task<IActionResult> SearchSimple() { var list = await db.Set<Order>().Where(o => true).OrderBy(u => u.Money).ToListAsync(); var count = list.Count(); return Json(new { status = "ok", msg = $"查询成功,数量:{count}", data = list }); } /// <summary> /// 查询-分库join问题 /// 关联字段是否有索引是有区别的,这里不测试了 /// </summary> [HttpPost] public async Task<IActionResult> SearchJoin() { var begin = new DateTime(2021, 3, 2); var end = new DateTime(2021, 4, 3); var sql1 = from user in db.Set<SysUser>().Where(o => o.Id == "1" || o.Id == "6") join order in db.Set<Order>().Where(o => o.CreationTime >= begin && o.CreationTime <= end) on user.Id equals order.Payer select new { user.Id, user.Name, user.Area, OrderId = order.Id, order.Payer, order.CreationTime }; var list = await sql1.ToListAsync(); return Json(new { status = "ok", msg = $"查询成功,数量:{list.Count}", data = list }); }
二. 分库且分表-实操
1. 目标
有三个DB,分别是ShardingDB_00、ShardingDB_01、ShardingDB_02,每个DB中都有6张表:SysUser_00、SysUser_01、SysUser_02、Order_2022、Order_2023、Order_2024。
(1). Order表中的数据,先按照用户区域字段Area来分库,在每个库的基础上,再按照时间字段 creationTime 来分表。
(2). SysUser表中的数据,先按照用户区域字段Area来分库,在每个库的基础上,再按照ID字段 ID 来分表。
PS:Area字段是string类型,所以需要自己设计分库算法, 我们这里的算法为:先对字段求hashcode,然后对 3 取模,得到 0、1、2, 正好存放到xx_00、xx_01、xx_02 数据库中。
2. 实操步骤
(这里只分享核心代码,表实体代码、EFCore上下文代码 不再重复)
(1) 分库路由
同上 单独的只分库的路由相同。
(2) 分表路由
Order表---根据CreationTime分表
/// <summary>
/// order表根据creationTime时间来分表,以年为单位
/// </summary>
public class OrderVirtualTableRoute : AbstractSimpleShardingYearKeyDateTimeVirtualTableRoute<Order>
{
/// <summary>
/// 自动建表
/// (目前没发现作用,即使为false也会自动建表)(无效!!)
/// </summary>
/// <returns></returns>
public override bool AutoCreateTableByTime()
{
return true;
}
/// <summary>
/// 配置分表键
/// </summary>
/// <param name="builder"></param>
public override void Configure(EntityMetadataTableBuilder<Order> builder)
{
builder.ShardingProperty(u => u.CreationTime);
}
/// <summary>
/// 配置开始时间
/// </summary>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public override DateTime GetBeginTime()
{
return new DateTime(2022, 1, 1);
}
}
SysUser表--根据ID分表
/// <summary>
/// SysUser表根据string类型Id进行分表
/// </summary>
public class SysUserVirtualTableRoute : AbstractSimpleShardingModKeyStringVirtualTableRoute<SysUser>
{
/// <summary>
/// base中第1个参数:表示分表名称后面的长度
/// 2表示分表后的格式:xxx_01
/// 3表示分表后的格式:xxx_001
/// 4表示分表后的格式:xxx_0001
/// base中的第2个参数:表示分表取模的除数 和 自动建表的个数
/// 如下3,表示对3取模, 自动建表的时候会建三张表 xxx_00 xxx_01 xxx_02
/// 注:就算不使用框架的自动建表,手动建表也需要按照这个格式
///
/// </summary>
public SysUserVirtualTableRoute() : base(2, 3)
{
}
public override void Configure(EntityMetadataTableBuilder<SysUser> builder)
{
builder.ShardingProperty(o => o.Id);
}
}
(3) DB字符串
同上,只分库的字符串
(4). program中的注入代码
注意代码: serviceProvider.UseAutoTryCompensateTable(); //启动进行数据库和表补偿--(自行判断缺少的分片对象(包括分表或者分库),自动创建)
代码分享:
public class Program { public static readonly ILoggerFactory efLogger = LoggerFactory.Create(builder => { builder.AddFilter((category, level) => category == DbLoggerCategory.Database.Command.Name && level == LogLevel.Information).AddConsole(); }); public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllers(); builder.Services.AddEndpointsApiExplorer(); //注册OpenApi builder.Services.AddSwaggerGen(options => { var xmlFilename = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, xmlFilename)); }); var SQLServerStr1 = builder.Configuration.GetConnectionString("SQLServerStr1"); var SQLServerStr2 = builder.Configuration.GetConnectionString("SQLServerStr2"); var SQLServerStr3 = builder.Configuration.GetConnectionString("SQLServerStr3"); /* ShardingCore相关配置 1. AddShardingDbContext配置包含了原生的AddDbContext+AddShardingConfigure,无需单独注入原生DBContext 2. DefaultShardingDbContext继承了原生DBContext,随意具有原生的所有功能 */ builder.Services.AddShardingDbContext<MyDbContext>() .UseRouteConfig(o => { //添加分库路由 o.AddShardingDataSourceRoute<OrderVirtualDataSourceRoute>(); o.AddShardingDataSourceRoute<SysUserVirtualDataSourceRoute>(); //添加分表路由 o.AddShardingTableRoute<SysUserVirtualTableRoute>(); o.AddShardingTableRoute<OrderVirtualTableRoute>(); }) .UseConfig(op => { #region 一些配置 【暂时注释,需要什么开启什么】 //{ // //当查询无法匹配到对应的路由是否抛出异常 true表示抛出异常 false表示返回默认值 // op.ThrowIfQueryRouteNotMatch = false; // //如果开启读写分离是否在savechange commit rollback后自动将dbcontext切换为写库 true表示是 false表示不是 // op.AutoUseWriteConnectionStringAfterWriteDb = false; // //创建表如果出现错误是否忽略掉错误不进行日志出输出 true表示忽略 false表示不忽略 // op.IgnoreCreateTableError = false; // //默认的系统默认迁移并发数,分库下会进行并发迁移 // op.MigrationParallelCount = Environment.ProcessorCount; // //补偿表并发线程数 补偿表用来进行最后一次补偿缺少的分片表 // op.CompensateTableParallelCount = Environment.ProcessorCount; // //默认最大连接数限制 如果出现跨分片查询需要开启n个链接,设置这个值会将x个链接分成每n个为一组进行同库串行执行 // op.MaxQueryConnectionsLimit = Environment.ProcessorCount; // //链接模式的选择系统自动 // op.ConnectionMode = ConnectionModeEnum.SYSTEM_AUTO; //} #endregion //如何通过字符串查询创建DbContext op.UseShardingQuery((conStr, builder) => { builder.UseSqlServer(conStr, o => o.UseCompatibilityLevel(120)).UseLoggerFactory(efLogger); }); //如何通过事务创建DbContext op.UseShardingTransaction((connection, builder) => { builder.UseSqlServer(connection, o => o.UseCompatibilityLevel(120)).UseLoggerFactory(efLogger); }); //添加默认数据源 op.AddDefaultDataSource("00", SQLServerStr1); //添加额外数据源--用于分库 op.AddExtraDataSource(sp => { return new Dictionary<string, string>() { { "01", SQLServerStr2 }, { "02", SQLServerStr3 }, }; }); }) .AddShardingCore(); /***************************************下面是管道相关***************************************************/ var app = builder.Build(); IServiceProvider serviceProvider = app.Services; if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } //启动进行数据库和表补偿--(自行判断缺少的分片对象(包括分表或者分库),自动创建) serviceProvider.UseAutoTryCompensateTable(); //初始化分库分表数据,可以放在这里,也可以定义接口初始化 app.UseRouting(); app.UseAuthorization(); app.MapControllers(); app.Run(); } }
3. 测试
(1). 启动代码,会自动生成对应的DB 和 Table
(2). 插入测试
代码分享
/// <summary>
/// 插入数据
/// </summary>
/// <returns></returns>
[HttpPost]
public async Task<IActionResult> InsertData()
{
//数据1
string[] areas = ["A", "B", "C"];
List<SysUser> users = [];
for (int i = 0; i < 100; i++)
{
var user = new SysUser()
{
Id = i.ToString(),
Name = $"MyName{i}",
Area = areas[new Random().Next(0, 3)]
};
users.Add(user);
}
//数据2
List<Order> orders = [];
var begin = new DateTime(2022, 1, 1);
for (int i = 0; i < 300; i++)
{
var sysUser = users[i % 100];
var order = new Order()
{
Id = i.ToString(),
Payer = sysUser.Id,
Money = 100 + new Random().Next(100, 3000),
OrderStatus = (OrderStatusEnum)(i % 4 + 1),
Area = sysUser.Area,
};
//让时间平均分配在 2022、2023、2024年度
if (i < 100)
{
order.CreationTime = begin.AddDays(i);
}
if (i >= 100 && i < 200)
{
order.CreationTime = begin.AddYears(1).AddDays(i);
}
if (i >= 200 && i < 300)
{
order.CreationTime = begin.AddYears(2).AddDays(i);
}
orders.Add(order);
}
db.AddRange(orders);
db.AddRange(users);
int count = await db.SaveChangesAsync();
return Json(new { status = "ok", msg = $"插入成功,条数为{count}" });
}
结果分析:
ShardingDB_00中Order表中的Area的值都是 B,每张表按照createTime分表, SysUser表中的Area的值都是 B,每张表按照ID分表
ShardingDB_01中Order表中的Area的值都是 C,每张表按照createTime分表, SysUser表中的Area的值都是 C,每张表按照ID分表
ShardingDB_02中Order表中的Area的值都是 A,每张表按照createTime分表, SysUser表中的Area的值都是 A,每张表按照ID分表
(3) 其他代码
/// <summary> /// 删除所有数据 /// </summary> [HttpPost] public async Task<IActionResult> DelAll() { var count1 = await db.Set<Order>().ExecuteDeleteAsync(); var count2 = await db.Set<SysUser>().ExecuteDeleteAsync(); return Json(new { status = "ok", msg = $"修改成功,条数为{count1 + count2}" }); } /// <summary> /// 根据player修改 /// </summary> /// <param name="playName"></param> /// <returns></returns> [HttpPost] public async Task<IActionResult> UpdateByPlayer(string playName) { var list = await db.Set<Order>().Where(o => o.Payer == playName).ToListAsync(); foreach (var item in list) { item.Money = 99999; } int count = await db.SaveChangesAsync(); return Json(new { status = "ok", msg = $"修改成功,条数为{count}" }); } /// <summary> /// 根据player删除 /// </summary> /// <param name="playName"></param> [HttpPost] public async Task<IActionResult> DelByPlayer(string playName) { var list = await db.Set<Order>().Where(o => o.Payer == playName).ToListAsync(); foreach (var item in list) { db.Remove(item); } var count = await db.SaveChangesAsync(); return Json(new { status = "ok", msg = $"修改成功,条数为{count}" }); } /// <summary> /// 查询-单对象 /// </summary> /// <param name="orderId">订单主键</param> /// <param name="userId">用户主键</param> /// <returns></returns> [HttpPost] public async Task<IActionResult> SearchSimple(string orderId, string userId) { var data1 = await db.Set<Order>().Where(o => o.Id == orderId).FirstOrDefaultAsync(); var data2 = await db.Set<SysUser>().Where(o => o.Id == userId).FirstOrDefaultAsync(); return Json(new { status = "ok", msg = $"查询成功", data = new { data1, data2 } }); } /// <summary> /// 查询-分库join问题 /// 关联字段是否有索引是有区别的,这里不测试了 /// </summary> [HttpPost] public async Task<IActionResult> SearchJoin() { var begin = new DateTime(2021, 3, 2); var end = new DateTime(2025, 4, 3); var sql1 = from user in db.Set<SysUser>().Where(o => o.Id == "1" || o.Id == "6") join order in db.Set<Order>().Where(o => o.CreationTime >= begin && o.CreationTime <= end) on user.Id equals order.Payer select new { user.Id, user.Name, user.Area, OrderId = order.Id, order.Payer, order.CreationTime }; var list = await sql1.ToListAsync(); return Json(new { status = "ok", msg = $"查询成功,数量:{list.Count}", data = list }); }
!
- 作 者 : Yaopengfei(姚鹏飞)
- 博客地址 : http://www.cnblogs.com/yaopengfei/
- 声 明1 : 如有错误,欢迎讨论,请勿谩骂^_^。
- 声 明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。