SHOW VARIABLES LIKE '%version%';
innodb_version - 8.0.11
version - 8.0.11
innodb_version - 5.7.30
version - 5.7.30-0ubuntu0.18.04.1
internal class Const
{
internal const string CHARSET = "utf8mb4";
internal const string COLLATE = "utf8mb4_unicode_ci";
}
using System;
namespace Domain.Entities.Events
{
/// <summary>
/// Событие системы.
/// </summary>
public class SystemEvent
{
/// <summary>
/// Уникальный идентификатор события.
/// </summary>
public long SystemEventId { get; set; }
/// <summary>
/// Метка времени в формате UTC, когда произошло событие.
/// </summary>
public DateTimeOffset EventTime { get; set; }
}
}
using Microsoft.EntityFrameworkCore;
using MySql.Data.EntityFrameworkCore.Extensions;
namespace Infrastructure.Data.Contexts
{
/// <summary>
/// Контекст для работы с БД событий.
/// </summary>
public class SystemEventContext : DbContextEx
{
public DbSet<SystemEvent> Events { get; set; }
public SystemEventContext(DbContextOptions<SystemEventContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder builder)
{
const string TIMESTAMP_COLUMN_TYPE = "timestamp(3)";
builder.Entity<SystemEvent>()
.ForMySQLHasCharset(Const.CHARSET)
.ForMySQLHasCollation(Const.COLLATE);
builder.Entity<SystemEvent>()
.HasKey(x => new { x.SystemEventId });
builder.Entity<SystemEvent>()
.HasIndex(x => new { x.EventTime });
builder.Entity<SystemEvent>()
.Property(x => x.EventTime)
.HasColumnType(TIMESTAMP_COLUMN_TYPE);
base.OnModelCreating(builder);
// Это необходимо для MySQL
// https://github.com/aspnet/EntityFrameworkCore/issues/14051#issuecomment-450469445
SetBoolToInt32Converter(builder);
}
}
}
using System.Data;
using Domain.ValueConversion;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Storage;
namespace Infrastructure.Data.Contexts
{
public class DbContextEx : DbContext
{
private IDbContextTransaction _currentTransaction;
public DbContextEx(DbContextOptions options)
: base(options)
{
}
protected static void SetBoolToInt32Converter(ModelBuilder builder)
{
foreach (IMutableEntityType entityType in builder.Model.GetEntityTypes())
{
foreach (IMutableProperty property in entityType.GetProperties())
{
if (property.ClrType == typeof(bool))
{
property.SetValueConverter(new BoolToIntConverter());
}
}
}
}
#region Transaction Handling
public void BeginTransaction(IsolationLevel isolationLevel = IsolationLevel.ReadCommitted)
{
if (_currentTransaction != null)
{
return;
}
if (!Database.IsInMemory())
{
_currentTransaction = Database.BeginTransaction(isolationLevel);
}
}
public void CommitTransaction()
{
try
{
_currentTransaction?.Commit();
}
catch
{
RollbackTransaction();
throw;
}
finally
{
if (_currentTransaction != null)
{
_currentTransaction.Dispose();
_currentTransaction = null;
}
}
}
public void RollbackTransaction()
{
try
{
_currentTransaction?.Rollback();
}
finally
{
if (_currentTransaction != null)
{
_currentTransaction.Dispose();
_currentTransaction = null;
}
}
}
#endregion
}
}
using System;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace Domain.ValueConversion
{
public static class EntityFrameworkCoreModelBuilderExtensions
{
public static void AddDateTimeOffsetConverter(this ModelBuilder builder)
{
// SQLite does not support DateTimeOffset
foreach (var property in builder.Model.GetEntityTypes()
.SelectMany(t => t.GetProperties())
.Where(p => p.ClrType == typeof(DateTimeOffset)))
{
property.SetValueConverter(
new ValueConverter<DateTimeOffset, DateTime>(
convertToProviderExpression: dateTimeOffset => dateTimeOffset.UtcDateTime,
convertFromProviderExpression: dateTime => new DateTimeOffset(dateTime)
));
}
foreach (var property in builder.Model.GetEntityTypes()
.SelectMany(t => t.GetProperties())
.Where(p => p.ClrType == typeof(DateTimeOffset?)))
{
property.SetValueConverter(
new ValueConverter<DateTimeOffset?, DateTime>(
convertToProviderExpression: dateTimeOffset => dateTimeOffset.Value.UtcDateTime,
convertFromProviderExpression: dateTime => new DateTimeOffset(dateTime)
));
}
}
public static void AddDateTimeUtcKindConverter(this ModelBuilder builder)
{
// If you store a DateTime object to the DB with a DateTimeKind of either `Utc` or `Local`,
// when you read that record back from the DB you'll get a DateTime object whose kind is `Unspecified`.
// Here is a fix for it!
var dateTimeConverter = new ValueConverter<DateTime, DateTime>(
v => v.Kind == DateTimeKind.Utc ? v : v.ToUniversalTime(),
v => DateTime.SpecifyKind(v, DateTimeKind.Utc));
var nullableDateTimeConverter = new ValueConverter<DateTime?, DateTime?>(
v => !v.HasValue ? v : (v.Value.Kind == DateTimeKind.Utc ? v : v.Value.ToUniversalTime()),
v => v.HasValue ? DateTime.SpecifyKind(v.Value, DateTimeKind.Utc) : v);
foreach (var property in builder.Model.GetEntityTypes()
.SelectMany(t => t.GetProperties()))
{
if (property.ClrType == typeof(DateTime))
{
property.SetValueConverter(dateTimeConverter);
}
if (property.ClrType == typeof(DateTime?))
{
property.SetValueConverter(nullableDateTimeConverter);
}
}
}
}
}
using Microsoft.EntityFrameworkCore;
using MySql.Data.EntityFrameworkCore.Extensions;
namespace Infrastructure.Data.Contexts
{
/// <summary>
/// Контекст для работы с БД событий.
/// </summary>
public class SystemEventContext : DbContextEx
{
public DbSet<SystemEvent> Events { get; set; }
public SystemEventContext(DbContextOptions<SystemEventContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder builder)
{
const string TIMESTAMP_COLUMN_TYPE = "timestamp(3)";
// ...
base.OnModelCreating(builder);
// ...
builder.AddDateTimeOffsetConverter();
builder.AddDateTimeUtcKindConverter();
}
}
}