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);
}
}
}