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,虽然这个方法可能不算完美。
实际操作如下图:
当然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的集成了。

欢迎进群催更。

造轮子之ORM集成的更多相关文章

  1. 手动造轮子——为Ocelot集成Nacos注册中心

    前言     近期在看博客的时候或者在群里看聊天的时候,发现很多都提到了Ocelot网关的问题.我之前也研究过一点,网关本身是一种通用的解决方案,主要的工作就是拦截请求统一处理,比如认证.授权.熔断. ...

  2. 避免重复造轮子的UI自动化测试框架开发

    一懒起来就好久没更新文章了,其实懒也还是因为忙,今年上半年的加班赶上了去年一年的加班,加班不息啊,好了吐槽完就写写一直打算继续的自动化开发 目前各种UI测试框架层出不穷,但是万变不离其宗,驱动PC浏览 ...

  3. 重复造轮子感悟 – XLinq性能提升心得

    曾经的两座大山 1.EF 刚接触linq那段时间,感觉这家伙好神奇,语法好优美,好厉害.后来经历了EF一些不如意的地方,就想去弥补,既然想弥补,就必须去了解原理.最开始甚至很长一段时间都搞不懂IQue ...

  4. GitHub Android 最火开源项目Top20 GitHub 上的开源项目不胜枚举,越来越多的开源项目正在迁移到GitHub平台上。基于不要重复造轮子的原则,了解当下比较流行的Android与iOS开源项目很是必要。利用这些项目,有时能够让你达到事半功倍的效果。

    1. ActionBarSherlock(推荐) ActionBarSherlock应该算得上是GitHub上最火的Android开源项目了,它是一个独立的库,通过一个API和主题,开发者就可以很方便 ...

  5. 除非你是BAT,前端开发中最好少造轮子

    站在前人的肩膀上 HTML.CSS.JavaScript是前端的根基,这是无可否认的事实.正如一辆车当然都是由一堆钢板和螺钉组成的,但是现在还有人拎着个锤子敲敲打打的造车吗?李书福说过,“汽车不过是四 ...

  6. Meteva——让预报检验不再重复造轮子

    更多精彩,请点击上方蓝字关注我们! 检验是什么?****预报准确率的客观表达 说到天气预报,你最先会想到什么? 早上听了预报,带了一天伞却没下一滴雨的调侃? 还是 "蓝天白云晴空万里突然暴风 ...

  7. 54 个官方 Spring Boot Starters 出炉!别再重复造轮子了…….

    在之前的文章,栈长介绍了 Spring Boot Starters,不清楚的可以点击链接进去看下. 前段时间 Spring Boot 2.4.0 也发布了,本文栈长再详细总结下最新的 Spring B ...

  8. 【疯狂造轮子-iOS】JSON转Model系列之二

    [疯狂造轮子-iOS]JSON转Model系列之二 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 上一篇<[疯狂造轮子-iOS]JSON转Model系列之一> ...

  9. 【疯狂造轮子-iOS】JSON转Model系列之一

    [疯狂造轮子-iOS]JSON转Model系列之一 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 之前一直看别人的源码,虽然对自己提升比较大,但毕竟不是自己写的,很容易遗 ...

  10. h5engine造轮子

    基于学习的造轮子,这是一个最简单,最基础的一个canvas渲染引擎,通过这个引擎架构,可以很快的学习canvas渲染模式! 地址:https://github.com/RichLiu1023/h5en ...

随机推荐

  1. C# Collections

    1. Generic 1.1 List<T> No need to say this is the most commonly used data structure in C# coll ...

  2. 【后端面经-Java】AQS详解

    目录 1. AQS是什么? 2. AQS核心思想 2.1 基本框架 2.1.1 资源state 2.1.2 CLH双向队列 2.2 AQS模板 3. 源码分析 3.1 acquire(int) 3.1 ...

  3. [渗透测试]—7.1 漏洞利用开发和Shellcode编写

    在本章节中,我们将学习漏洞利用开发和Shellcode编写的基本概念和技巧.我们会尽量详细.通俗易懂地讲解,并提供尽可能多的实例. 7.1 漏洞利用开发 漏洞利用开发是渗透测试中的高级技能.当你发现一 ...

  4. RabbitMQ 多消费者 使用单信道和多信道区别

    RabbitMQ 多个消费者共用一个信道实例 与 每个消费者使用不同的信道实例 区别: 1. 多个消费者共用一个信道实例:这种方式下,多个消费者共享同一个信道实例来进行消息的消费. 优点:这样可以减少 ...

  5. MASA Blazor中MSwitch如何实现二次确认

    <MSwitch @bind-Value="switch" Readonly OnClick="OnClick"> </MSwitch> ...

  6. 王道oj/problem16

    网址:http://oj.lgwenda.com/problem/16 思路:都在注释里,注意增删查的参数以及停止条件 代码: #define _CRT_SECURE_NO_WARNINGS#incl ...

  7. Java程序员的MacBookPro(14寸M1)配置备忘录

    欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 本篇概览 欣宸的月俸虽然很低,但还是咬着牙攒够银子,入 ...

  8. [mysql]安全加固

    前言 因等保安全的要求,需要对MySQL用户密码和登录策略进行安全加固,以满足以下需求: 密码至少8位,包含大小写字母.数字和特殊字符. 当密码登录失败一定次数后锁定账户. 密码90天过期 本文使用的 ...

  9. buu-(ACTF新生赛2020)usualCrypt

    base64的常用套路了 文件直接给base,我大胆盲猜base64: 先进sub-401080函数康康: 先看byte-40e0a0 这个很明显了,然后看上面的函数 进这连个地址发现是base64加 ...

  10. valgrind 配合 gdb 调试程序

    在实际研发过程中,可能会遇到过这样的问题:测试通过 valgrind 验证当前代码存在变量未初始化的问题,但仅通过 valgrind 测试报告,研发无法确认具体的应用场景.本文将通过 valgrind ...