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. 【Redis】基础命令

    声明:本篇文章参考于该作者的# Redis从入门到精通:中级篇,大家有兴趣,去关注一下. 1.字符串(String) String(字符串)是Redis中最简单的一种数据结构,和MemCache数据结 ...

  2. Web网页音视频通话之Webrtc相关操作(一)

    目录 打开摄像头/关闭摄像头 静音/解除静音 打开视频/关闭视频 截图且下载 打开摄像头/关闭摄像头 效果图 HTML <!DOCTYPE html> <html lang=&quo ...

  3. 从零实现的Chrome扩展

    从零实现的Chrome扩展 Chrome扩展是一种可以在Chrome浏览器中添加新功能和修改浏览器行为的软件程序,例如我们常用的TamperMonkey.Proxy SwitchyOmega.AdGu ...

  4. Java开发大型互联网-架构师必须掌握的分布式技术

    Java开发大型互联网-架构师必须掌握的分布式技术 摘要:在当今互联网行业,随着用户量和业务的不断增长,大型互联网系统的设计和开发已经成为了一项头等重要的任务.作为架构师,要能够应对这样的挑战,就必须 ...

  5. C语言基础--数组详细说明

    目录 一.什么是数组 二.一维数组 1.一维数组创建 2.一维数组的使用 2.1 索引值 2.2 遍历数组 2.3 如何使用sizeof()计算出数组的长度 三.二维数组 1.二维数组的创建 2.二维 ...

  6. Centos7 安装部署 Kubernetes(k8s) 高可用集群

    目录 一.系统环境 二.前言 三.Kubernetes(k8s)高可用简介 四.配置机器基本环境 五.部署haproxy负载均衡器 六.部署etcd集群 七.部署Kubernetes(k8s) mas ...

  7. altas2.1.0编译、安装、集成CDH6.3.2

    目录 altas2.1.0编译.安装.集成CDH6.3.2 一: Atlas源码下载 二: Atlas源码编译 1.修改altas项目主pom文件,即需要编译的CDH6.3.2对应版本信息 2.Atl ...

  8. OpenAI API访问速度不佳?试试用Vercel来加速!

    前言 众所周知,使用openAI API在国内访问体验并不佳,经常遇到访问较慢或者访问失败的问题.本文着重讲讲怎么解决这个问题,让我们日常开发和使用能够更方便的体验到AI带来的便利 为了帮大家省钱,也 ...

  9. (转)IBM Appscan9.0.3安全扫描简单安装、使用以及高危漏洞修复

    最近手上负责一个的项目要进行等保评测.请的第三方公司采用IBM Security AppScan Standard对项目进行安全测试.测试报告高危漏洞主要包含sql注入.sql盲注.跨站点脚本编制如下 ...

  10. 一文详解自然语言处理两大任务与代码实战:NLU与NLG

    自然语言处理(NLP)涵盖了从基础理论到实际应用的广泛领域,本文深入探讨了NLP的关键概念,包括词向量.文本预处理.自然语言理解与生成.统计与规则驱动方法等,为读者提供了全面而深入的视角. 作者 Te ...