namespace SharedDATA.Api { using System; using System.Collections.Generic; using System.Data; using System.Linq; using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Transactions; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; /// /// Represents the default implementation of the and interface. /// /// The type of the db context. public class UnitOfWork : IRepositoryFactory, IUnitOfWork, IUnitOfWork where TContext : DbContext { private readonly TContext _context; private bool disposed = false; private Dictionary repositories; /// /// Initializes a new instance of the class. /// /// The context. public UnitOfWork(TContext context) { _context = context ?? throw new ArgumentNullException(nameof(context)); } /// /// Gets the db context. /// /// The instance of type . public TContext DbContext => _context; /// /// Changes the database name. This require the databases in the same machine. NOTE: This only work for MySQL right now. /// /// The database name. /// /// This only been used for supporting multiple databases in the same model. This require the databases in the same machine. /// public void ChangeDatabase(string database) { var connection = _context.Database.GetDbConnection(); if (connection.State.HasFlag(ConnectionState.Open)) { connection.ChangeDatabase(database); } else { var connectionString = Regex.Replace(connection.ConnectionString.Replace(" ", ""), @"(?<=[Dd]atabase=)\w+(?=;)", database, RegexOptions.Singleline); connection.ConnectionString = connectionString; } // Following code only working for mysql. var items = _context.Model.GetEntityTypes(); foreach (var item in items) { if (item is IConventionEntityType entityType) { entityType.SetSchema(database); } } } /// /// Gets the db context. /// /// public TDbContext GetDbContext() where TDbContext : DbContext { if (_context == null) return null; return _context as TDbContext; } /// /// Gets the specified repository for the . /// /// True if providing custom repositry /// The type of the entity. /// An instance of type inherited from interface. public IRepository GetRepository(bool hasCustomRepository = false) where TEntity : class { if (repositories == null) { repositories = new Dictionary(); } // what's the best way to support custom reposity? if (hasCustomRepository) { var customRepo = _context.GetService>(); if (customRepo != null) { return customRepo; } } var type = typeof(TEntity); if (!repositories.ContainsKey(type)) { repositories[type] = new Repository(_context); } return (IRepository)repositories[type]; } /// /// Executes the specified raw SQL command. /// /// The raw SQL. /// The parameters. /// The number of state entities written to database. public int ExecuteSqlCommand(string sql, params object[] parameters) => _context.Database.ExecuteSqlRaw(sql, parameters); /// /// Uses raw SQL queries to fetch the specified data. /// /// The type of the entity. /// The raw SQL. /// The parameters. /// An that contains elements that satisfy the condition specified by raw SQL. public IQueryable FromSql(string sql, params object[] parameters) where TEntity : class => _context.Set().FromSqlRaw(sql, parameters); /// /// Saves all changes made in this context to the database. /// /// True if save changes ensure auto record the change history. /// The number of state entries written to the database. public int SaveChanges(bool ensureAutoHistory = false) { if (ensureAutoHistory) { _context.EnsureAutoHistory(); } return _context.SaveChanges(); } /// /// Asynchronously saves all changes made in this unit of work to the database. /// /// True if save changes ensure auto record the change history. /// A that represents the asynchronous save operation. The task result contains the number of state entities written to database. public async Task SaveChangesAsync(bool ensureAutoHistory = false) { if (ensureAutoHistory) { _context.EnsureAutoHistory(); } return await _context.SaveChangesAsync(); } /// /// Saves all changes made in this context to the database with distributed transaction. /// /// True if save changes ensure auto record the change history. /// An optional array. /// A that represents the asynchronous save operation. The task result contains the number of state entities written to database. public async Task SaveChangesAsync(bool ensureAutoHistory = false, params IUnitOfWork[] unitOfWorks) { using (var ts = new TransactionScope()) { var count = 0; foreach (var unitOfWork in unitOfWorks) { count += await unitOfWork.SaveChangesAsync(ensureAutoHistory); } count += await SaveChangesAsync(ensureAutoHistory); ts.Complete(); return count; } } /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// /// The disposing. protected virtual void Dispose(bool disposing) { if (!disposed) { if (disposing) { // clear repositories if (repositories != null) { repositories.Clear(); } // dispose the db context. _context.Dispose(); } } disposed = true; } public void TrackGraph(object rootEntity, Action callback) { _context.ChangeTracker.TrackGraph(rootEntity, callback); } } }