высокопроизводительное легковесное решение для шардинга таблиц и баз данных в EF Core, поддержка разделения на чтение и запись.
Китайская документация Github | Английская документация Github
Китайская документация Gitee | Английская документация Gitee
shardingcore lastversion.efcoreversion.x.x
первая версия - это версия shardingcore
вторая версия - это версия efcore
остальные версии используют последнюю версию
efcore9 использует shardingcore7.9.x.x
efcore8 использует shardingcore7.8.x.x
efcore7 использует shardingcore7.7.x.x
efcore6 использует shardingcore7.6.x.x
efcore5 использует shardingcore7.5.x.x
efcore3 использует shardingcore7.3.x.x
efcore2 использует shardingcore7.2.x.x
--- | --- | --- | --- 6.x.x.x | 6.0.0 | net 6.0 | 6.0+ 5.x.x.x | 5.0.10 | Standard 2.1 | 5.0+ 3.x.x.x | 3.1.18 | Standard 2.0 | 2.0+ 2.x.x.x | 2.2.6 | Standard 2.0 | 2.0+
Выпуск | EF Core | .NET (Core) |
---|---|---|
6.7.0.0+ | 6.x | net6 |
6.7.0.0+ | 5.x | net5 или netstandard2.1 |
6.7.0.0+ | 3.x | netcoreapp3 или netstandard2.0 |
6.7.0.0+ | 2.x | netcoreapp2 |
5 шагов для реализации шардинга по месяцам и поддержки автоматического создания таблиц по месяцам
Выберите драйвер базы данных для EF Core
# базовый пакет [README-zh.md](README-zh.md)
PM> Install-Package ShardingCore
# драйвер для SQL Server
PM> Install-Package Microsoft.EntityFrameworkCore.SqlServer
# драйвер для MySQL
#PM> Install-Package Pomelo.EntityFrameworkCore.MySql
# используйте другой драйвер базы данных, если EF Core поддерживает
Запрос сущности заказа
/// таблица заказов
/// </summary>
public class Order
{
/// <summary>
/// идентификатор заказа
/// </summary>
public string Id { get; set; }
/// <summary>
/// идентификатор плательщика
/// </summary>
public string Payer { get; set; }
/// <summary>
/// сумма платежа в центах
/// </summary>
public long Money { get; set; }
/// <summary>
/// регион
/// </summary>
public string Area { get; set; }
/// <summary>
/// статус заказа
/// </summary>
public OrderStatusEnum OrderStatus { get; set; }
/// <summary>
/// время создания
/// </summary>
public DateTime CreationTime { get; set; }
}
public enum OrderStatusEnum
{
NoPay = 1,
Paying = 2,
Paid = 3,
PayFail = 4
}
Создайте MyDbContext
, расширяющий AbstractShardingDbContext
,
затем этот DbContext
поддерживает шардинг базы данных, если вы хотите поддержать
шардинг таблиц (например, разбиение таблицы, order_202101, order_202102, order_202103...),
вам нужно реализовать IShardingTableDbContext
{
public MyDbContext(DbContextOptions<MyDbContext> options) : base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Order>(entity =>
{
entity.HasKey(o => o.Id);
entity.Property(o => o.Id).IsRequired().IsUnicode(false).HasMaxLength(50);
entity.Property(o => o.Payer).IsRequired().IsUnicode(false).HasMaxLength(50);
entity.Property(o => o.Area).IsRequired().IsUnicode(false).HasMaxLength(50);
entity.Property(o => o.OrderStatus).HasConversion<int>();
// На самом деле имя таблицы Order_202101, Order_202102, Order_202103.....
entity.ToTable(nameof(Order));
});
}
/// <summary>
/// Пустая реализация, если используется шардинг таблиц
/// </summary>
public IRouteTail RouteTail { get; set; }
}
### Шаг 4: Создание маршрута для запроса данных (Order) с отображением имени таблицы
```csharp
// Конструктор маршрута поддерживает внедрение зависимостей, поэтому его срок жизни — `Singleton`
public class OrderVirtualTableRoute : AbstractSimpleShardingMonthKeyDateTimeVirtualTableRoute<Order>
{
//private readonly IServiceProvider _serviceProvider;
//public OrderVirtualTableRoute(IServiceProvider serviceProvider)
//{
//_serviceProvider = serviceProvider;
//}
/// <summary>
/// Фиксированное значение, не используйте DateTime.Now, так как при перезапуске приложения это значение изменится
/// </summary>
/// <returns></returns>
public override DateTime GetBeginTime()
{
return new DateTime(2021, 1, 1);
}
/// <summary>
/// Настройка свойства шардинга
/// </summary>
/// <param name="builder"></param>
``` public override void Configure(EntityMetadataTableBuilder<Order> builder)
{
builder.ShardingProperty(o => o.CreationTime);
}
/// <summary>
/// Включение автоматического создания таблицы
/// </summary>
/// <returns></returns>
public override bool AutoCreateTable()
{
return true;
}
}
Необходимо изменить второй параметр метода AddDefaultDataSource
(строка подключения), не изменяйте параметры делегатов UseShardingQuery
и UseShardingTransaction
{
``` // конфигурация шардинга
services.AddShardingDbContext<MyDbContext>()
.UseRouteConfig(op =>
{
op.AddShardingTableRoute<OrderVirtualTableRoute>();
}).UseConfig(op =>
{
op.UseShardingQuery((connStr, builder) =>
{
// connStr — это входной параметр делегата
builder.UseSqlServer(connStr);
});
op.UseShardingTransaction((connection, builder) =>
{
// connection — это входной параметр делегата
builder.UseSqlServer(connection);
});
// используйте строку подключения к базе данных
op.AddDefaultDataSource(Guid.NewGuid().ToString("n"),
"Data Source=localhost;Initial Catalog=EFCoreShardingTableDB;Integrated Security=True;");
}).AddShardingCore();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
// не требуется, включить проверку отсутствующих таблиц и автоматическое создание
app.ApplicationServices.UseAutoTryCompensateTable();
// другие настройки....
}
Затем EF Core поддерживает шардинг таблиц, как Sharding-JDBC в Java, теперь вы можете с удовольствием программировать для EF Core.```csharp [Route("api/[controller]")] public class ValuesController : Controller { private readonly MyDbContext _myDbContext;
public ValuesController(MyDbContext myDbContext)
{
_myDbContext = myDbContext;
}
[HttpGet]
public async Task<IActionResult> Get()
{
//_myDbContext.Add(order);
//_myDbContext.Update(order);
//_myDbContext.Remove(order);
//_myDbContext.SaveChanges();
var order = await _myDbContext.Set<Order>().FirstOrDefaultAsync(o => o.Id == "2");
return Ok(order);
}
}
## Пропускная способность
Тестирование
- кэша компиляции выражений
- версия ShardingCore 3.1.63+
- версия EF Core 6.0
- идентификатор заказа является строкой, шардинг мод (хешкод % 5)
- N означает количество выполнений
[Пример тестирования](https://github.com/xuejmnet/sharding-core/blob/main/benchmarks/ShardingCoreBenchmark/EFCoreCrud.cs)
### Убыль производительности SQL Server 2012, строки данных 7 734 363 = 7,73 млн
// * Сводка *
BenchmarkDotNet=v0.13.1, ОС=Windows 10.0.18363.1500 (1909/November2019Update/19H2)
AMD Ryzen 9 3900X, 1 CPU, 24 логических и 12 физических ядер
.NET SDK=6.0.100
[Host] : .NET 6.0.0 (6.0.21.52210), X64 RyuJIT
DefaultJob : .NET 6.0.0 (6.0.21.52210), X64 RyuJIT
| Метод | N | Среднее | Ошибка | Стандартное отклонение |
|----------------------------------- |--- |---------:|----------:|----------:|
| NoShardingIndexFirstOrDefaultAsync | 10 | 1.512 мс | 0.0071 мс | 0.0063 мс |
| ShardingIndexFirstOrDefaultAsync | 10 | 1.567 мс | 0.0127 мс | 0.0113 мс |
Для запросов к нешардируемым данным видно, что разница в 10 запросах составляет 0,05 мс, а убыль в одном запросе составляет около 5 микросекунд = 0,005 мс, что составляет 3% от общего времени.### Тест производительности#### SQL Server 2012, строк данных 7734363 = 7730000
// * Сводка *
BenchmarkDotNet=v0.13.1, ОС=Windows 10.0.18363.1500 (1909/November2019Update/19H2)
AMD Ryzen 9 3900X, 1 ЦПУ, 24 логических и 12 физических ядра
.NET SDK=6.0.101
[Host] : .NET 6.0.1 (6.0.121.56705), X64 RyuJIT
DefaultJob : .NET 6.0.1 (6.0.121.56705), X64 RyuJIT
| Метод | N | Среднее | Ошибка | Стандартное отклонение |
|------------------------------------- |--- |---------------:|-------------:|---------------------------:|
| NoShardingIndexFirstOrDefaultAsync | 10 | 1.678 мс | 0.0323 мс | 0.0359 мс |
| ShardingIndexFirstOrDefaultAsync | 10 | 2.005 мс | 0.0161 мс | 0.0143 мс |
| NoShardingNoIndexFirstOrDefaultAsync | 10 | 495.933 мс | 9.4911 мс | 10.5494 мс |
| ShardingNoIndexFirstOrDefaultAsync | 10 | 596.112 мс | 11.8907 мс | 13.2165 мс |
| NoShardingNoIndexCountAsync | 10 | 477.537 мс | 1.4817 мс | 1.2373 мс |
| ShardingNoIndexCountAsync | 10 | 594.833 мс | 7.4057 мс | 5.7819 мс |
| NoShardingNoIndexLikeToListAsync | 10 | 665.277 мс | 1.3382 мс | 1.1174 мс |
| ShardingNoIndexLikeToListAsync | 10 | 840.865 мс | 16.1917 мс | 17.3249 мс |
| NoShardingNoIndexToListAsync | 10 | 480.368 мс | 1.3688 мс | 1.2134 мс |
| ShardingNoIndexToListAsync | 10 | 604.850 мс | 8.6204 мс | 8.0635 мс |
#### MySQL 5.7, строк данных 7553790 = 7550000, innodb_buffer_pool_size=3G
// * Сводка *BenchmarkDotNet=v0.13.1, ОС=Windows 10.0.18363.1500 (1909/November2019Update/19H2)
AMD Ryzen 9 3900X, 1 ЦПУ, 24 логических и 12 физических ядра
.NET SDK=6.0.101
[Host] : .NET 6.0.1 (6.0.121.56705), X64 RyuJIT
DefaultJob : .NET 6.0.1 (6.0.121.56705), X64 RyuJIT| Метод | N | Среднее | Ошибка | Стандартное отклонение |
|------------------------------------- |--- |--------------:|------------:|------------:|
| NoShardingIndexFirstOrDefaultAsync | 10 | 5.646 мс | 0.0164 мс | 0.0145 мс |
| ShardingIndexFirstOrDefaultAsync | 10 | 5.679 мс | 0.0359 мс | 0.0319 мс |
| NoShardingNoIndexFirstOrDefaultAsync | 10 | 5,212.736 мс | 230.0841 мс | 678.4080 мс |
| ShardingNoIndexFirstOrDefaultAsync | 10 | 2,013.107 мс | 10.4256 мс | 9.2420 мс |
| NoShardingNoIndexCountAsync | 10 | 9,483.988 мс | 42.0931 мс | 39.3739 мс |
| ShardingNoIndexCountAsync | 10 | 2,029.698 мс | 12.4008 мс | 10.9929 мс |
| NoShardingNoIndexLikeToListAsync | 10 | 10,569.283 мс | 20.9163 мс | 16.3301 мс |
| ShardingNoIndexLikeToListAsync | 10 | 2,208.804 мс | 11.0483 мс | 10.3346 мс |
| NoShardingNoIndexToListAsync | 10 | 9,485.263 мс | 21.2558 мс | 17.7496 мс |
| ShardingNoIndexToListAsync | 10 | 2,012.086 мс | 39.2986 мс | 45.2563 мс |# ИспользованиеВсе приведенные ниже примеры иллюстрируются на примере SQL Server. Код, представленный ниже, демонстрирует разделение таблиц. Если вам требуется разделение баз данных, обратитесь к [Sample.SqlServerShardingDataSource](https://github.com/xuejmnet/sharding-core/tree/main/samples/Sample.SqlServerShardingDataSource). Для других баз данных ситуация аналогична.
## Введение
### Краткое описание библиотеки: все версии этой библиотеки определяются номером версии EF Core, а второй номер версии указывает на поддержку разделения баз данных и таблиц. Библиотека разделена на два основных ветвления: main и shardingTableOnly. Она поддерживает полностью настраиваемое маршрутизирование для разделения баз данных, что подходит для 95% бизнес-потребностей. Поддержка разделения таблиц включает в себя схему x+y+z, где x — это фиксированное имя таблицы, y — связь между фиксированным именем таблицы и её суффиксом (может быть пустым), а z — это суффикс таблицы, который можно настроить по своему усмотрению. Примеры: user_0, user_1 или user202101, user202102 и т.д. Библиотека также подходит для изоляции в многоснимковых моделях.Поддержка различных запросов включает ```join, group by, max, count, min, avg, sum``` и т.д. В настоящее время использование библиотеки очень простое и сводится к расширению IQueryable. Для обеспечения чистоты и независимости библиотеки, автоматическое создание таблиц можно настроить с помощью задач CRON. Библиотека предоставляет [IShardingTableCreator](https://github.com/xuejmnet/sharding-core/blob/main/src/ShardingCore/TableCreator/IShardingTableCreator.cs) для создания таблиц, а также пример [автоматического создания таблиц по дате](https://github.com/xuejmnet/sharding-core/tree/main/samples/Samples.AutoByDate.SqlServer) для динамического добавления разделения баз данных.## КонцепцииНесколько основных концепций этого репозитория:### Концепция разделения источников данных
- [DataSourceName]
Имя источника данных используется для маршрутизации объектов к конкретному источнику данных.
- [IVirtualDataSource]
Виртуальный источник данных [IVirtualDataSource](https://github.com/xuejmnet/sharding-core/blob/main/src/ShardingCore/Core/VirtualDatabase/VirtualDataSources/IVirtualDataSource.cs)
- [IVirtualDataSourceRoute]
Маршрутизация источников данных [IVirtualDataSourceRoute](https://github.com/xuejmnet/sharding-core/blob/main/src/ShardingCore/Core/VirtualRoutes/DataSourceRoutes/IVirtualDataSourceRoute.cs)
### Концепция разделения таблиц
- [Tail]
Хвост, конец физической таблицы.
- [TailPrefix]
Префикс хвоста, символы между концом виртуальной таблицы и физической таблицы.
- [Физическая таблица]
Физическая таблица представляет собой реальную таблицу в базе данных, имя таблицы (tablename + tailprefix + tail) [IPhysicTable](https://github.com/xuejmnet/sharding-core/blob/main/src/ShardingCore/Core/PhysicTables/IPhysicTable.cs)
- [Виртуальная таблица]
Виртуальная таблица представляет собой абстракцию всех физических таблиц в системе, соответствует одной сущности в программе [IVirtualTable](https://github.com/xuejmnet/sharding-core/blob/main/src/ShardingCore/Core/VirtualDatabase/VirtualTables/IVirtualTable.cs)
- [Виртуальная маршрутизация]
Виртуальная маршрутизация является промежуточным средством между виртуальными и физическими таблицами. Виртуальная таблица существует в программе в единственном экземпляре, поэтому программа должна знать, какую таблицу в системе она должна запросить.Самый простой способ — через маршрут, соответствующий виртуальной таблице [IVirtualTableRoute](https://github.com/xuejmnet/sharding-core/blob/main/src/ShardingCore/Core/VirtualRoutes/TableRoutes/IVirtualTableRoute.cs),
поскольку большинство маршрутов связаны с бизнес-логикой, виртуальная маршрутизация должна быть реализована пользователем, а этот фреймворк предоставляет более высокий уровень абстракции.## Преимущества
- [Поддержка пользовательских маршрутизаций источников данных]
- [Поддержка разделения чтения и записи]
- [Поддержка высокопроизводительной пагинации]
- [Поддержка ручной маршрутизации]
- [Поддержка массовых операций]
- [Поддержка пользовательских правил маршрутизации таблиц]
- [Поддержка произвольных ключей маршрутизации таблиц]
- [Нулевая стоимость обучения для dbcontext]
- [Поддержка соединений таблиц в рамках маршрутизации таблиц] ```join,group by,max,count,min,avg,sum```
- [Поддержка использования для массовых операций] [EFCore.BulkExtensions](https://github.com/borisdj/EFCore.BulkExtensions) ... поддержка расширений для EFCore
- [Предоставление нескольких стандартных маршрутизаций таблиц] по времени, по остатку, можно настроить
- [Оптимизация для пагинации] поддержка низкого потребления памяти при прокрутке большого количества страниц, высокопроизводительная пагинация
## Недостатки
- [Потребление соединений] При соединении распределенных таблиц с условием, которое не может быть индексировано до конкретной таблицы, генерируется ```произведение Картезиана``` и происходит взрыв соединений. В будущем будет введен специальный параметр для решения этой проблемы.
## Установка
```xml
<PackageReference Include="ShardingCore" Version="5.LastVersion" />
или
<PackageReference Include="ShardingCore" Version="3.LastVersion" />
или
<PackageReference Include="ShardingCore" Version="2.LastVersion" />
```# Начало
## Распределение таблиц
В качестве примера мы будем использовать пользователя с модулями, конфигурация entity рекомендуется [fluent api](https://docs.microsoft.com/en-us/ef/core/modeling/)
```c#
public class SysUserMod
{
/// <summary>
/// Идентификатор пользователя для распределения таблиц
/// </summary>
public string Id { get; set; }
/// <summary>
/// Имя пользователя
/// </summary>
public string Name { get; set; }
/// <summary>
/// Возраст пользователя
/// </summary>
public int Age { get; set; }
}
Создание виртуального маршрута
Реализация AbstractShardingOperatorVirtualTableRoute<T, TKey>
абстракции или реализация системного виртуального маршрута
Фреймворк предоставляет несколько простых маршрутов по умолчанию
public class SysUserModVirtualTableRoute : AbstractSimpleShardingModKeyStringVirtualRoute<SysUserMod>
{
// хвост длиной: 00, 01, 02......99
// хэшкод % 3: [0, 1, 2]
public SysUserModVirtualTableRoute() : base(2, 3)
{
}
public override void Configure(EntityMetadataTableBuilder<SysUserMod> builder)
{
builder.ShardingProperty(o => o.Id);
}
}
Если вы используете распределение таблиц, необходимо создать контекст DbContext
, который наследует от интерфейса IShardingTableDbContext
,
и реализовать IShardingDbContext
, по умолчанию предоставляется AbstractShardingDbContext
// DefaultTableDbContext - это фактический контекст выполнения
public class DefaultShardingDbContext : AbstractShardingDbContext, IShardingTableDbContext
{
public DefaultShardingDbContext(DbContextOptions<DefaultShardingDbContext> options) : base(options)
{
}
``````c#
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.ApplyConfiguration(new SysUserModMap());
}
public IRouteTail RouteTail { get; set; }
``````markdown
}
Startup.cs
файле метод ConfigureServices(IServiceCollection services)
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
//если вы хотите использовать режим без шардинга
//services.AddDbContext<DefaultTableDbContext>(o => o.UseSqlServer("Data Source=localhost;Initial Catalog=ShardingCoreDB;Integrated Security=True"));
//добавляем поддержку ShardingDbContext для жизненного цикла
services.AddShardingDbContext<DefaultShardingDbContext>(
(conStr, builder) => builder.UseSqlServer(conStr)
)
.Begin(o =>
{
o.CreateShardingTableOnStart = true; //создаем шардинг таблицу
o.EnsureCreatedWithOutShardingTable = true; //создаем источник данных без шардинг таблицы
})
.AddShardingTransaction((connection, builder) =>
builder.UseSqlServer(connection))
.AddDefaultDataSource("ds0", "Data Source=localhost;Initial Catalog=ShardingCoreDB1;Integrated Security=True;")
.AddShardingTableRoute(o =>
{
o.AddShardingTableRoute<SysUserModVirtualTableRoute>();
}).End();
Startup.cs
файле метод Configure(IApplicationBuilder app, IWebHostEnvironment env)
var shardingBootstrapper = app.ApplicationServices.GetRequiredService<IShardingBootstrapper>();
shardingBootstrapper.Start();
Как использовать
private readonly DefaultShardingDbContext _defaultShardingDbContext;
``` public ctor(DefaultShardingDbContext defaultShardingDbContext)
{
_defaultShardingDbContext = defaultShardingDbContext;
}
``` ```csharp
public async Task Insert_1000()
{
if (!_defaultShardingDbContext.Set<SysUserMod>().Any())
{
var ids = Enumerable.Range(1, 1000);
var userMods = new List<SysUserMod>();
foreach (var id in ids)
{
userMods.Add(new SysUserMod()
{
Id = id.ToString(),
Age = id,
Name = $"name_{id}",
AgeGroup = Math.Abs(id % 10)
});
}
``````markdown
## Распределение данных по базамДля примера возьмем пользовательские данные, используемые для распределения данных по базам. Рекомендуется использовать [fluent api](https://docs.microsoft.com/en-us/ef/core/modeling/) для конфигурации entity. Объекты баз данных `IShardingDataSource` должны наследовать этот интерфейс. Поля для распределения данных по базам должны быть помечены атрибутом `ShardingDataSourceKey`.
```c#
public class SysUserMod
{
/// <summary>
/// Идентификатор пользователя для распределения данных по базам
/// </summary>
[ShardingDataSourceKey]
public string Id { get; set; }
/// <summary>
/// Имя пользователя
/// </summary>
public string Name { get; set; }
/// <summary>
/// Возраст пользователя
/// </summary>
public int Age { get; set; }
}
Создание виртуального маршрута
Реализация AbstractShardingOperatorVirtualDataSourceRoute<T, TKey>
Абстрактный класс или реализация системного по умолчанию виртуального маршрута
Фреймворк предоставляет несколько простых маршрутов по умолчанию по умолчанию
public class SysUserModVirtualDataSourceRoute : AbstractShardingOperatorVirtualDataSourceRoute<SysUserMod, string>
{
protected readonly int Mod = 3;
protected readonly int TailLength = 1;
protected readonly char PaddingChar = '0';
}
``` protected override string ConvertToShardingKey(object shardingKey)
{
return shardingKey.ToString();
}
``` ```csharp
public override string ShardingKeyToDataSourceName(object shardingKey)
{
var shardingKeyStr = ConvertToShardingKey(shardingKey);
return "ds" + Math.Abs(ShardingCoreHelper.GetStringHashCode(shardingKeyStr) % Mod).ToString().PadLeft(TailLength, PaddingChar);
}
``` ```csharp
public override List<string> GetAllDataSourceNames()
{
return new List<string>()
{
"ds0",
"ds1",
"ds2"
};
} public override bool AddDataSourceName(string dataSourceName)
{
throw new NotImplementedException();
}
public override void Configure(EntityMetadataDataSourceBuilder<SysUserMod> builder)
{
builder.ShardingProperty(o => o.Name);
}
protected override Expression<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;
}
}
}
}
Если вы используете разделение баз данных, вам не потребуется интерфейс IShardingTableDbContext
.
Для создания контекста баз данных с разделением таблиц необходимо наследовать от AbstractShardingDbContext
.
// DefaultShardingDbContext — это фактический контекст базы данных
public class DefaultShardingDbContext : AbstractShardingDbContext
{
public DefaultShardingDbContext(DbContextOptions<DefaultShardingDbContext> options) : base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.ApplyConfiguration(new SysUserModMap());
}
}
Startup.cs
в методе `ConfigureServices(IServiceCollection services)````csharp
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddShardingDbContext<DefaultShardingDbContext>(
(conStr, builder) => builder.UseSqlServer(conStr)
).Begin(o =>
{
o.CreateShardingTableOnStart = true;
o.EnsureCreatedWithOutShardingTable = true;
})
.AddShardingTransaction((connection, builder) =>
builder.UseSqlServer(connection))
.AddDefaultDataSource("ds0", "Data Source=localhost;Initial Catalog=ShardingCoreDBxx0;Integrated Security=True;")
.AddShardingDataSource(sp =>
{
return new Dictionary<string, string>()
{
{"ds1", "Data Source=localhost;Initial Catalog=ShardingCoreDBxx1;Integrated Security=True;"},
{"ds2", "Data Source=localhost;Initial Catalog=ShardingCoreDBxx2;Integrated Security=True;"},
};
}).AddShardingDataSourceRoute(o =>
{
o.AddShardingDatabaseRoute<SysUserModVirtualDataSourceRoute>();
}).End();
````Startup.cs` файле в методе `Configure(IApplicationBuilder app, IWebHostEnvironment env)` вы также можете самостоятельно обернуть [app.UseShardingCore()](https://github.com/xuejmnet/sharding-core/blob/main/samples/Sample.SqlServer/DIExtension.cs)
``````c#
var shardingBootstrapper = app.ApplicationServices.GetRequiredService<IShardingBootstrapper>();
shardingBootstrapper.Start();
Как использовать
private readonly DefaultShardingDbContext _defaultShardingDbContext;
public ctor(DefaultShardingDbContext defaultShardingDbContext)
{
_defaultShardingDbContext = defaultShardingDbContext;
}
public async Task Insert_1000()
{
if (!_defaultShardingDbContext.Set<SysUserMod>().Any())
{
var ids = Enumerable.Range(1, 1000);
var userMods = new List<SysUserMod>();
foreach (var id in ids)
{
userMods.Add(new SysUserMod()
{
Id = id.ToString(),
Age = id,
Name = $"name_{id}",
AgeGroup = Math.Abs(id % 10)
});
}
_defaultShardingDbContext.AddRange(userMods);
await _defaultShardingDbContext.SaveChangesAsync();
}
}
public async Task ToList_All()
{
var mods = await _defaultShardingDbContext.Set<SysUserMod>().ToListAsync();
Assert.Equal(1000, mods.Count);
var modOrders1 = await _defaultShardingDbContext.Set<SysUserMod>().OrderBy(o => o.Age).ToListAsync();
int ascAge = 1;
foreach (var sysUserMod in modOrders1)
{
Assert.Equal(ascAge, sysUserMod.Age);
ascAge++;
}
var modOrders2 = await _defaultShardingDbContext.Set<SysUserMod>().OrderByDescending(o => o.Age).ToListAsync();
int descAge = 1000;
foreach (var sysUserMod in modOrders2)
{
Assert.Equal(descAge, sysUserMod.Age);
descAge--;
}
}
Дополнительные операции можно найти в юнит-тестах.
--- | --- | --- Получить коллекцию | ToListAsync | да Первый элемент | FirstOrDefaultAsync | да Максимум | MaxAsync | да Минимум | MinAsync | да Существование | AnyAsync | да Количество | CountAsync | да Количество | LongCountAsync | да Сумма | SumAsync | да Среднее значение | AverageAsync | да Вхождение | ContainsAsync | да Группировка | GroupByAsync | да## По умолчанию
Разделение по базам данных предоставляет по умолчанию правила маршрутизации, а разделение по таблицам требует реализации самостоятельно. Конкретные реализации можно посмотреть в разделении по базам данных.
Абстракция | Правила маршрутизации | хвост | индекс |
---|---|---|---|
AbstractSimpleShardingModKeyIntVirtualTableRoute | Берем остаток от деления | 0, 1, 2... | =, contains |
AbstractSimpleShardingModKeyStringVirtualTableRoute | Берем остаток от деления | 0, 1, 2... | =, contains |
AbstractSimpleShardingDayKeyDateTimeVirtualTableRoute | По дате | yyyyMMdd | >, >=, <, <=, =, contains |
AbstractSimpleShardingDayKeyLongVirtualTableRoute | По дате | yyyyMMdd | >, >=, <, <=, =, contains |
AbstractSimpleShardingWeekKeyDateTimeVirtualTableRoute | По дате | yyyyMMdd_dd | >, >=, <, <=, =, contains |
AbstractSimpleShardingWeekKeyLongVirtualTableRoute | По дате | yyyyMMdd_dd | >, >=, <, <=, =, contains |
AbstractSimpleShardingMonthKeyDateTimeVirtualTableRoute | По дате | yyyyMM | >, >=, <, <=, =, contains |
AbstractSimpleShardingMonthKeyLongVirtualTableRoute | По дате | yyyyMM | >, >=, <, <=, =, contains |
AbstractSimpleShardingYearKeyDateTimeVirtualTableRoute | По дате | yyyy | >, >=, <, <=, =, contains |
AbstractSimpleShardingYearKeyLongVirtualTableRoute | По дате | yyyy | >, >=, <, <=, =, contains |
Примечание: contains
означает o => ids.contains(o.shardingkey)
Примечание: Использование по умолчанию правил маршрутизации по дате заставит вас переопределить метод GetBeginTime
. Этот метод должен использовать статические значения, такие как new DateTime(2021, 1, 1)
, а не использовать динамические значения, такие как DateTime.Now
, так как каждый перезапуск будет вызывать этот метод, что приведет к неодинаковым результатам.# Продвинутое использование## code-first
В настоящее время sharding-core
поддерживает code-first, что позволяет использовать код для первоначальной настройки. Конкретная реализация можно посмотреть в разделе Migrations.
По умолчанию ShardingCore не поддерживает автоматическое отслеживание, и его использование не рекомендуется. Однако, если вам требуется, ShardingCore предоставляет возможность включения автоматического отслеживания. Важно отметить следующие моменты:
DbContex.Model
, анонимные типы не поддерживаются, а также не поддерживаются связанные запросы.ToList
, выполняют запрос к базе данных. При возврате данных проверяется, отслеживается ли объект. Если объект уже отслеживается, возвращается значение из кэша.First
, FirstOrDefault
, Last
, LastOrDefault
, Single
, SingleOrDefault
.Как включить:
services.AddShardingDbContext<DefaultShardingDbContext>(...)
.Begin(o => {
o.CreateShardingTableOnStart = true;
o.EnsureCreatedWithOutShardingTable = true;
// поддержка автоматического отслеживания через asnotracking, astracking, QueryTrackingBehavior. TrackAll
o.AutoTrackEntity = true;
})
```c#
конструктор принимает IShardingRouteManager shardingRouteManager public class SysUserModVirtualTableRoute : AbstractSimpleShardingModKeyStringVirtualTableRoute<SysUserMod>
{
/// <summary>
/// Включить подсказку маршрутизации
/// </summary>
protected override bool EnableHintRoute => true;
public SysUserModVirtualTableRoute() : base(2, 3)
{
}
}
using (_shardingRouteManager.CreateScope())
{
_shardingRouteManager.Current.TryCreateOrAddMustTail<SysUserMod>("00");
var mod00s = await _defaultTableDbContext.Set<SysUserMod>().Skip(10).Take(11).ToListAsync();
}
await _defaultShardingDbContext.SaveChangesAsync();
using (var tran = _defaultTableDbContext.Database.BeginTransaction())
{
........
_defaultTableDbContext.SaveChanges();
tran.Commit();
}
Z.EntityFramework.Plus.EFCore
для выполнения батч-операций или EFCore.BulkExtensions
, поддерживающие все сторонние батч-фреймворки.var list = new List<SysUserMod>();
/// Через коллекцию возвращаются соответствующие k-v агрегаты с использованием транзакций
var dbContexts = _defaultTableDbContext.BulkShardingEnumerable(list);
foreach (var dataSourceMap in dbContexts)
{
foreach (var tailMap in dataSourceMap.Value)
{
tailMap.Key.BulkInsert(tailMap.Value.ToList());
//tailMap.Key.BulkDelete(tailMap.Value.ToList());
//tailMap.Key.BulkUpdate(tailMap.Value.ToList());
}
}
_defaultTableDbContext.SaveChanges();
//или
var dbContexts = _defaultTableDbContext.BulkShardingEnumerable(list);
using (var tran = _defaultTableDbContext.Database.BeginTransaction())
{
foreach (var dataSourceMap in dbContexts)
{
foreach (var tailMap in dataSourceMap.Value)
{
tailMap.Key.BulkInsert(tailMap.Value.ToList());
//tailMap.Key.BulkDelete(tailMap.Value.ToList());
//tailMap.Key.BulkUpdate(tailMap.Value.ToList());
}
}
_defaultTableDbContext.SaveChanges();
tran.Commit();
}
Данная фреймворк поддерживает разделение на один основной и несколько дополнительных узлов для чтения и записи AddReadWriteSeparation
. Поддерживает два типа стратегий разделения на чтение и запись: циклический Loop
и случайный Random
. Однако, при использовании нескольких соединений для чтения и записи могут возникнуть проблемы с несоответствием данных (например, при пагинации это происходит в два этапа: сначала получение количества записей, затем получение списка записей), что может привести к проблемам с количеством данных на последних страницах.
Для решения этой проблемы фреймворк реализовал стратегию получения соединений для чтения ReadConnStringGetStrategyEnum.LatestEveryTime
, которая означает, что каждое соединение будет новым (что может привести к вышеупомянутым проблемам), и ReadConnStringGetStrategyEnum.LatestFirstTime
, которая означает, что соединение будет получено один раз на уровне dbcontext
(что не приведет к проблемам на уровне dbcontext
).Также из-за различных проблем с сетью, связанных с разделением на чтение и запись, новые записи могут не быть доступны. Поэтому система по умолчанию добавляет флаг использования разделения на чтение и запись на уровне dbcontext
. Если false
, то по умолчанию используется строка для записи для чтения _defaultTableDbContext.ReadWriteSeparation=false
или используются два готовых метода.
// Переключение на только чтение, только запись и только конфигурация для чтения A и B источников данных
_virtualDbContext.ReadWriteSeparationReadOnly();
_virtualDbContext.ReadWriteSeparationWriteOnly();
services.AddShardingDbContext<DefaultShardingDbContext>(
(conStr, builder) => builder.UseSqlServer(conStr).UseLoggerFactory(efLogger)
).Begin(o =>
{
o.CreateShardingTableOnStart = true;
o.EnsureCreatedWithOutShardingTable = true;
})
.AddShardingTransaction((connection, builder) =>
builder.UseSqlServer(connection).UseLoggerFactory(efLogger))
.AddDefaultDataSource("ds0",
"Data Source=localhost;Initial Catalog=ShardingCoreDB1;Integrated Security=True;"
)
.AddShardingTableRoute(o =>
{
o.AddShardingTableRoute<SysUserModVirtualTableRoute>();
})
.AddReadWriteSeparation(o =>
{
return new Dictionary<string, ISet<string>>()
{
{
"ds0", new HashSet<string>(){
"Data Source=localhost;Initial Catalog=ShardingCoreDBReadOnly1;Integrated Security=True;",
"Data Source=localhost;Initial Catalog=ShardingCoreDBReadOnly2;Integrated Security=True;"}
}
};
}, ReadStrategyEnum.Loop, defaultEnable: true)
.End();
Поддерживаемые версии x.2.0.16+
1. Как включить конфигурацию пагинации, например, если мы хотим настроить пагинацию для таблицы с новыми пользователями за месяц, сначала реализуем интерфейс IPaginationConfiguration<>
, который является интерфейсом конфигурации пагинации.
c#
markdown
2. Добавление конфигурации
В соответствующем пользовательском маршруте добавьте конфигурацию [XXXXXXVirtualTableRoute]
public override IPaginationConfiguration<SysUserSalary> CreatePaginationConfiguration()
{
return new SysUserSalaryPaginationConfiguration();
}
builder.PaginationSequence(o => o.Id)
конфигурирует последовательность таблиц при использовании поля Id в качестве поля для сортировки. Правила сравнения маршрутов устанавливаются с помощью UseRouteCompare
, где string
представляет хвост таблицы или имя источника данных. Если, например, текущая страница пагинации состоит из трех таблиц (table1, table2, table3), и если Id не был конфигурирован, то потребуется запрос к каждой из трех таблиц и последующее объединение результатов. Если же Id был конфигурирован, и текущий SQL-запрос использует Id в качестве поля для сортировки, то запрос будет направлен только к первой таблице (table1). Если количество записей в table1 превышает количество записей, которые нужно пропустить, то время выполнения запроса значительно сокращается.Например, если table1 содержит 100 записей, table2 — 200, table3 — 300, и нужно пропустить 90 записей, чтобы получить 10 записей, то время выполнения запроса сокращается с O(100) до O(10).UseQueryMatch
? Это правило, которое определяет, какие поля должны быть использованы для сортировки. Это могут быть поля текущего класса или просто поля с одинаковым именем. Например, если используется select new {}
для создания анонимного объекта, то поля могут иметь разные типы. PrimaryMatch
указывает, что требуется только первое поле для сортировки.
orderby условие должно соответствовать, `UseAppendIfOrderNone` указывает, нужно ли добавлять сортировку по этому полю в случае отсутствия соответствующего условия сортировки, что гарантирует оптимальное производительство сортировки.
3) builder.ConfigReverseShardingPage
указывает, нужно ли включать обратную сортировку, так как прямая сортировка при большом значении skip
может привести к необходимости пропускать слишком много данных, особенно на последних страницах. Если включить обратную сортировку, то последние страницы будут представлять собой обратную сортировку первых страниц. Первый параметр указывает коэффициент пропуска, то есть значение skip
должно быть больше общего количества записей (total) умноженного на этот коэффициент (0-1). Второй параметр указывает минимальное количество записей (total), которое должно быть больше 500 для включения обратной сортировки, и приоритет обратной сортировки ниже, чем приоритет прямой сортировки.var shardingPageResultAsync = await _defaultTableDbContext.Set<SysUserMod>().OrderBy(o => o.Age).ToShardingPageAsync(pageIndex, pageSize);
```### Внимание: если вы сортируете по времени, рекомендуется включить сортировку по времени независимо от направления. Если вы используете модульное или пользовательское распределение по таблицам, рекомендуется использовать Id для прямой сортировки и добавить обратную сортировку для оптимизации производительности. Если entity поддерживает распределение по таблицам и базам данных, и оба маршрута поддерживают сортировку по одному и тому же полю, приоритет будет следующим: сначала распределение по базам данных, затем по таблицам.
## Кэширование выражений
Кэширование выражений может быть включено через маршрутизацию для кэширования выражений для отдельного tail. Поддерживает операторы =, >, >=, <, <=, equal.
```c#
public class OrderCreateTimeVirtualTableRoute : AbstractSimpleShardingMonthKeyDateTimeVirtualTableRoute<Order>
{
// включение кэширования выражений
public override bool EnableRouteParseCompileCache => true;
}
Для кэширования выражений можно переопределить методы родительского класса для реализации собственной логики, или реализовать методы для нескольких tail выражений AbstractShardingRouteParseCompileCacheVirtualTableRoute
, AbstractShardingRouteParseCompileCacheVirtualDataSourceRoute
.
public virtual Func<string, bool> CachingCompile(Expression<Func<string, bool>> parseWhere)
{
if (EnableRouteParseCompileCache)
{
var doCachingCompile = DoCachingCompile(parseWhere);
if (doCachingCompile != null)
return doCachingCompile;
doCachingCompile = CustomerCachingCompile(parseWhere);
if (doCachingCompile != null)
``` return doCachingCompile;
}
return parseWhere.Compile();
}
/// <summary>
/// Системное по умолчанию постоянное кэширование одного выражения
/// </summary>
/// <param name="parseWhere"></param>
/// <returns>Если возвращается null, то вызывается метод <see cref="CustomerCachingCompile"/>, если он возвращает null, то вызывается метод <see cref="Compile"/></returns>
protected virtual Func<string, bool> DoCachingCompile(Expression<Func<string, bool>> parseWhere)
{
var shouldCache = ShouldCache(parseWhere);
if (shouldCache)
return _routeCompileCaches.GetOrAdd(parseWhere, key => parseWhere.Compile());
return null;
}
protected virtual Func<string, bool> CustomerCachingCompile(Expression<Func<string, bool>> parseWhere)
{
return null;
}
```Включение кэширования выражений может улучшить производительность маршрутизации, уменьшив время компиляции с 0.14 мс до 0.013 мс, что составляет приблизительно 10 раз быстрее. # Важные замечания
При использовании данного фреймворка обратите внимание на два момента: если ваш `ShardingDbContext` переопределил следующие службы, они могут не работать. Если вы хотите использовать их, вам потребуется самостоятельно переопределить расширение [см. пример](https://github.com/xuejmnet/sharding-core/blob/main/src/ShardingCore/DIExtension.cs)
1. `ShardingDbContext`
```c#
return optionsBuilder.UseShardingWrapMark()
.ReplaceService<IDbSetSource, ShardingDbSetSource>()
.ReplaceService<IQueryCompiler, ShardingQueryCompiler>()
.ReplaceService<IDbContextTransactionManager, ShardingRelationalTransactionManager<TShardingDbContext>>()
.ReplaceService<IRelationalTransactionFactory, ShardingRelationalTransactionFactory<TShardingDbContext>>();
DefaultDbContext
return optionsBuilder.ReplaceService<IModelCacheKeyFactory, ShardingModelCacheKeyFactory>()
.ReplaceService<IModelCustomizer, ShardingModelCustomizer<TShardingDbContext>>();
В настоящее время фреймворк использует AppDomain.CurrentDomain.GetAssemblies();
, что может привести к тому, что некоторые сборки не будут загружены, поэтому рекомендуется загружать необходимые DLL на уровне API.
При использовании следует учитывать:
Наследуют ли сущности для разделенных таблиц интерфейс IShardingTable
?
Имеют ли сущности для разделенных таблиц поле ShardingKey
?
Наследуют ли сущности для разделенных источников данных интерфейс IShardingDataSource
?- Имеют ли сущности для разделённых источников данных поле ShardingDataSourceKey
?
Наследуют ли сущности виртуальный маршрут?
Был ли виртуальный маршрут добавлен в startup?
Был ли метод bootstrapper.start()
добавлен в startup?```c#
// Поддержка окончательной модификации
var sresult = _defaultTableDbContext.Set().ToList();
var sysUserMod98 = result.FirstOrDefault(o => o.Id == "98");
sysUserMod98.Name = "name_update" + new Random().Next(1, 99) + "_98";
await _defaultTableDbContext.SaveChangesAsync();
-- Лог информации Выполненная DbCommand (1ms) [Параметры=[@p1='?' (Размер = 128), @p0='?' (Размер = 128)], CommandType='Text', CommandTimeout='30'] SET NOCOUNT ON; UPDATE [SysUserMod_02] SET [Name] = @p0 WHERE [Id] = @p1; SELECT @@ROWCOUNT;
# План
- [Предоставить официальный сайт, если проект будет успешным]
- [Разработать более полную документацию]
- [Переформатировать для поддержки других ORM .NET]
# Заключение
Этот фреймворк был создан, опираясь на идеи большинства компонентов для разделенных таблиц. В настоящее время все предоставляемые интерфейсы реализованы и поддерживают запросы к нескольким таблицам. Для запросов на основе пагинации фреймворк использует потоковое чтение, чтобы избежать переполнения памяти при пропуске больших объемов данных. В настоящее время это библиотека находится на ранней стадии разработки и имеет множество недоработок, поэтому просим прощения за возможные недостатки. Если вам понравился проект, пожалуйста, оставьте звезды.
Этот документ был создан ночью, и я надеюсь, что он будет полезен и привлечет больше внимания. Также я надеюсь, что он позволит людям общаться и обмениваться идеями.Этот фреймворк был создан с использованием отличного кода и идей из различных открытых экосистем .NET. Я надеюсь, что этот фреймворк сможет внести свой вклад в развитие экосистемы .NET. Я буду поддерживать этот проект на протяжении долгого времени. Если у вас есть предложения или идеи, пожалуйста, свяжитесь со мной по следующим контактам. Добро пожаловать к оставлению звезд!
# Донат
<img src="./imgs/zfb.jpg" title="JetBrains" width=200 />
<img src="./imgs/wx.jpg" title="JetBrains" width=222 />
[Блог](https://www.cnblogs.com/xuejiaming)
QQ-группа: 771630778
Личный QQ: 326308290 (добро пожаловать к технической поддержке и предоставлению ценных замечаний)
Личная почта: 326308290@qq.com
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Комментарии ( 0 )