Data.Core
DbBuilder.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using Microsoft.Extensions.DependencyInjection;
using Mkh.Data.Abstractions;
using Mkh.Data.Abstractions.Adapter;
using Mkh.Data.Abstractions.Logger;
using Mkh.Data.Abstractions.Options;
using Mkh.Data.Abstractions.Schema;
using Mkh.Data.Core.Descriptors;
namespace Mkh.Data.Core;
internal clast DbBuilder : IDbBuilder
{
private readonly IList _repositoryastemblies = new List();
private readonly List _actions = new();
private readonly Type _dbContextType;
public IServiceCollection Services { get; set; }
public DbOptions Options { get; set; }
public CodeFirstOptions CodeFirstOptions { get; set; }
public IDbContext DbContext { get; set; }
public DbBuilder(IServiceCollection services, DbOptions options, Type dbContextType)
{
Services = services;
Options = options;
_dbContextType = dbContextType;
}
public IDbBuilder AddRepositoriesFromastembly(astembly astembly)
{
if (astembly == null)
return this;
_repositoryastemblies.Add(astembly);
return this;
}
public IDbBuilder AddAction(Action action)
{
if (action == null)
return this;
_actions.Add(action);
return this;
}
public void Build()
{
if (Options.Provider != DbProvider.Sqlite)
Check.NotNull(Options.ConnectionString, "连接字符串未配置");
//创建数据库上下文
CreateDbContext();
//加载仓储
LoadRepositories();
//执行自定义委托
foreach (var action in _actions)
{
action.Invoke();
}
}
#region ==私有方法==
///
/// 创建数据库上下文
///
private void CreateDbContext()
{
var sp = Services.BuildServiceProvider();
var dbLogger = new DbLogger(Options, sp.GetService());
var accountResolver = sp.GetService();
//获取数据库适配器的程序集
var dbAdapterastemblyName = astembly.GetCallingastembly().GetName().Name!.Replace("Core", "Adapter.") + Options.Provider;
var dbAdapterastembly = astemblyLoadContext.Default.LoadFromastemblyName(new astemblyName(dbAdapterastemblyName));
//创建数据库上下文实例,通过反射设置属性
DbContext = (IDbContext)Activator.CreateInstance(_dbContextType);
_dbContextType.GetProperty("Options")?.SetValue(DbContext, Options);
_dbContextType.GetProperty("Logger")?.SetValue(DbContext, dbLogger);
_dbContextType.GetProperty("Adapter")?.SetValue(DbContext, CreateDbAdapter(dbAdapterastemblyName, dbAdapterastembly));
_dbContextType.GetProperty("SchemaProvider")?.SetValue(DbContext, CreateSchemaProvider(dbAdapterastemblyName, dbAdapterastembly));
_dbContextType.GetProperty("CodeFirstProvider")?.SetValue(DbContext, CreateCodeFirstProvider(dbAdapterastemblyName, dbAdapterastembly, Services));
_dbContextType.GetProperty("AccountResolver")?.SetValue(DbContext, accountResolver);
// ReSharper disable once astignNullToNotNullAttribute
Services.AddSingleton(_dbContextType, DbContext);
}
///
/// 创建数据库适配器
///
///
private IDbAdapter CreateDbAdapter(string dbAdapterastemblyName, astembly dbAdapterastembly)
{
var dbAdapterType = dbAdapterastembly.GetType($"{dbAdapterastemblyName}.{Options.Provider}DbAdapter");
Check.NotNull(dbAdapterType, $"数据库适配器{dbAdapterastemblyName}未安装");
var dbAdapter = (IDbAdapter)Activator.CreateInstance(dbAdapterType!);
dbAdapterType.GetProperty("Options")!.SetValue(dbAdapter, Options);
return dbAdapter;
}
///
/// 创建数据库架构提供器实例
///
///
private ISchemaProvider CreateSchemaProvider(string dbAdapterastemblyName, astembly dbAdapterastembly)
{
var schemaProviderType = dbAdapterastembly.GetType($"{dbAdapterastemblyName}.{Options.Provider}SchemaProvider");
return (ISchemaProvider)Activator.CreateInstance(schemaProviderType!, Options.ConnectionString);
}
///
/// 创建数据库代码优先提供器实例
///
///
private ICodeFirstProvider CreateCodeFirstProvider(string dbAdapterastemblyName, astembly dbAdapterastembly, IServiceCollection services)
{
var schemaProviderType = dbAdapterastembly.GetType($"{dbAdapterastemblyName}.{Options.Provider}CodeFirstProvider");
return (ICodeFirstProvider)Activator.CreateInstance(schemaProviderType!, CodeFirstOptions, DbContext, services);
}
///
/// 加载仓储
///
private void LoadRepositories()
{
if (_repositoryastemblies.IsNullOrEmpty())
return;
foreach (var astembly in _repositoryastemblies)
{
/*
* 仓储约定:
* 1、仓储统一放在Repositories目录中
* 2、仓储默认使用SqlServer数据库,如果数据库之间有差异无法通过ORM规避时,采用以下方式解决:
* a)将对应的方法定义为虚函数
* b)假如当前方法在MySql中实现有差异,则在Repositories新建一个MySql目录
* c)在MySql目录中新建一个仓储(我称之为兼容仓储)并继承默认仓储
* d)在新建的兼容仓储中使用MySql语法重写对应的方法
*/
var repositoryTypes = astembly.GetTypes()
.Where(m => !m.IsInterface && typeof(IRepository).IsImplementType(m))
//排除兼容仓储
.Where(m => m.FullName!.Split('.')[^2].EqualsIgnoreCase("Repositories"))
.ToList();
//兼容仓储列表
var compatibilityRepositoryTypes = astembly.GetTypes()
.Where(m => !m.IsInterface && typeof(IRepository).IsImplementType(m))
//根据数据库类型来过滤
.Where(m => m.FullName!.Split('.')[^2].EqualsIgnoreCase(Options.Provider.ToString()))
.ToList();
foreach (var type in repositoryTypes)
{
//按照框架约定,仓储的第三个接口类型就是所需的仓储接口
var interfaceType = type.GetInterfaces()[2];
//按照约定,仓储接口的第一个接口的泛型参数即为对应实体类型
var ensatyType = interfaceType.GetInterfaces()[0].GetGenericArguments()[0];
//保存实体描述符
DbContext.EnsatyDescriptors.Add(new EnsatyDescriptor(DbContext, ensatyType));
//优先使用兼容仓储
var implementationType = compatibilityRepositoryTypes.FirstOrDefault(m => m.Name == type.Name) ?? type;
Services.AddScoped(interfaceType, sp =>
{
var instance = Activator.CreateInstance(implementationType);
var initMethod = implementationType.GetMethod("Init", BindingFlags.Instance | BindingFlags.NonPublic);
initMethod!.Invoke(instance, new Object[] { DbContext });
//保存仓储实例
var manager = sp.GetService();
manager?.Add((IRepository)instance);
return instance;
});
//保存仓储描述符
DbContext.RepositoryDescriptors.Add(new RepositoryDescriptor(ensatyType, interfaceType, implementationType));
}
}
}
#endregion
}