造轮子之ORM集成

轮子,orm,集成 · 浏览次数 : 16

小编点评

**EF拦截器的逻辑操作** 1. 当调用 `SavingChanges` 方法是就会执行 `OnSavingChanges` 方法。 2. `OnSavingChanges` 方法会对 `eventData` 中的 `Context` 进行实例化。 3. 在实例化 `Context` 中的 `ChangeTracker` 中进行检测。 4. 遍历 `ChangeTracker` 的 `Entries`,并根据 `State` 和 `Entity` 的类型进行处理。 5. 当遇到 `Deleted`、`Modified`和`Added` 的实体时,会设置 `IsDeleted`、`UpdateTime` 和 `CreationTime` 属性。 6. 当遇到 `SoftDelete` 的实体时,会设置 `IsDeleted` 属性为 `true`。 ** AddDbContext 中的拦截器** ```csharp builder.Services.AddDbContext(options => options.UseSqlite(connectionString) .AddInterceptors(new WheelEFCoreInterceptor()) .UseLazyLoadingProxies()); ``` **具体逻辑操作** * `OnSavingChanges` 方法会对 `eventData` 中的 `Context` 进行实例化。 * `Context` 的 `ChangeTracker` 进行检测。 * 遍历 `ChangeTracker` 的 `Entries`,并根据 `State` 和 `Entity` 的类型进行处理。 * 当遇到 `Deleted`、`Modified`和`Added` 的实体时,会设置 `IsDeleted`、`UpdateTime` 和 `CreationTime` 属性。 * 当遇到 `SoftDelete` 的实体时,会设置 `IsDeleted` 属性为 `true`。 **总结** EF拦截器在数据库操作中,当遇到 `Deleted`、`Modified`和`Added` 的实体时,会设置对应的属性,从而实现数据库的软删除、创建时间和更新。

正文

Dotnet的ORM千千万,还是喜欢用EF CORE

前面一些基础完成的差不多了,接下来可以集成数据库了,官方出品的ORM还是比较香。所以接下来就是来集成EF CORE。

安装包

首先我们需要安装一下EF CORE的NUGET包,有如下几个:

Microsoft.EntityFrameworkCore.Proxies
Microsoft.EntityFrameworkCore.Sqlite
Microsoft.EntityFrameworkCore.Design
Microsoft.EntityFrameworkCore.Tools

其中Microsoft.EntityFrameworkCore.Sqlite我们可以根据我们实际使用的数据库进行替换。
而Microsoft.EntityFrameworkCore.Proxies则是用于启用EF中的懒加载模式。.
Microsoft.EntityFrameworkCore.Design和Microsoft.EntityFrameworkCore.Tools则是用于数据库迁移

DbContext

接下来创建我们的DbContext文件


namespace Wheel.EntityFrameworkCore
{
    public class WheelDbContext : DbContext
    {
        public WheelDbContext(DbContextOptions<WheelDbContext> options) : base(options)
        {

        }

        protected override void OnModelCreating(ModelBuilder builder)
        {
            base.OnModelCreating(builder);
        }

}

在Program中添加DbContext


var connectionString = builder.Configuration.GetConnectionString("Default") ?? throw new InvalidOperationException("Connection string 'Default' not found.");

builder.Services.AddDbContext<WheelDbContext>(options =>
    options.UseSqlite(connectionString)
        .UseLazyLoadingProxies()
);

在配置文件中添加连接字符串

"ConnectionStrings": {
  "Default": "Data Source=Wheel.WebApi.Host.db"
}

封装Repository

在AddDbContext之后,我们就可以在程序中直接注入WheelDbContext来操作我们的数据库了。但是为了我们以后可能随时切换ORM,我们还是封装一层Repository来操作我们的数据库。
新增IBasicRepository泛型接口:

    public interface IBasicRepository<TEntity, TKey> where TEntity : class 
    {
        Task<TEntity> InsertAsync(TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default);
        Task InsertManyAsync(List<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default);
        Task<TEntity> UpdateAsync(TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default);
        Task UpdateAsync(Expression<Func<TEntity, bool>> predicate, Expression<Func<SetPropertyCalls<TEntity>, SetPropertyCalls<TEntity>>> setPropertyCalls, bool autoSave = false, CancellationToken cancellationToken = default);
        Task UpdateManyAsync(List<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default);
        Task DeleteAsync(TKey id, bool autoSave = false, CancellationToken cancellationToken = default);
        Task DeleteAsync(TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default);
        Task DeleteAsync(Expression<Func<TEntity, bool>> predicate, bool autoSave = false, CancellationToken cancellationToken = default);
        Task DeleteManyAsync(List<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default);
        Task<TEntity?> FindAsync(TKey id, CancellationToken cancellationToken = default);
        Task<TEntity?> FindAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default);
        Task<bool> AnyAsync(CancellationToken cancellationToken = default);
        Task<bool> AnyAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default);
        Task<List<TEntity>> GetListAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default, params Expression<Func<TEntity, object>>[] propertySelectors);
        Task<List<TSelect>> SelectListAsync<TSelect>(Expression<Func<TEntity, bool>> predicate, Expression<Func<TEntity, TSelect>> selectPredicate, CancellationToken cancellationToken = default);
        Task<List<TSelect>> SelectListAsync<TSelect>(Expression<Func<TEntity, bool>> predicate, Expression<Func<TEntity, TSelect>> selectPredicate, CancellationToken cancellationToken = default, params Expression<Func<TEntity, object>>[] propertySelectors);
        Task<(List<TSelect> items, long total)> SelectPageListAsync<TSelect>(Expression<Func<TEntity, bool>> predicate, Expression<Func<TEntity, TSelect>> selectPredicate, int skip, int take, string orderby = "Id", CancellationToken cancellationToken = default);
        Task<(List<TSelect> items, long total)> SelectPageListAsync<TSelect>(Expression<Func<TEntity, bool>> predicate, Expression<Func<TEntity, TSelect>> selectPredicate, int skip, int take, string orderby = "Id", CancellationToken cancellationToken = default, params Expression<Func<TEntity, object>>[] propertySelectors);
        Task<(List<TEntity> items, long total)> GetPageListAsync(Expression<Func<TEntity, bool>> predicate, int skip, int take, string orderby = "Id", CancellationToken cancellationToken = default);
        Task<(List<TEntity> items, long total)> GetPageListAsync(Expression<Func<TEntity, bool>> predicate, int skip, int take, string orderby = "Id", CancellationToken cancellationToken = default, params Expression<Func<TEntity, object>>[] propertySelectors);
        IQueryable<TEntity> GetQueryable(bool noTracking = true);

        IQueryable<TEntity> GetQueryableWithIncludes(params Expression<Func<TEntity, object>>[] propertySelectors);

        Task<int> SaveChangeAsync(CancellationToken cancellationToken = default);
        Expression<Func<TEntity, bool>> BuildPredicate(params (bool condition, Expression<Func<TEntity, bool>> predicate)[] conditionPredicates);
    }

    public interface IBasicRepository<TEntity> : IBasicRepository<TEntity, object> where TEntity : class
    {

    }

IBasicRepository<TEntity, TKey>用于单主键的表结构,IBasicRepository : IBasicRepository<TEntity, object>用于复合主键的表结构。
然后我们来实现一下BasicRepository:


    public class EFBasicRepository<TEntity, TKey> : IBasicRepository<TEntity, TKey> where TEntity : class
    {
        private readonly WheelDbContext _dbContext;

        private DbSet<TEntity> DbSet => _dbContext.Set<TEntity>();

        public EFBasicRepository(WheelDbContext dbContext)
        {
            _dbContext = dbContext;
        }

        public async Task<TEntity> InsertAsync(TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default)
        {
            var savedEntity = (await _dbContext.Set<TEntity>().AddAsync(entity, cancellationToken)).Entity;
            if (autoSave)
            {
                await _dbContext.SaveChangesAsync(cancellationToken);
            }
            return savedEntity;
        }
        public async Task InsertManyAsync(List<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default)
        {
            await _dbContext.Set<TEntity>().AddRangeAsync(entities, cancellationToken);
            if (autoSave)
            {
                await _dbContext.SaveChangesAsync(cancellationToken);
            }
        }
        public async Task<TEntity> UpdateAsync(TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default)
        {
            var savedEntity = _dbContext.Set<TEntity>().Update(entity).Entity;

            if (autoSave)
            {
                await _dbContext.SaveChangesAsync(cancellationToken);
            }
            return savedEntity;
        }
        public async Task UpdateAsync(Expression<Func<TEntity, bool>> predicate, Expression<Func<SetPropertyCalls<TEntity>, SetPropertyCalls<TEntity>>> setPropertyCalls, bool autoSave = false, CancellationToken cancellationToken = default)
        {
            await _dbContext.Set<TEntity>().Where(predicate).ExecuteUpdateAsync(setPropertyCalls, cancellationToken);
            if (autoSave)
            {
                await _dbContext.SaveChangesAsync(cancellationToken);
            }
        }
        public async Task UpdateManyAsync(List<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default)
        {
            _dbContext.Set<TEntity>().UpdateRange(entities);
            if (autoSave)
            {
                await _dbContext.SaveChangesAsync(cancellationToken);
            }
        }
        public async Task DeleteAsync(TKey id, bool autoSave = false, CancellationToken cancellationToken = default)
        {
            var entity = await _dbContext.Set<TEntity>().FindAsync(id, cancellationToken);
            if(entity != null)
                _dbContext.Set<TEntity>().Remove(entity);
            if (autoSave)
            {
                await _dbContext.SaveChangesAsync(cancellationToken);
            }
        }
        public async Task DeleteAsync(TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default)
        {
            _dbContext.Set<TEntity>().Remove(entity);
            if (autoSave)
            {
                await _dbContext.SaveChangesAsync(cancellationToken);
            }
        }
        public async Task DeleteAsync(Expression<Func<TEntity, bool>> predicate, bool autoSave = false, CancellationToken cancellationToken = default)
        {
            await _dbContext.Set<TEntity>().Where(predicate).ExecuteDeleteAsync(cancellationToken);
            if (autoSave)
            {
                await _dbContext.SaveChangesAsync(cancellationToken);
            }
        }
        public async Task DeleteManyAsync(List<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default)
        {
            _dbContext.Set<TEntity>().RemoveRange(entities);
            if (autoSave)
            {
                await _dbContext.SaveChangesAsync(cancellationToken);
            }
        }
        public async Task<TEntity?> FindAsync(TKey id, CancellationToken cancellationToken = default)
        {
            return await _dbContext.Set<TEntity>().FindAsync(id, cancellationToken);
        }
        public async Task<TEntity?> FindAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default)
        {
            return await _dbContext.Set<TEntity>().AsNoTracking().FirstOrDefaultAsync(predicate, cancellationToken);
        }
        public async Task<List<TEntity>> GetListAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default)
        {
            return await _dbContext.Set<TEntity>().Where(predicate).ToListAsync(cancellationToken);
        }
        public async Task<List<TEntity>> GetListAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default, params Expression<Func<TEntity, object>>[] propertySelectors)
        {
            return await GetQueryableWithIncludes(propertySelectors).Where(predicate).ToListAsync(cancellationToken);
        }
        public async Task<List<TSelect>> SelectListAsync<TSelect>(Expression<Func<TEntity, bool>> predicate, Expression<Func<TEntity, TSelect>> selectPredicate, CancellationToken cancellationToken = default, params Expression<Func<TEntity, object>>[] propertySelectors)
        {
            return await GetQueryableWithIncludes(propertySelectors).Where(predicate).Select(selectPredicate).ToListAsync(cancellationToken);
        }
        public async Task<List<TSelect>> SelectListAsync<TSelect>(Expression<Func<TEntity, bool>> predicate, Expression<Func<TEntity, TSelect>> selectPredicate, CancellationToken cancellationToken = default)
        {
            return await GetQueryable().Where(predicate).Select(selectPredicate).ToListAsync(cancellationToken);
        }
        public async Task<(List<TSelect> items, long total)> SelectPageListAsync<TSelect>(Expression<Func<TEntity, bool>> predicate, Expression<Func<TEntity, TSelect>> selectPredicate, int skip, int take, string orderby = "Id", CancellationToken cancellationToken = default)
        {
            var query = GetQueryable().Where(predicate).Select(selectPredicate);
            var total = await query.LongCountAsync(cancellationToken);
            var items = await query.OrderBy(orderby)
                .Skip(skip).Take(take)
                .ToListAsync(cancellationToken);
            return (items, total);
        }
        public async Task<(List<TSelect> items, long total)> SelectPageListAsync<TSelect>(Expression<Func<TEntity, bool>> predicate, Expression<Func<TEntity, TSelect>> selectPredicate, int skip, int take, string orderby = "Id", CancellationToken cancellationToken = default, params Expression<Func<TEntity, object>>[] propertySelectors)
        {
            var query = GetQueryableWithIncludes(propertySelectors).Where(predicate).Select(selectPredicate);
            var total = await query.LongCountAsync(cancellationToken);
            var items = await query.OrderBy(orderby)
                .Skip(skip).Take(take)
                .ToListAsync(cancellationToken);
            return (items, total);
        }
        public async Task<(List<TEntity> items, long total)> GetPageListAsync(Expression<Func<TEntity, bool>> predicate, int skip, int take, string orderby = "Id", CancellationToken cancellationToken = default)
        {
            var query = GetQueryable().Where(predicate);
            var total = await query.LongCountAsync(cancellationToken);
            var items = await query.OrderBy(orderby)
                .Skip(skip).Take(take)
                .ToListAsync(cancellationToken);
            return (items, total);
        }
        public async Task<(List<TEntity> items, long total)> GetPageListAsync(Expression<Func<TEntity, bool>> predicate, 
            int skip, int take, string orderby = "Id", CancellationToken cancellationToken = default, params Expression<Func<TEntity, object>>[] propertySelectors)
        {
            var query = GetQueryableWithIncludes(propertySelectors).Where(predicate);
            var total = await query.LongCountAsync(cancellationToken);
            var items = await query.OrderBy(orderby)
                .Skip(skip).Take(take)
                .ToListAsync(cancellationToken);
            return (items, total);
        }

        public Task<bool> AnyAsync(CancellationToken cancellationToken = default)
        {
            return DbSet.AnyAsync(cancellationToken);
        }

        public Task<bool> AnyAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default)
        {
            return DbSet.AnyAsync(predicate, cancellationToken);
        }
        public IQueryable<TEntity> GetQueryable(bool noTracking = true)
        {
            if (noTracking)
            {
                return _dbContext.Set<TEntity>().AsNoTracking();
            }
            return _dbContext.Set<TEntity>();
        }
        public IQueryable<TEntity> GetQueryableWithIncludes(params Expression<Func<TEntity, object>>[] propertySelectors)
        {
            return Includes(GetQueryable(), propertySelectors);
        }

        public Expression<Func<TEntity, bool>> BuildPredicate(params (bool condition, Expression<Func<TEntity, bool>> predicate)[] conditionPredicates)
        {
            if(conditionPredicates == null || conditionPredicates.Length == 0)
            {
                throw new ArgumentNullException("conditionPredicates can not be null.");
            }
            Expression<Func<TEntity, bool>>? buildPredicate = null;
            foreach (var (condition, predicate) in conditionPredicates)
            {
                if (condition)
                {
                    if (buildPredicate == null)
                        buildPredicate = predicate;
                    else if(predicate != null)
                        buildPredicate = buildPredicate.And(predicate);
                }
            }
            if(buildPredicate == null)
            {
                buildPredicate = (o) => true;
            }
            return buildPredicate;
        }

        private static IQueryable<TEntity> Includes(IQueryable<TEntity> query, Expression<Func<TEntity, object>>[] propertySelectors)
        {
            if (propertySelectors != null && propertySelectors.Length > 0)
            {
                foreach (var propertySelector in propertySelectors)
                {
                    query = query.Include(propertySelector);
                }
            }

            return query;
        }
        public async Task<int> SaveChangeAsync(CancellationToken cancellationToken = default)
        {
            return await _dbContext.SaveChangesAsync(cancellationToken);
        }

        protected DbSet<TEntity> GetDbSet()
        {
            return _dbContext.Set<TEntity>();
        }

        protected IDbConnection GetDbConnection()
        {
            return _dbContext.Database.GetDbConnection();
        }

        protected IDbTransaction? GetDbTransaction()
        {
            return _dbContext.Database.CurrentTransaction?.GetDbTransaction();
        }

    }


    public class EFBasicRepository<TEntity> : EFBasicRepository<TEntity, object>, IBasicRepository<TEntity> where TEntity : class
    {
        public EFBasicRepository(WheelDbContext dbContext) : base(dbContext)
        {
        }
    }

这样我们CURD的操作的Repository就实现好了。
在列表查询和分页查询中,特意实现了SelectList,避免在某些场景下每次查询数据库都查询所有表字段却只使用了其中几个字段。也能有效提高查询性能。
这里分页查询特意使用了元组返回值,避免我们在分页查询时需要写两次操作,一次查总数,一次查真实数据。
还有实现了一个BuildPredicate来拼接我们的条件表达式,个人由于写太多WhereIf有点腻了,所以弄了个方法来干掉WhereIf,虽然这个方法可能不算完美。
实际操作如下图:image.png
当然BuildPredicate这个方法也不只有在查询方法中可以使用,在删除和更新方法中,我们同样可以根据条件这样拼接条件表达式。

添加到依赖注入

由于Autofac的RegisterAssemblyTypes不支持泛型接口注入,所以我们这里需要使用RegisterGeneric来注册我们的泛型仓储。
在WheelAutofacModule添加如下代码即可:

builder.RegisterGeneric(typeof(EFBasicRepository<,>)).As(typeof(IBasicRepository<,>)).InstancePerDependency();
builder.RegisterGeneric(typeof(EFBasicRepository<>)).As(typeof(IBasicRepository<>)).InstancePerDependency();

工作单元UOW

工作单元模式用于协调多个仓储的操作并确保它们在一个事务中进行。
这里我们来实现一个简单的工作单元模式。
首先实现一个DbTransaction:

namespace Wheel.Uow
{
    public interface IDbTransaction : IDisposable, IAsyncDisposable
    {
        Task<IDbContextTransaction> BeginTransactionAsync(CancellationToken cancellationToken = default);
        Task CommitAsync(CancellationToken cancellationToken = default);
        Task RollbackAsync(CancellationToken cancellationToken = default);
    }
    public class DbTransaction : IDbTransaction
    {
        private readonly DbContext _dbContext;

        IDbContextTransaction? CurrentDbContextTransaction;

        bool isCommit = false;
        bool isRollback = false;
        public DbTransaction(DbContext dbContext)
        {
            _dbContext = dbContext;
        }

        public async Task<IDbContextTransaction> BeginTransactionAsync(CancellationToken cancellationToken = default)
        {
            CurrentDbContextTransaction = await _dbContext.Database.BeginTransactionAsync(cancellationToken);
            return CurrentDbContextTransaction;
        }

        public async Task CommitAsync(CancellationToken cancellationToken = default)
        {
            await _dbContext.SaveChangesAsync();
            await _dbContext.Database.CommitTransactionAsync();
            isCommit = true;
            CurrentDbContextTransaction = null;
        }
        public void Commit()
        {
            _dbContext.Database.CommitTransaction();
            isCommit = true;
            CurrentDbContextTransaction = null;
        }

        public async Task RollbackAsync(CancellationToken cancellationToken = default)
        {
            await _dbContext.Database.RollbackTransactionAsync(cancellationToken);
            isRollback = true;
            CurrentDbContextTransaction = null;
        }
        public void Dispose()
        {
            if(CurrentDbContextTransaction != null)
            {
                if(!isCommit && !isRollback)
                {
                    Commit();
                }
                CurrentDbContextTransaction.Dispose();
            }
        }

        public async ValueTask DisposeAsync()
        {
            if(CurrentDbContextTransaction != null)
            {
                if (!isCommit && !isRollback)
                {
                    await CommitAsync();
                }
                await CurrentDbContextTransaction.DisposeAsync();
            }
        }

    }
}

DbTransaction负责操作开启事务,提交事务以及回滚事务。
实现UnitOfWork:

namespace Wheel.Uow
{
    public interface IUnitOfWork : IScopeDependency, IDisposable, IAsyncDisposable
    {
        Task<int> SaveChangesAsync(CancellationToken cancellationToken = default); 
        Task<IDbTransaction> BeginTransactionAsync(CancellationToken cancellationToken = default);
        Task CommitAsync(CancellationToken cancellationToken = default);
        Task RollbackAsync(CancellationToken cancellationToken = default);
    }
    public class UnitOfWork : IUnitOfWork
    {
        private readonly WheelDbContext _dbContext;
        private IDbTransaction? Transaction = null;

        public UnitOfWork(WheelDbContext dbContext)
        {
            _dbContext = dbContext;
        }

        public async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
        {
            return await _dbContext.SaveChangesAsync(cancellationToken);
        }
        public async Task<IDbTransaction> BeginTransactionAsync(CancellationToken cancellationToken = default)
        {
            Transaction = new DbTransaction(_dbContext);
            await Transaction.BeginTransactionAsync(cancellationToken);
            return Transaction;
        }
        public async Task CommitAsync(CancellationToken cancellationToken = default)
        {
            if(Transaction == null) 
            {
                throw new Exception("Transaction is null, Please BeginTransaction");
            }
            await Transaction.CommitAsync(cancellationToken);
        }

        public async Task RollbackAsync(CancellationToken cancellationToken = default)
        {
            if (Transaction == null)
            {
                throw new Exception("Transaction is null, Please BeginTransaction");
            }
            await Transaction.RollbackAsync(cancellationToken);
        }
        public void Dispose()
        {
            if(Transaction != null)
                Transaction.Dispose();
            _dbContext.Dispose();
        }

        public async ValueTask DisposeAsync()
        {
            if (Transaction != null)
                await Transaction.DisposeAsync();
            await _dbContext.DisposeAsync();
        }
    }
}

UnitOfWork负责控制DbTransaction的操作以及数据库SaveChanges。

EF拦截器

在数据库操作中,我们经常有一些数据是希望可以自动记录的,如插入数据自动根据当前时间给创建时间字段赋值,修改时自动根据当前时间修改最近更新时间字段。亦或者当需要软删除操作时,我们正常调用Delete方法,实际是修改表数据,而不是从表中物理删除数据。
添加软删除,创建时间以及更新时间接口:

public interface ISoftDelete
{
    /// <summary>
    /// 是否删除
    /// </summary>
    public bool IsDeleted { get; set; }
}
public interface IHasUpdateTime
{
    /// <summary>
    /// 最近修改时间
    /// </summary>
    DateTimeOffset UpdateTime { get; set; }
}
public interface IHasCreationTime
{
    /// <summary>
    /// 创建时间
    /// </summary>
    DateTimeOffset CreationTime { get; set; }
}

实现WheelEFCoreInterceptor,继承SaveChangesInterceptor,当调用SaveChanges方法是就会执行拦截器的逻辑操作。

namespace Wheel.EntityFrameworkCore
{
    /// <summary>
    /// EF拦截器
    /// </summary>
    public sealed class WheelEFCoreInterceptor : SaveChangesInterceptor
    {
        public override InterceptionResult<int> SavingChanges(DbContextEventData eventData, InterceptionResult<int> result)
        {
            OnSavingChanges(eventData);
            return base.SavingChanges(eventData, result);
        }

        public static void OnSavingChanges(DbContextEventData eventData)
        {
            ArgumentNullException.ThrowIfNull(eventData.Context);
            eventData.Context.ChangeTracker.DetectChanges();
            foreach (var entityEntry in eventData.Context.ChangeTracker.Entries())
            {
                if (entityEntry is { State: EntityState.Deleted, Entity: ISoftDelete softDeleteEntity })
                {
                    softDeleteEntity.IsDeleted = true;
                    entityEntry.State = EntityState.Modified;
                }
                if (entityEntry is { State: EntityState.Modified, Entity: IHasUpdateTime hasUpdateTimeEntity })
                {
                    hasUpdateTimeEntity.UpdateTime = DateTimeOffset.Now;
                }
                if (entityEntry is { State: EntityState.Added, Entity: IHasCreationTime hasCreationTimeEntity })
                {
                    hasCreationTimeEntity.CreationTime = DateTimeOffset.Now;
                }
            }
        }
    }
}

在AddDbContext添加我们的拦截器:

builder.Services.AddDbContext<WheelDbContext>(options =>
    options.UseSqlite(connectionString)
        .AddInterceptors(new WheelEFCoreInterceptor())
        .UseLazyLoadingProxies()
);

这样就完成了我们ORM的集成了。

轮子仓库地址https://github.com/Wheel-Framework/Wheel
欢迎进群催更。

image.png

与造轮子之ORM集成相似的内容:

造轮子之ORM集成

Dotnet的ORM千千万,还是喜欢用EF CORE 前面一些基础完成的差不多了,接下来可以集成数据库了,官方出品的ORM还是比较香。所以接下来就是来集成EF CORE。 安装包 首先我们需要安装一下EF CORE的NUGET包,有如下几个: Microsoft.EntityFrameworkCor

造轮子之消息实时推送

前面我们的EventBus已经弄好了,那么接下来通过EventBus来实现我们的消息推送就是自然而然的事情了。说到消息推送,很多人肯定会想到Websocket,既然我们使用Asp.net core,那么SignalR肯定是我们的首选。接下来就用SignalR来实现我们的消息实时推送。 Notific

造轮子之EventBus

前面基础管理的功能基本开发完了,接下来我们来优化一下开发功能,来添加EventBus功能。EventBus也是我们使用场景非常广的东西。这里我会实现一个本地的EventBus以及分布式的EventBus。分别使用MediatR和Cap来实现。 现在简单介绍一下这两者:MediatR是一个轻量级的中介

造轮子之菜单管理

前面完成了基础管理的相关API,接下来就得做一个菜单管理了,用于对接管理后台前端界面。 设计菜单结构 菜单是一个多级结构,所以我们得设计一个树形的。包含自己上级和下级的属性。同时预留Permission用于做可选的权限限制。 namespace Wheel.Domain.Menus { ///

造轮子之多语言管理

多语言也是我们经常能用到的东西,asp.net core中默认支持了多语言,可以使用.resx资源文件来管理多语言配置。但是在修改资源文件后,我们的应用服务无法及时更新,属实麻烦一些。我们可以通过扩展IStringLocalizer,实现我们想要的多语言配置方式,比如Json配置,PO 文件配置,E

造轮子之权限管理

上文已经完成了自定义授权策略,那么接下来就得完善我们的权限管理了。不然没有数据,如何鉴权~ 表设计 创建我们的表实体类: namespace Wheel.Domain.Permissions { public class PermissionGrant : Entity { public

造轮子之自定义授权策略

前面我们已经弄好了用户角色这块内容,接下来就是我们的授权策略。在asp.net core中提供了自定义的授权策略方案,我们可以按照需求自定义我们的权限过滤。这里我的想法是,不需要在每个Controller或者Action打上AuthorizeAttribute,自动根据ControllerName和

造轮子之asp.net core identity

在前面我们完成了应用最基础的功能支持以及数据库配置,接下来就是我们的用户角色登录等功能了,在asp.net core中原生Identity可以让我们快速完成这个功能的开发,在.NET8中,asp.net core identity支持了WebApi的注册登录。这让我们在WebApi中可以更爽快的使用

动手造轮子自己实现人工智能神经网络(ANN),解决鸢尾花分类问题Golang1.18实现

人工智能神经网络( Artificial Neural Network,又称为ANN)是一种由人工神经元组成的网络结构,神经网络结构是所有机器学习的基本结构,换句话说,无论是深度学习还是强化学习都是基于神经网络结构进行构建。关于人工神经元,请参见:人工智能机器学习底层原理剖析,人造神经元,您一定能看

前端文件上传的几种交互造轮子

前端文件上传本来是一个常规交互操作,没什么特殊性可言,但是最近在做文件上传,需要实现截图粘贴上传,去找了下有没有什么好用的组件,网上提供的方法有,但是没找完整的组件来支持cv上传,经过了解发现可以用剪贴板功能让自己的cv实现文件上传,于是自己就整合了目前几种文件上传的交互方式,码了一个支持cv的vue3文件上传组件(造个轮子)。