Commit 707e93cb authored by 阿星Plus's avatar 阿星Plus

EF Done.

parent 3fd5c627
using Microsoft.EntityFrameworkCore;
namespace Plus.EntityFramework.Configuration
{
/// <summary>
/// IPlusDbContextConfigurer
/// </summary>
/// <typeparam name="TDbContext"></typeparam>
public interface IPlusDbContextConfigurer<TDbContext>
where TDbContext : DbContext
{
void Configure(PlusDbContextConfiguration<TDbContext> configuration);
}
}
\ No newline at end of file
using Microsoft.EntityFrameworkCore;
using System;
namespace Plus.EntityFramework.Configuration
{
/// <summary>
/// IPlusEfCoreConfiguration
/// </summary>
public interface IPlusEfCoreConfiguration
{
void AddDbContext<TDbContext>(Action<PlusDbContextConfiguration<TDbContext>> action) where TDbContext : DbContext;
}
}
\ No newline at end of file
using Microsoft.EntityFrameworkCore;
using System.Data.Common;
namespace Plus.EntityFramework.Configuration
{
/// <summary>
/// PlusDbContextConfiguration
/// </summary>
/// <typeparam name="TDbContext"></typeparam>
public class PlusDbContextConfiguration<TDbContext>
where TDbContext : DbContext
{
public string ConnectionString { get; internal set; }
public DbConnection ExistingConnection { get; internal set; }
public DbContextOptionsBuilder<TDbContext> DbContextOptions { get; }
public PlusDbContextConfiguration(string connectionString, DbConnection existingConnection)
{
ConnectionString = connectionString;
ExistingConnection = existingConnection;
DbContextOptions = new DbContextOptionsBuilder<TDbContext>();
}
}
}
\ No newline at end of file
using Microsoft.EntityFrameworkCore;
using System;
namespace Plus.EntityFramework.Configuration
{
/// <summary>
/// PlusDbContextConfigurerAction
/// </summary>
/// <typeparam name="TDbContext"></typeparam>
public class PlusDbContextConfigurerAction<TDbContext> : IPlusDbContextConfigurer<TDbContext>
where TDbContext : DbContext
{
public Action<PlusDbContextConfiguration<TDbContext>> Action { get; set; }
public PlusDbContextConfigurerAction(Action<PlusDbContextConfiguration<TDbContext>> action)
{
Action = action;
}
public void Configure(PlusDbContextConfiguration<TDbContext> configuration)
{
Action(configuration);
}
}
}
\ No newline at end of file
using Castle.MicroKernel.Registration;
using Microsoft.EntityFrameworkCore;
using Plus.Dependency;
using System;
namespace Plus.EntityFramework.Configuration
{
/// <summary>
/// PlusEfCoreConfiguration
/// </summary>
public class PlusEfCoreConfiguration : IPlusEfCoreConfiguration
{
private readonly IIocManager _iocManager;
public PlusEfCoreConfiguration(IIocManager iocManager)
{
_iocManager = iocManager;
}
public void AddDbContext<TDbContext>(Action<PlusDbContextConfiguration<TDbContext>> action) where TDbContext : DbContext
{
_iocManager.IocContainer.Register(
Component.For<IPlusDbContextConfigurer<TDbContext>>().Instance(
new PlusDbContextConfigurerAction<TDbContext>(action)
).IsDefault()
);
}
}
}
\ No newline at end of file
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Plus.Dependency;
using Plus.EntityFramework.Configuration;
using System;
using System.Data.Common;
using System.Linq;
using System.Reflection;
namespace Plus.EntityFramework
{
/// <summary>
/// DefaultDbContextResolver
/// </summary>
public class DefaultDbContextResolver : IDbContextResolver, ITransientDependency
{
private static readonly MethodInfo CreateOptionsMethod = typeof(DefaultDbContextResolver).GetMethod("CreateOptions", BindingFlags.NonPublic | BindingFlags.Instance);
private readonly IIocResolver _iocResolver;
private readonly IDbContextTypeMatcher _dbContextTypeMatcher;
public DefaultDbContextResolver(
IIocResolver iocResolver,
IDbContextTypeMatcher dbContextTypeMatcher)
{
_iocResolver = iocResolver;
_dbContextTypeMatcher = dbContextTypeMatcher;
}
public TDbContext Resolve<TDbContext>(string connectionString, DbConnection existingConnection) where TDbContext : DbContext
{
var dbContextType = typeof(TDbContext);
Type concreteType = null;
var isAbstractDbContext = dbContextType.GetTypeInfo().IsAbstract;
if (isAbstractDbContext)
{
concreteType = _dbContextTypeMatcher.GetConcreteType(dbContextType);
}
try
{
if (isAbstractDbContext)
{
return (TDbContext)_iocResolver.Resolve(concreteType);
}
return _iocResolver.Resolve<TDbContext>(concreteType);
}
catch (Castle.MicroKernel.Resolvers.DependencyResolverException ex)
{
var hasOptions = isAbstractDbContext ? HasOptions(concreteType) : HasOptions(dbContextType);
if (!hasOptions)
{
throw new AggregateException($"The parameter name of {dbContextType.Name}'s constructor must be 'options'", ex);
}
throw;
}
bool HasOptions(Type contextType)
{
return contextType.GetConstructors().Any(ctor =>
{
var parameters = ctor.GetParameters();
return parameters.Length == 1 && parameters.FirstOrDefault()?.Name == "options";
});
}
}
private object CreateOptionsForType(Type dbContextType, string connectionString, DbConnection existingConnection)
{
return CreateOptionsMethod.MakeGenericMethod(dbContextType).Invoke(this, new object[] { connectionString, existingConnection });
}
protected virtual DbContextOptions<TDbContext> CreateOptions<TDbContext>(string connectionString, DbConnection existingConnection) where TDbContext : DbContext
{
if (_iocResolver.IsRegistered<IPlusDbContextConfigurer<TDbContext>>())
{
var configuration = new PlusDbContextConfiguration<TDbContext>(connectionString, existingConnection);
ReplaceServices(configuration);
using (var configurer = _iocResolver.ResolveAsDisposable<IPlusDbContextConfigurer<TDbContext>>())
{
configurer.Object.Configure(configuration);
}
return configuration.DbContextOptions.Options;
}
if (_iocResolver.IsRegistered<DbContextOptions<TDbContext>>())
{
return _iocResolver.Resolve<DbContextOptions<TDbContext>>();
}
throw new PlusException($"Could not resolve DbContextOptions for {typeof(TDbContext).AssemblyQualifiedName}.");
}
protected virtual void ReplaceServices<TDbContext>(PlusDbContextConfiguration<TDbContext> configuration) where TDbContext : DbContext
{
configuration.DbContextOptions.ReplaceService<IEntityMaterializerSource, PlusEntityMaterializerSource>();
}
}
}
\ No newline at end of file
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage;
using Plus.Data;
using Plus.Dependency;
using System;
using System.Data;
using System.Reflection;
namespace Plus.EntityFramework
{
/// <summary>
/// EfCoreActiveTransactionProvider
/// </summary>
public class EfCoreActiveTransactionProvider : IActiveTransactionProvider, ITransientDependency
{
private readonly IIocResolver _iocResolver;
public EfCoreActiveTransactionProvider(IIocResolver iocResolver)
{
_iocResolver = iocResolver;
}
public IDbTransaction GetActiveTransaction(ActiveTransactionProviderArgs args)
{
return GetDbContext(args).Database.CurrentTransaction?.GetDbTransaction();
}
public IDbConnection GetActiveConnection(ActiveTransactionProviderArgs args)
{
return GetDbContext(args).Database.GetDbConnection();
}
private DbContext GetDbContext(ActiveTransactionProviderArgs args)
{
Type dbContextProviderType = typeof(IDbContextProvider<>).MakeGenericType((Type)args["ContextType"]);
using (IDisposableDependencyObjectWrapper dbContextProviderWrapper = _iocResolver.ResolveAsDisposable(dbContextProviderType))
{
MethodInfo method = dbContextProviderWrapper.Object.GetType()
.GetMethod("GetDbContext");
return (DbContext)method.Invoke(dbContextProviderWrapper.Object, new object[0]);
}
}
}
}
\ No newline at end of file
using System;
namespace Plus.EntityFramework
{
/// <summary>
/// EfCoreBasedSecondaryOrmRegistrar
/// </summary>
public class EfCoreBasedSecondaryOrmRegistrar : SecondaryOrmRegistrarBase
{
public override string OrmContextKey { get; } = "EntityFrameworkCore";
public EfCoreBasedSecondaryOrmRegistrar(Type dbContextType, IDbContextEntityFinder dbContextEntityFinder)
: base(dbContextType, dbContextEntityFinder)
{
}
}
}
\ No newline at end of file
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage;
namespace Plus.EntityFramework.Extensions
{
/// <summary>
/// DbContextExtensions
/// </summary>
internal static class DbContextExtensions
{
public static bool HasRelationalTransactionManager(this DbContext dbContext)
{
return dbContext.Database.GetService<IDbContextTransactionManager>() is IRelationalTransactionManager;
}
}
}
\ No newline at end of file
...@@ -4,6 +4,9 @@ using System.Linq; ...@@ -4,6 +4,9 @@ using System.Linq;
namespace Plus.EntityFramework.Extensions namespace Plus.EntityFramework.Extensions
{ {
/// <summary>
/// EntityEntryExtensions
/// </summary>
public static class EntityEntryExtensions public static class EntityEntryExtensions
{ {
/// <summary> /// <summary>
......
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Query;
using System;
using System.Linq;
using System.Linq.Expressions;
namespace Plus.EntityFramework.Extensions
{
/// <summary>
/// QueryableExtensions
/// </summary>
public static class QueryableExtensions
{
public static IQueryable<T> IncludeIf<T>(this IQueryable<T> source, bool condition, string path) where T : class
{
return condition ? EntityFrameworkQueryableExtensions.Include(source, path) : source;
}
public static IQueryable<T> IncludeIf<T, TProperty>(this IQueryable<T> source, bool condition, Expression<Func<T, TProperty>> path) where T : class
{
object result;
if (!condition)
{
result = source;
}
else
{
IQueryable<T> queryable = source.Include(path);
result = queryable;
}
return (IQueryable<T>)result;
}
public static IQueryable<T> IncludeIf<T>(this IQueryable<T> source, bool condition, Func<IQueryable<T>, IIncludableQueryable<T, object>> include) where T : class
{
object result;
if (!condition)
{
result = source;
}
else
{
IQueryable<T> queryable = include(source);
result = queryable;
}
return (IQueryable<T>)result;
}
}
}
\ No newline at end of file
using Plus.Domain.Entities;
using System;
using System.Collections.Generic;
namespace Plus.EntityFramework
{
/// <summary>
/// IDbContextEntityFinder
/// </summary>
public interface IDbContextEntityFinder
{
IEnumerable<EntityTypeInfo> GetEntityTypeInfos(Type dbContextType);
}
}
\ No newline at end of file
using Microsoft.EntityFrameworkCore;
namespace Plus.EntityFramework
{
/// <summary>
/// IDbContextProvider
/// </summary>
/// <typeparam name="TDbContext"></typeparam>
public interface IDbContextProvider<out TDbContext> where TDbContext : DbContext
{
TDbContext GetDbContext();
}
}
\ No newline at end of file
using Microsoft.EntityFrameworkCore;
using System.Data.Common;
namespace Plus.EntityFramework
{
/// <summary>
/// IDbContextResolver
/// </summary>
public interface IDbContextResolver
{
TDbContext Resolve<TDbContext>(string connectionString, DbConnection existingConnection) where TDbContext : DbContext;
}
}
\ No newline at end of file
using Castle.MicroKernel.Registration;
using Plus.EntityFramework.Configuration;
using Plus.EntityFramework.Repositories;
using Plus.EntityFramework.Uow;
using Plus.Modules;
using Plus.Orm;
using Plus.Reflection;
using System;
using System.Reflection;
namespace Plus.EntityFramework
{
/// <summary>
/// EntityFramework 数据访问层
/// </summary>
[DependsOn(typeof(PlusLeadershipModule))]
public class PlusEntityFrameworkModule : PlusModule
{
private readonly ITypeFinder _typeFinder;
public PlusEntityFrameworkModule(ITypeFinder typeFinder)
{
_typeFinder = typeFinder;
}
public override void PreInitialize()
{
IocManager.Register<IPlusEfCoreConfiguration, PlusEfCoreConfiguration>();
}
public override void Initialize()
{
IocManager.RegisterAssembly(typeof(PlusEntityFrameworkModule).GetAssembly());
IocManager.IocContainer.Register(
Component.For(typeof(IDbContextProvider<>))
.ImplementedBy(typeof(UnitOfWorkDbContextProvider<>))
.LifestyleTransient()
);
RegisterGenericRepositoriesAndMatchDbContexes();
}
private void RegisterGenericRepositoriesAndMatchDbContexes()
{
var dbContextTypes =
_typeFinder.Find(type =>
{
var typeInfo = type.GetTypeInfo();
return typeInfo.IsPublic &&
!typeInfo.IsAbstract &&
typeInfo.IsClass &&
typeof(PlusDbContext).IsAssignableFrom(type);
});
if (dbContextTypes.IsNullOrEmpty())
{
Logger.Warn("No class found derived from AbpDbContext.");
return;
}
foreach (var dbContextType in dbContextTypes)
{
Logger.Debug("Registering DbContext: " + dbContextType.AssemblyQualifiedName);
IocManager.Resolve<IEfGenericRepositoryRegistrar>().RegisterForDbContext(dbContextType, IocManager, EfCoreAutoRepositoryTypes.Default);
IocManager.IocContainer.Register(
Component.For<ISecondaryOrmRegistrar>()
.Named(Guid.NewGuid().ToString("N"))
.Instance(new EfCoreBasedSecondaryOrmRegistrar(dbContextType, IocManager.Resolve<IDbContextEntityFinder>()))
.LifestyleTransient()
);
}
IocManager.Resolve<IDbContextTypeMatcher>().Populate(dbContextTypes);
}
}
}
\ No newline at end of file
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Plus.Reflection;
using Plus.Timing;
using System;
using System.Linq.Expressions;
using System.Reflection;
namespace Plus.EntityFramework
{
/// <summary>
/// PlusEntityMaterializerSource
/// </summary>
public class PlusEntityMaterializerSource : EntityMaterializerSource
{
private static readonly MethodInfo NormalizeDateTimeMethod =
typeof(PlusEntityMaterializerSource).GetTypeInfo().GetMethod(nameof(NormalizeDateTime), BindingFlags.Static | BindingFlags.NonPublic);
private static readonly MethodInfo NormalizeNullableDateTimeMethod =
typeof(PlusEntityMaterializerSource).GetTypeInfo().GetMethod(nameof(NormalizeNullableDateTime), BindingFlags.Static | BindingFlags.NonPublic);
public override Expression CreateReadValueExpression(Expression valueBuffer, Type type, int index, IPropertyBase propertyBase)
{
if (ShouldDisableDateTimeNormalization(propertyBase))
{
return base.CreateReadValueExpression(valueBuffer, type, index, propertyBase);
}
if (type == typeof(DateTime))
{
return Expression.Call(
NormalizeDateTimeMethod,
base.CreateReadValueExpression(valueBuffer, type, index, propertyBase)
);
}
if (type == typeof(DateTime?))
{
return Expression.Call(
NormalizeNullableDateTimeMethod,
base.CreateReadValueExpression(valueBuffer, type, index, propertyBase)
);
}
return base.CreateReadValueExpression(valueBuffer, type, index, propertyBase);
}
private static DateTime NormalizeDateTime(DateTime value)
{
return value;
}
private static DateTime? NormalizeNullableDateTime(DateTime? value)
{
if (value == null)
{
return null;
}
return value.Value;
}
private static bool ShouldDisableDateTimeNormalization(IPropertyBase propertyBase)
{
if (propertyBase == null)
{
return false;
}
if (propertyBase.PropertyInfo == null)
{
return false;
}
if (propertyBase.PropertyInfo.IsDefined(typeof(DisableDateTimeNormalizationAttribute), true))
{
return true;
}
propertyBase.TryGetMemberInfo(false, false, out var memberInfo, out _);
return ReflectionHelper.GetSingleAttributeOfMemberOrDeclaringTypeOrDefault<DisableDateTimeNormalizationAttribute>(memberInfo) != null;
}
}
}
\ No newline at end of file
using Plus.Domain.Repositories;
namespace Plus.EntityFramework.Repositories
{
/// <summary>
/// EfCoreAutoRepositoryTypes
/// </summary>
public static class EfCoreAutoRepositoryTypes
{
public static AutoRepositoryTypesAttribute Default { get; }
static EfCoreAutoRepositoryTypes()
{
Default = new AutoRepositoryTypesAttribute(
typeof(IRepository<>),
typeof(IRepository<,>),
typeof(EfCoreRepositoryBase<,>),
typeof(EfCoreRepositoryBase<,,>)
);
}
}
}
\ No newline at end of file
using Microsoft.EntityFrameworkCore;
using Plus.Domain.Entities;
using Plus.Domain.Repositories;
namespace Plus.EntityFramework.Repositories
{
/// <summary>
/// EfCoreRepositoryBase
/// </summary>
/// <typeparam name="TDbContext"></typeparam>
/// <typeparam name="TEntity"></typeparam>
public class EfCoreRepositoryBase<TDbContext, TEntity> : EfCoreRepositoryBase<TDbContext, TEntity, int>, IRepository<TEntity>
where TEntity : class, IEntity<int>
where TDbContext : DbContext
{
public EfCoreRepositoryBase(IDbContextProvider<TDbContext> dbContextProvider)
: base(dbContextProvider)
{
}
}
}
\ No newline at end of file
using Microsoft.EntityFrameworkCore;
using Plus.Data;
using Plus.Domain.Entities;
using Plus.Domain.Repositories;
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Linq;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
namespace Plus.EntityFramework.Repositories
{
/// <summary>
/// Implements IRepository for Entity Framework.
/// </summary>
/// <typeparam name="TDbContext">DbContext which contains <typeparamref name="TEntity"/>.</typeparam>
/// <typeparam name="TEntity">Type of the Entity for this repository</typeparam>
/// <typeparam name="TPrimaryKey">Primary key of the entity</typeparam>
public class EfCoreRepositoryBase<TDbContext, TEntity, TPrimaryKey> :
PlusRepositoryBase<TEntity, TPrimaryKey>,
ISupportsExplicitLoading<TEntity, TPrimaryKey>,
IRepositoryWithDbContext
where TEntity : class, IEntity<TPrimaryKey>
where TDbContext : DbContext
{
/// <summary>
/// Gets EF DbContext object.
/// </summary>
public virtual TDbContext Context => _dbContextProvider.GetDbContext();
/// <summary>
/// Gets DbSet for given entity.
/// </summary>
public virtual DbSet<TEntity> Table => Context.Set<TEntity>();
public virtual DbTransaction Transaction
{
get
{
return (DbTransaction)TransactionProvider?.GetActiveTransaction(new ActiveTransactionProviderArgs
{
{"ContextType", typeof(TDbContext) }
});
}
}
public virtual DbConnection Connection
{
get
{
var connection = Context.Database.GetDbConnection();
if (connection.State != ConnectionState.Open)
{
connection.Open();
}
return connection;
}
}
public IActiveTransactionProvider TransactionProvider { private get; set; }
private readonly IDbContextProvider<TDbContext> _dbContextProvider;
/// <summary>
/// Constructor
/// </summary>
/// <param name="dbContextProvider"></param>
public EfCoreRepositoryBase(IDbContextProvider<TDbContext> dbContextProvider)
{
_dbContextProvider = dbContextProvider;
}
public override IQueryable<TEntity> GetAll()
{
return GetAllIncluding();
}
public override IQueryable<TEntity> GetAllIncluding(params Expression<Func<TEntity, object>>[] propertySelectors)
{
var query = Table.AsQueryable();
if (!propertySelectors.IsNullOrEmpty())
{
foreach (var propertySelector in propertySelectors)
{
query = query.Include(propertySelector);
}
}
return query;
}
public override async Task<List<TEntity>> GetAllListAsync()
{
return await GetAll().ToListAsync();
}
public override async Task<List<TEntity>> GetAllListAsync(Expression<Func<TEntity, bool>> predicate)
{
return await GetAll().Where(predicate).ToListAsync();
}
public override async Task<TEntity> SingleAsync(Expression<Func<TEntity, bool>> predicate)
{
return await GetAll().SingleAsync(predicate);
}
public override async Task<TEntity> FirstOrDefaultAsync(TPrimaryKey id)
{
return await GetAll().FirstOrDefaultAsync(CreateEqualityExpressionForId(id));
}
public override async Task<TEntity> FirstOrDefaultAsync(Expression<Func<TEntity, bool>> predicate)
{
return await GetAll().FirstOrDefaultAsync(predicate);
}
public override TEntity Insert(TEntity entity)
{
return Table.Add(entity).Entity;
}
public override Task<TEntity> InsertAsync(TEntity entity)
{
return Task.FromResult(Insert(entity));
}
public override TPrimaryKey InsertAndGetId(TEntity entity)
{
entity = Insert(entity);
if (MayHaveTemporaryKey(entity) || entity.IsTransient())
{
Context.SaveChanges();
}
return entity.Id;
}
public override async Task<TPrimaryKey> InsertAndGetIdAsync(TEntity entity)
{
entity = await InsertAsync(entity);
if (MayHaveTemporaryKey(entity) || entity.IsTransient())
{
await Context.SaveChangesAsync();
}
return entity.Id;
}
public override TPrimaryKey InsertOrUpdateAndGetId(TEntity entity)
{
entity = InsertOrUpdate(entity);
if (MayHaveTemporaryKey(entity) || entity.IsTransient())
{
Context.SaveChanges();
}
return entity.Id;
}
public override async Task<TPrimaryKey> InsertOrUpdateAndGetIdAsync(TEntity entity)
{
entity = await InsertOrUpdateAsync(entity);
if (MayHaveTemporaryKey(entity) || entity.IsTransient())
{
await Context.SaveChangesAsync();
}
return entity.Id;
}
public override TEntity Update(TEntity entity)
{
AttachIfNot(entity);
Context.Entry(entity).State = EntityState.Modified;
return entity;
}
public override Task<TEntity> UpdateAsync(TEntity entity)
{
entity = Update(entity);
return Task.FromResult(entity);
}
public override void Delete(TEntity entity)
{
AttachIfNot(entity);
Table.Remove(entity);
}
public override void Delete(TPrimaryKey id)
{
var entity = GetFromChangeTrackerOrNull(id);
if (entity != null)
{
Delete(entity);
return;
}
entity = FirstOrDefault(id);
if (entity != null)
{
Delete(entity);
return;
}
//Could not found the entity, do nothing.
}
public override async Task<int> CountAsync()
{
return await GetAll().CountAsync();
}
public override async Task<int> CountAsync(Expression<Func<TEntity, bool>> predicate)
{
return await GetAll().Where(predicate).CountAsync();
}
public override async Task<long> LongCountAsync()
{
return await GetAll().LongCountAsync();
}
public override async Task<long> LongCountAsync(Expression<Func<TEntity, bool>> predicate)
{
return await GetAll().Where(predicate).LongCountAsync();
}
protected virtual void AttachIfNot(TEntity entity)
{
var entry = Context.ChangeTracker.Entries().FirstOrDefault(ent => ent.Entity == entity);
if (entry != null)
{
return;
}
Table.Attach(entity);
}
public DbContext GetDbContext()
{
return Context;
}
public Task EnsureCollectionLoadedAsync<TProperty>(
TEntity entity,
Expression<Func<TEntity, IEnumerable<TProperty>>> collectionExpression,
CancellationToken cancellationToken)
where TProperty : class
{
return Context.Entry(entity).Collection(collectionExpression).LoadAsync(cancellationToken);
}
public Task EnsurePropertyLoadedAsync<TProperty>(
TEntity entity,
Expression<Func<TEntity, TProperty>> propertyExpression,
CancellationToken cancellationToken)
where TProperty : class
{
return Context.Entry(entity).Reference(propertyExpression).LoadAsync(cancellationToken);
}
private TEntity GetFromChangeTrackerOrNull(TPrimaryKey id)
{
var entry = Context.ChangeTracker.Entries()
.FirstOrDefault(
ent =>
ent.Entity is TEntity &&
EqualityComparer<TPrimaryKey>.Default.Equals(id, (ent.Entity as TEntity).Id)
);
return entry?.Entity as TEntity;
}
private static bool MayHaveTemporaryKey(TEntity entity)
{
if (typeof(TPrimaryKey) == typeof(byte))
{
return true;
}
if (typeof(TPrimaryKey) == typeof(int))
{
return Convert.ToInt32(entity.Id) <= 0;
}
if (typeof(TPrimaryKey) == typeof(long))
{
return Convert.ToInt64(entity.Id) <= 0;
}
return false;
}
}
}
\ No newline at end of file
using Microsoft.EntityFrameworkCore;
using Plus.Domain.Entities;
using Plus.Domain.Repositories;
using Plus.Reflection;
using System;
namespace Plus.EntityFramework.Repositories
{
/// <summary>
/// EfCoreRepositoryExtensions
/// </summary>
public static class EfCoreRepositoryExtensions
{
public static DbContext GetDbContext<TEntity, TPrimaryKey>(this IRepository<TEntity, TPrimaryKey> repository)
where TEntity : class, IEntity<TPrimaryKey>
{
var repositoryWithDbContext = ProxyHelper.UnProxy(repository) as IRepositoryWithDbContext;
if (repositoryWithDbContext != null)
{
return repositoryWithDbContext.GetDbContext();
}
throw new ArgumentException("Given repository does not implement IRepositoryWithDbContext", nameof(repository));
}
public static void DetachFromDbContext<TEntity, TPrimaryKey>(this IRepository<TEntity, TPrimaryKey> repository, TEntity entity)
where TEntity : class, IEntity<TPrimaryKey>
{
repository.GetDbContext().Entry(entity).State = EntityState.Detached;
}
}
}
\ No newline at end of file
using Castle.Core.Logging;
using Castle.MicroKernel.Registration;
using Plus.Dependency;
using Plus.Domain.Entities;
using Plus.Domain.Repositories;
using System;
using System.Reflection;
namespace Plus.EntityFramework.Repositories
{
/// <summary>
/// EfGenericRepositoryRegistrar
/// </summary>
public class EfGenericRepositoryRegistrar : IEfGenericRepositoryRegistrar, ITransientDependency
{
public ILogger Logger { get; set; }
private readonly IDbContextEntityFinder _dbContextEntityFinder;
public EfGenericRepositoryRegistrar(IDbContextEntityFinder dbContextEntityFinder)
{
_dbContextEntityFinder = dbContextEntityFinder;
Logger = NullLogger.Instance;
}
public void RegisterForDbContext(
Type dbContextType,
IIocManager iocManager,
AutoRepositoryTypesAttribute defaultAutoRepositoryTypesAttribute)
{
var autoRepositoryAttr = dbContextType.GetTypeInfo().GetSingleAttributeOrNull<AutoRepositoryTypesAttribute>() ?? defaultAutoRepositoryTypesAttribute;
RegisterForDbContext(
dbContextType,
iocManager,
autoRepositoryAttr.RepositoryInterface,
autoRepositoryAttr.RepositoryInterfaceWithPrimaryKey,
autoRepositoryAttr.RepositoryImplementation,
autoRepositoryAttr.RepositoryImplementationWithPrimaryKey
);
if (autoRepositoryAttr.WithDefaultRepositoryInterfaces)
{
RegisterForDbContext(
dbContextType,
iocManager,
defaultAutoRepositoryTypesAttribute.RepositoryInterface,
defaultAutoRepositoryTypesAttribute.RepositoryInterfaceWithPrimaryKey,
autoRepositoryAttr.RepositoryImplementation,
autoRepositoryAttr.RepositoryImplementationWithPrimaryKey
);
}
}
private void RegisterForDbContext(
Type dbContextType,
IIocManager iocManager,
Type repositoryInterface,
Type repositoryInterfaceWithPrimaryKey,
Type repositoryImplementation,
Type repositoryImplementationWithPrimaryKey)
{
foreach (var entityTypeInfo in _dbContextEntityFinder.GetEntityTypeInfos(dbContextType))
{
var primaryKeyType = EntityHelper.GetPrimaryKeyType(entityTypeInfo.EntityType);
if (primaryKeyType == typeof(int))
{
var genericRepositoryType = repositoryInterface.MakeGenericType(entityTypeInfo.EntityType);
if (!iocManager.IsRegistered(genericRepositoryType))
{
var implType = repositoryImplementation.GetGenericArguments().Length == 1
? repositoryImplementation.MakeGenericType(entityTypeInfo.EntityType)
: repositoryImplementation.MakeGenericType(entityTypeInfo.DeclaringType,
entityTypeInfo.EntityType);
iocManager.IocContainer.Register(
Component
.For(genericRepositoryType)
.ImplementedBy(implType)
.Named(Guid.NewGuid().ToString("N"))
.LifestyleTransient()
);
}
}
var genericRepositoryTypeWithPrimaryKey = repositoryInterfaceWithPrimaryKey.MakeGenericType(entityTypeInfo.EntityType, primaryKeyType);
if (!iocManager.IsRegistered(genericRepositoryTypeWithPrimaryKey))
{
var implType = repositoryImplementationWithPrimaryKey.GetGenericArguments().Length == 2
? repositoryImplementationWithPrimaryKey.MakeGenericType(entityTypeInfo.EntityType, primaryKeyType)
: repositoryImplementationWithPrimaryKey.MakeGenericType(entityTypeInfo.DeclaringType, entityTypeInfo.EntityType, primaryKeyType);
iocManager.IocContainer.Register(
Component
.For(genericRepositoryTypeWithPrimaryKey)
.ImplementedBy(implType)
.Named(Guid.NewGuid().ToString("N"))
.LifestyleTransient()
);
}
}
}
}
}
\ No newline at end of file
using Plus.Dependency;
using Plus.Domain.Repositories;
using System;
namespace Plus.EntityFramework.Repositories
{
/// <summary>
/// IEfGenericRepositoryRegistrar
/// </summary>
public interface IEfGenericRepositoryRegistrar
{
void RegisterForDbContext(Type dbContextType, IIocManager iocManager, AutoRepositoryTypesAttribute defaultAutoRepositoryTypesAttribute);
}
}
\ No newline at end of file
using Microsoft.EntityFrameworkCore;
namespace Plus.EntityFramework.Repositories
{
/// <summary>
/// IRepositoryWithDbContext
/// </summary>
public interface IRepositoryWithDbContext
{
DbContext GetDbContext();
}
}
\ No newline at end of file
using Plus.Dependency;
using Plus.Domain.Entities;
using Plus.Domain.Repositories;
using Plus.Orm;
using System;
using System.Reflection;
namespace Plus.EntityFramework
{
/// <summary>
/// SecondaryOrmRegistrarBase
/// </summary>
public abstract class SecondaryOrmRegistrarBase : ISecondaryOrmRegistrar
{
private readonly IDbContextEntityFinder _dbContextEntityFinder;
private readonly Type _dbContextType;
public abstract string OrmContextKey { get; }
protected SecondaryOrmRegistrarBase(Type dbContextType, IDbContextEntityFinder dbContextEntityFinder)
{
_dbContextType = dbContextType;
_dbContextEntityFinder = dbContextEntityFinder;
}
public virtual void RegisterRepositories(IIocManager iocManager, AutoRepositoryTypesAttribute defaultRepositoryTypes)
{
AutoRepositoryTypesAttribute autoRepositoryAttr = _dbContextType.GetTypeInfo().GetSingleAttributeOrNull<AutoRepositoryTypesAttribute>()
?? defaultRepositoryTypes;
foreach (EntityTypeInfo entityTypeInfo in _dbContextEntityFinder.GetEntityTypeInfos(_dbContextType))
{
Type primaryKeyType = EntityHelper.GetPrimaryKeyType(entityTypeInfo.EntityType);
if (primaryKeyType == typeof(int))
{
Type genericRepositoryType = autoRepositoryAttr.RepositoryInterface.MakeGenericType(entityTypeInfo.EntityType);
if (!iocManager.IsRegistered(genericRepositoryType))
{
Type implType = autoRepositoryAttr.RepositoryImplementation.GetTypeInfo().GetGenericArguments().Length == 1
? autoRepositoryAttr.RepositoryImplementation.MakeGenericType(entityTypeInfo.EntityType)
: autoRepositoryAttr.RepositoryImplementation.MakeGenericType(entityTypeInfo.DeclaringType, entityTypeInfo.EntityType);
iocManager.Register(
genericRepositoryType,
implType,
DependencyLifeStyle.Transient
);
}
}
Type genericRepositoryTypeWithPrimaryKey = autoRepositoryAttr.RepositoryInterfaceWithPrimaryKey.MakeGenericType(entityTypeInfo.EntityType, primaryKeyType);
if (!iocManager.IsRegistered(genericRepositoryTypeWithPrimaryKey))
{
Type implType = autoRepositoryAttr.RepositoryImplementationWithPrimaryKey.GetTypeInfo().GetGenericArguments().Length == 2
? autoRepositoryAttr.RepositoryImplementationWithPrimaryKey.MakeGenericType(entityTypeInfo.EntityType, primaryKeyType)
: autoRepositoryAttr.RepositoryImplementationWithPrimaryKey.MakeGenericType(entityTypeInfo.DeclaringType, entityTypeInfo.EntityType, primaryKeyType);
iocManager.Register(
genericRepositoryTypeWithPrimaryKey,
implType,
DependencyLifeStyle.Transient
);
}
}
}
}
}
\ No newline at end of file
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage;
using System.Collections.Generic;
namespace Plus.EntityFramework.Uow
{
/// <summary>
/// ActiveTransactionInfo
/// </summary>
public class ActiveTransactionInfo
{
public IDbContextTransaction DbContextTransaction { get; }
public DbContext StarterDbContext { get; }
public List<DbContext> AttendedDbContexts { get; }
public ActiveTransactionInfo(IDbContextTransaction dbContextTransaction, DbContext starterDbContext)
{
DbContextTransaction = dbContextTransaction;
StarterDbContext = starterDbContext;
AttendedDbContexts = new List<DbContext>();
}
}
}
\ No newline at end of file
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage;
using Plus.Dependency;
using Plus.Domain.Uow;
using Plus.EntityFramework.Extensions;
using System.Collections.Generic;
using System.Transactions;
namespace Plus.EntityFramework.Uow
{
/// <summary>
/// DbContextEfCoreTransactionStrategy
/// </summary>
public class DbContextEfCoreTransactionStrategy : IEfCoreTransactionStrategy, ITransientDependency
{
protected UnitOfWorkOptions Options { get; private set; }
protected IDictionary<string, ActiveTransactionInfo> ActiveTransactions { get; }
public DbContextEfCoreTransactionStrategy()
{
ActiveTransactions = new Dictionary<string, ActiveTransactionInfo>();
}
public void InitOptions(UnitOfWorkOptions options)
{
Options = options;
}
public DbContext CreateDbContext<TDbContext>(string connectionString, IDbContextResolver dbContextResolver) where TDbContext : DbContext
{
DbContext dbContext;
var activeTransaction = ActiveTransactions.GetOrDefault(connectionString);
if (activeTransaction == null)
{
dbContext = dbContextResolver.Resolve<TDbContext>(connectionString, null);
var dbtransaction = dbContext.Database.BeginTransaction((Options.IsolationLevel ?? IsolationLevel.ReadUncommitted).ToSystemDataIsolationLevel());
activeTransaction = new ActiveTransactionInfo(dbtransaction, dbContext);
ActiveTransactions[connectionString] = activeTransaction;
}
else
{
dbContext = dbContextResolver.Resolve<TDbContext>(
connectionString,
activeTransaction.DbContextTransaction.GetDbTransaction().Connection
);
if (dbContext.HasRelationalTransactionManager())
{
dbContext.Database.UseTransaction(activeTransaction.DbContextTransaction.GetDbTransaction());
}
else
{
dbContext.Database.BeginTransaction();
}
activeTransaction.AttendedDbContexts.Add(dbContext);
}
return dbContext;
}
public void Commit()
{
foreach (var activeTransaction in ActiveTransactions.Values)
{
activeTransaction.DbContextTransaction.Commit();
foreach (var dbContext in activeTransaction.AttendedDbContexts)
{
if (dbContext.HasRelationalTransactionManager())
{
continue; //Relational databases use the shared transaction
}
dbContext.Database.CommitTransaction();
}
}
}
public void Dispose(IIocResolver iocResolver)
{
foreach (var activeTransaction in ActiveTransactions.Values)
{
activeTransaction.DbContextTransaction.Dispose();
foreach (var attendedDbContext in activeTransaction.AttendedDbContexts)
{
iocResolver.Release(attendedDbContext);
}
iocResolver.Release(activeTransaction.StarterDbContext);
}
ActiveTransactions.Clear();
}
}
}
\ No newline at end of file
using Microsoft.EntityFrameworkCore;
using Plus.Dependency;
using Plus.Domain.Uow;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Threading.Tasks;
namespace Plus.EntityFramework.Uow
{
/// <summary>
/// EfCoreUnitOfWork
/// </summary>
public class EfCoreUnitOfWork : UnitOfWorkBase, ITransientDependency
{
protected IDictionary<string, DbContext> ActiveDbContexts { get; }
protected IIocResolver IocResolver { get; }
private readonly IDbContextResolver _dbContextResolver;
private readonly IDbContextTypeMatcher _dbContextTypeMatcher;
private readonly IEfCoreTransactionStrategy _transactionStrategy;
public EfCoreUnitOfWork(
IIocResolver iocResolver,
IConnectionStringResolver connectionStringResolver,
IUnitOfWorkFilterExecuter filterExecuter,
IDbContextResolver dbContextResolver,
IUnitOfWorkDefaultOptions defaultOptions,
IDbContextTypeMatcher dbContextTypeMatcher,
IEfCoreTransactionStrategy transactionStrategy)
: base(
connectionStringResolver,
defaultOptions,
filterExecuter)
{
IocResolver = iocResolver;
_dbContextResolver = dbContextResolver;
_dbContextTypeMatcher = dbContextTypeMatcher;
_transactionStrategy = transactionStrategy;
ActiveDbContexts = new Dictionary<string, DbContext>();
}
protected override void BeginUow()
{
if (Options.IsTransactional == true)
{
_transactionStrategy.InitOptions(Options);
}
}
public override void SaveChanges()
{
foreach (var dbContext in GetAllActiveDbContexts())
{
SaveChangesInDbContext(dbContext);
}
}
public override async Task SaveChangesAsync()
{
foreach (var dbContext in GetAllActiveDbContexts())
{
await SaveChangesInDbContextAsync(dbContext);
}
}
protected override void CompleteUow()
{
SaveChanges();
CommitTransaction();
}
protected override async Task CompleteUowAsync()
{
await SaveChangesAsync();
CommitTransaction();
}
private void CommitTransaction()
{
if (Options.IsTransactional == true)
{
_transactionStrategy.Commit();
}
}
public IReadOnlyList<DbContext> GetAllActiveDbContexts()
{
return ActiveDbContexts.Values.ToImmutableList();
}
public virtual TDbContext GetOrCreateDbContext<TDbContext>(string name = null)
where TDbContext : DbContext
{
var concreteDbContextType = _dbContextTypeMatcher.GetConcreteType(typeof(TDbContext));
var connectionStringResolveArgs = new ConnectionStringResolveArgs
{
["DbContextType"] = typeof(TDbContext),
["DbContextConcreteType"] = concreteDbContextType
};
var connectionString = ResolveConnectionString(connectionStringResolveArgs);
var dbContextKey = concreteDbContextType.FullName + "#" + connectionString;
if (name != null)
{
dbContextKey += "#" + name;
}
if (!ActiveDbContexts.TryGetValue(dbContextKey, out DbContext dbContext))
{
if (Options.IsTransactional == true)
{
dbContext = _transactionStrategy.CreateDbContext<TDbContext>(connectionString, _dbContextResolver);
}
else
{
dbContext = _dbContextResolver.Resolve<TDbContext>(connectionString, null);
}
if (Options.Timeout.HasValue &&
dbContext.Database.IsRelational() &&
!dbContext.Database.GetCommandTimeout().HasValue)
{
dbContext.Database.SetCommandTimeout(Options.Timeout.Value.TotalSeconds.To<int>());
}
//TODO: Object materialize event
//TODO: Apply current filters to this dbcontext
ActiveDbContexts[dbContextKey] = dbContext;
}
return (TDbContext)dbContext;
}
protected override void DisposeUow()
{
if (Options.IsTransactional == true)
{
_transactionStrategy.Dispose(IocResolver);
}
else
{
foreach (var context in GetAllActiveDbContexts())
{
Release(context);
}
}
ActiveDbContexts.Clear();
}
protected virtual void SaveChangesInDbContext(DbContext dbContext)
{
dbContext.SaveChanges();
}
protected virtual async Task SaveChangesInDbContextAsync(DbContext dbContext)
{
await dbContext.SaveChangesAsync();
}
protected virtual void Release(DbContext dbContext)
{
dbContext.Dispose();
IocResolver.Release(dbContext);
}
}
}
\ No newline at end of file
using Microsoft.EntityFrameworkCore;
using Plus.Dependency;
using Plus.Domain.Uow;
namespace Plus.EntityFramework.Uow
{
/// <summary>
/// IEfCoreTransactionStrategy
/// </summary>
public interface IEfCoreTransactionStrategy
{
void InitOptions(UnitOfWorkOptions options);
DbContext CreateDbContext<TDbContext>(string connectionString, IDbContextResolver dbContextResolver) where TDbContext : DbContext;
void Commit();
void Dispose(IIocResolver iocResolver);
}
}
\ No newline at end of file
using Microsoft.EntityFrameworkCore;
using Plus.Domain.Uow;
namespace Plus.EntityFramework.Uow
{
/// <summary>
/// UnitOfWorkDbContextProvider
/// </summary>
/// <typeparam name="TDbContext"></typeparam>
public class UnitOfWorkDbContextProvider<TDbContext> : IDbContextProvider<TDbContext> where TDbContext : DbContext
{
private readonly ICurrentUnitOfWorkProvider _currentUnitOfWorkProvider;
public UnitOfWorkDbContextProvider(ICurrentUnitOfWorkProvider currentUnitOfWorkProvider)
{
_currentUnitOfWorkProvider = currentUnitOfWorkProvider;
}
public TDbContext GetDbContext()
{
return _currentUnitOfWorkProvider.Current.GetDbContext<TDbContext>();
}
}
}
\ No newline at end of file
using Microsoft.EntityFrameworkCore;
using Plus.Domain.Uow;
using System;
namespace Plus.EntityFramework.Uow
{
/// <summary>
/// UnitOfWorkExtensions
/// </summary>
public static class UnitOfWorkExtensions
{
public static TDbContext GetDbContext<TDbContext>(this IActiveUnitOfWork unitOfWork, string name = null) where TDbContext : DbContext
{
if (unitOfWork == null)
{
throw new ArgumentNullException("unitOfWork");
}
if (!(unitOfWork is EfCoreUnitOfWork))
{
throw new ArgumentException("unitOfWork is not type of " + typeof(EfCoreUnitOfWork).FullName, "unitOfWork");
}
return (unitOfWork as EfCoreUnitOfWork).GetOrCreateDbContext<TDbContext>(name);
}
}
}
\ No newline at end of file
...@@ -28,6 +28,14 @@ namespace Plus.Dependency ...@@ -28,6 +28,14 @@ namespace Plus.Dependency
/// <returns></returns> /// <returns></returns>
T[] ResolveAll<T>(); T[] ResolveAll<T>();
/// <summary>
/// 从IOC容器中获取对象
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="type"></param>
/// <returns></returns>
T Resolve<T>(Type type);
/// <summary> /// <summary>
/// 是否注册了给定的类型 /// 是否注册了给定的类型
/// </summary> /// </summary>
......
using System;
namespace Plus.Domain.Entities
{
/// <summary>
/// EntityTypeInfo
/// </summary>
public class EntityTypeInfo
{
public Type EntityType { get; private set; }
public Type DeclaringType { get; private set; }
public EntityTypeInfo(Type entityType, Type declaringType)
{
EntityType = entityType;
DeclaringType = declaringType;
}
}
}
\ No newline at end of file
using Castle.DynamicProxy;
namespace Plus.Reflection
{
/// <summary>
/// ProxyHelper
/// </summary>
public class ProxyHelper
{
public static object UnProxy(object obj)
{
return ProxyUtil.GetUnproxiedInstance(obj);
}
}
}
\ No newline at end of file
...@@ -8,7 +8,7 @@ namespace Plus.Reflection ...@@ -8,7 +8,7 @@ namespace Plus.Reflection
/// <summary> /// <summary>
/// 反射帮助类 /// 反射帮助类
/// </summary> /// </summary>
internal static class ReflectionHelper public static class ReflectionHelper
{ {
/// <summary> /// <summary>
/// Checks whether <paramref name="givenType"/> implements/inherits <paramref name="genericType"/>. /// Checks whether <paramref name="givenType"/> implements/inherits <paramref name="genericType"/>.
...@@ -130,7 +130,7 @@ namespace Plus.Reflection ...@@ -130,7 +130,7 @@ namespace Plus.Reflection
/// <param name="memberInfo">MemberInfo</param> /// <param name="memberInfo">MemberInfo</param>
/// <param name="defaultValue">Default value (null as default)</param> /// <param name="defaultValue">Default value (null as default)</param>
/// <param name="inherit">Inherit attribute from base classes</param> /// <param name="inherit">Inherit attribute from base classes</param>
public static TAttribute GetSingleAttributeOfMemberOrDeclaringTypeOrDefault<TAttribute>(MemberInfo memberInfo, TAttribute defaultValue = default(TAttribute), bool inherit = true) public static TAttribute GetSingleAttributeOfMemberOrDeclaringTypeOrDefault<TAttribute>(MemberInfo memberInfo, TAttribute defaultValue = default, bool inherit = true)
where TAttribute : class where TAttribute : class
{ {
return memberInfo.GetCustomAttributes(true).OfType<TAttribute>().FirstOrDefault() return memberInfo.GetCustomAttributes(true).OfType<TAttribute>().FirstOrDefault()
......
using System;
namespace Plus.Timing
{
/// <summary>
/// DisableDateTimeNormalizationAttribute
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Parameter)]
public class DisableDateTimeNormalizationAttribute : Attribute
{
}
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment