высокопроизводительное легковесное решение для разбиения таблиц и баз данных в EF Core, поддержка разделения на чтение и запись.
shardingcore lastversion.efcoreversion.x.x
первая версия — версия shardingcore
вторая версия — версия EF Core
остальные версии используют последнюю версию
EF Core 9 использует ShardingCore 7.9.x.x
EF Core 8 использует ShardingCore 7.8.x.x
EF Core 7 использует ShardingCore 7.7.x.x
EF Core 6 использует ShardingCore 7.6.x.x
EF Core 5 использует ShardingCore 7.5.x.x- EF Core 3 использует ShardingCore 7.3.x.x
EF Core 2 использует ShardingCore 7.2.x.x
Выпуск | EF Core | .NET | .NET (Core) |
---|---|---|---|
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+ |
Используйте условное компиляцию с NetFramework
Релиз | 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 поддерживает
Запрос сущности Order
/// Таблица заказов
/// </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
{
Неоплачен = 1,
Оплачивается = 2,
Оплачен = 3,
ОплатаНесуспешна = 4
}
Создайте MyDbContext
, расширяющий AbstractShardingDbContext
,
затем этот DbContext поддерживает шардинг базы данных, если вы хотите поддержать
шардинг таблиц (например, order_202101, order_202102, order_202103...), вам нужно реализовать IShardingTableDbContext
{
public MyDbContext(DbContextOptions<MyDbContext> options) : base(options)
{
}
```markdown
```csharp
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>```markdown
public void ConfigureServices(IServiceCollection services)
{
//конфигурация шардинга
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();
// другие настройки....
}
[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);
}
}
Тестирование
// * Summary *
BenchmarkDotNet=v0.13.1, OS=Windows Yöntem | N | Среднее | Ошибка | Стандартное отклонение |
---|---|---|---|---|
NoShardingIndexFirstOrDefaultAsync | 10 | 1.512 ms | 0.0071 ms | 0.0063 ms |
ShardingIndexFirstOrDefaultAsync | 10 | 1.567 ms | 0.0127 ms | 0.0113 ms |
При тестировании производительности запросов к нешардированной таблице, можно заметить, что разница в 10 запросах составляет 0.05 мс, что эквивалентно потере около 5 микросекунд или 0.005 мс на одном запросе, что составляет 3% от общего времени выполнения.Заключение: запросы с использованием efcore и ShardingCore для нешардированных объектов имеют производительность на уровне 97% от оригинальной, что свидетельствует о высокой производительности.
// * Summary *
BenchmarkDotNet=v0.13.1, OS=Windows 10.0.18363.1500 (1909/November2019Update/19H2) AMD Ryzen 9 3900X, 1 CPU, 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 мс |
// * Summary *
BenchmarkDotNet=v0.13.1, OS=Windows 10.0.18363.1500 (1909/November2019Update/19H2) AMD Ryzen 9 3900X, 1 CPU, 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 мс |Измерение времени выполнения запросов показывает, что разница между первыми двумя запросами составляет около 0,04 миллисекунды. В случае с SQL Server, данные показывают, что разница между использованием и неиспользованием шардинга минимальна, что указывает на то, что база данных не является ограничивающим фактором при работе с набором данных из 770 000 строк. В случае с MySQL, если запросы включают полное сканирование таблицы без индексов, то разница в производительности между использованием и неиспользованием шардинга может быть значительной. В тестах эта разница составляет около 5–6 раз.
join, group by, max, count, min, avg, sum
и другие. Использование этого пакета очень простое и сводится к расширению IQueryable. Чтобы обеспечить чистоту и независимость этого пакета, автоматическое создание таблиц можно реализовать с помощью задачи планировщика. Для этого пакет предоставляет IShardingTableCreator как зависимость для создания таблиц.Если вам это нужно, вы можете обратиться к автоматическому созданию таблиц по дате для примера, который предназначен для динамического добавления разделения баз данных.## Основные понятияНесколько основных концепций этого пакета:### Концепция шардинга по источникам данныхjoin,group by,max,count,min,avg,sum
произведение декартовых множеств
что приводит к взрывному росту соединений, в будущем будет добавлена конфигурация для решения этой проблемы<PackageReference Include="ShardingCore" Version="5.LastVersion" />
или
<PackageReference Include="ShardingCore" Version="3.LastVersion" />
или
<PackageReference Include="ShardingCore" Version="2.LastVersion" />
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>
{
//2 хвост длиной:00,01,02......99
//3 хэш % 3: [0,1,2]
public SysUserModVirtualTableRoute() : base(2, 3)
{
}
public override void Configure(EntityMetadataTableBuilder<SysUserMod> builder)
{
builder.ShardingProperty(o => o.Id);
}
}
Если вы используете шардинг, необходимо создать контекст, наследуя от IShardingTableDbContext
интерфейса,
необходимо реализовать IShardingDbContext
, по умолчанию предоставляется AbstractShardingDbContext
//DefaultShardingDbContext — это фактический исполняемый dbcontext
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; }
}
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"));
//добавить поддержку шардинг контекста для жизненного цикла
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;
``````markdown
## Распределение баз данных
```c#
```Для примера возьмем модуль пользователя. Рекомендуется использовать [fluent api](https://docs.microsoft.com/en-us/ef/core/modeling/) для конфигурации entity.
Объекты баз данных `IShardingDataSource` должны наследовать этот интерфейс.
Поле распределения баз данных `ShardingDataSourceKey` должно использовать этот атрибут.
```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>
Абстракция или реализация системного по умолчанию виртуального маршрута
Фреймворк по умолчанию предоставляет несколько простых маршрутов по умолчанию
``````markdown
Если вы используете разделение баз данных, вам не потребуется интерфейс `IShardingTableDbContext`.
Для создания контекста баз данных с разделением таблиц необходимо наследовать от `AbstractShardingDbContext`.
```c#
// DefaultShardingDbContext является фактическим контекстом баз данных
public class DefaultShardingDbContext : AbstractShardingDbContext
{
public DefaultShardingDbContext(DbContextOptions<DefaultShardingDbContext> options) : base(options)
{
}
}
``````c#
}
Startup.cs
файл содержит метод ConfigureServices(IServiceCollection services)
:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
// добавляем поддержку 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=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)
:
var shardingBootstrapper = app.ApplicationServices.GetRequiredService<IShardingBootstrapper>();
shardingBootstrapper.Start();
Использование:
private readonly 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 = $"имя_{id}",
AgeGroup = Math.Abs(id % 10)
});
}
``````markdown
_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 | по времени (timestamp) | yyyyMMdd | `>, >=, <, <=, =, contains`
AbstractSimpleShardingWeekKeyDateTimeVirtualTableRoute | по времени | yyyyMMdd_dd | `>, >=, <, <=, =, contains`
AbstractSimpleShardingWeekKeyLongVirtualTableRoute | по времени (timestamp) | yyyyMMdd_dd | `>, >=, <, <=, =, contains`
AbstractSimpleShardingMonthKeyDateTimeVirtualTableRoute | по времени | yyyyMM | `>, >=, <, <=, =, contains`
AbstractSimpleShardingMonthKeyLongVirtualTableRoute | по времени (timestamp) | yyyyMM | `>, >=, <, <=, =, contains`
AbstractSimpleShardingYearKeyDateTimeVirtualTableRoute | по времени | yyyy | `>, >=, <, <=, =, contains`
AbstractSimpleShardingYearKeyLongVirtualTableRoute | по времени (timestamp) | yyyy | `>, >=, <, <=, =, contains`
Примечание: `contains` указывает на `o => ids.contains(o.shardingkey)`
Примечание: Использование по умолчанию правила маршрутизации по времени разделения таблиц будет требовать переопределения метода `GetBeginTime`. Этот метод должен использовать статическое значение, например: `new DateTime(2021, 1, 1)`, динамическое значение, например `DateTime.Now`, использовать нельзя, так как при каждом перезапуске будет вызван этот метод, что приведет к несоответствиям.# Продвинутый
## code-first
В настоящее время `sharding-core` поддерживает code-first, подробная реализация можно посмотреть в [Migrations](https://github.com/xuejmnet/sharding-core/tree/main/samples/Sample.Migrations/readme.md)
## Автоматическое отслеживание
По умолчанию `shardingcore` не поддерживает автоматическое отслеживание, и его не рекомендуется использовать. Если вам это необходимо, `shardingcore` по умолчанию предоставляет функцию автоматического отслеживания.
Важно отметить следующие моменты:
1. В настоящее время поддерживается только объект с одним первичным ключом.
2. `shardingcore` поддерживает только модели `dbcontext`, анонимные типы запросов не поддерживаются.
3. Одиночные запросы `shardingcore` выполняются напрямую в базе данных, а не в кэше. Если результат запроса уже находится в кэше, он будет возвращен из кэша, а не из базы данных.
4. Операции, такие как `ToList`, выполняют запрос к базе данных и возвращают результат. Если результат уже отслеживается, он будет возвращен из кэша.
5. Поддерживается `first`, `firstordefault`, `last`, `lastordefault`, `single`, `singleordefault`.
Как включить:
```c#
services.AddShardingDbContext<DefaultShardingDbContext>(.......)
.Begin(o => {
o.CreateShardingTableOnStart = true;
o.EnsureCreatedWithOutShardingTable = true;
//поддержка автоматического отслеживания asnotracking astracking QueryTrackingBehavior.TrackAll
o.AutoTrackEntity = true;
})
ctor inject IShardingRouteManager shardingRouteManager
``````markdown
## Автоматическое создание таблиц
[Ссылка](https://github.com/xuejmnet/sharding-core/tree/main/samples/Samples.AutoByDate.SqlServer)
## Транзакции
1. По умолчанию поддерживается транзакция с помощью `SaveChangesAsync`
```c#
await _defaultShardingDbContext.SaveChangesAsync();
using (var tran = _defaultTableDbContext.DataBase.BeginTransaction())
{
........
_defaultTableDbContext.SaveChanges();
tran.Commit();
}
Батч-операции разделяют соответствующий DbContext
и данные, позволяя пользователю самостоятельно выбрать сторонний фреймворк, например, Z.EntityFramework.Plus.EFCore
для выполнения батч-операций или EFCore.BulkExtensions
, поддерживающий все сторонние батч-фреймворки.
var list = new List<SysUserMod>();
var dbContexts = _defaultTableDbContext.BulkShardingEnumerable(list);
``````csharp
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` или используются два готовых метода.
```c#
// Переключение на только чтение, только запись и только конфигурация для чтения 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 )