- Notifications
You must be signed in to change notification settings - Fork 893
Repository
中文 | English
FreeSql.Repository 实现了通用仓储层功能。FreeSql.Repository 参考 abp vnext 接口规范,实现仓储层(CURD)。
- Select/Attach 快照对象,Update 只更新变化的字段;
- Insert 插入数据,适配各数据库优化执行 ExecuteAffrows/ExecuteIdentity/ExecuteInserted;
- InsertOrUpdate 插入或更新;
- SaveMany 方法快速保存导航对象(一对多、多对多);
环境1:.NET Core 或 .NET 5.0+
dotnet add package FreeSql.Repository环境2、.NET Framework
Install-Package FreeSql.DbContextstatic IFreeSql fsql = new FreeSql.FreeSqlBuilder() .UseConnectionString(FreeSql.DataType.Sqlite, connectionString) .UseAutoSyncStructure(true) //自动迁移实体的结构到数据库 .Build(); //请务必定义成 Singleton 单例模式 public class Song { [Column(IsIdentity = true)] public int Id { get; set; } public string Title { get; set; } }方法1、IFreeSql 的扩展方法;
var curd = fsql.GetRepository<Song>();注意:Repository对象多线程不安全,因此不应在多个线程上同时对其执行工作。
- 不支持从不同的线程同时使用同一仓储实例
方法2、继承实现;
public class SongRepository : BaseRepository<Song, int> { public SongRepository(IFreeSql fsql) : base(fsql, null, null) {} //在这里增加 CURD 以外的方法 }方法3、依赖注入;
public void ConfigureServices(IServiceCollection services) { services.AddSingleton<IFreeSql>(Fsql); services.AddFreeRepository(null, this.GetType().Assembly); } //在控制器使用 public SongsController(IBaseRepository<Song> songRepository) { }依赖注入的方式可实现全局【过滤与验证】的设定,方便租户功能的设计;
更多资料:《过滤器、全局过滤器》
只更新变化的属性:
var repo = fsql.GetRepository<Topic>(); var item = repo.Where(a => a.Id == 1).First(); //此时快照 item item.Title = "newtitle"; repo.Update(item); //对比快照时的变化 //UPDATE `tb_topic` SET `Title` = @p_0 //WHERE (`Id` = 1)是不是觉得先查询再更新,啰嗦?
var repo = fsql.GetRepository<Topic>(); var item = new Topic { Id = 1 }; repo.Attach(item); //此时快照 item item.Title = "newtitle"; repo.Update(item); //对比快照时的变化 //UPDATE `tb_topic` SET `Title` = @p_0 //WHERE (`Id` = 1)repo.CompareState(item) 可获取 item 的状态变化信息
/// <summary> /// 比较实体,计算出值发生变化的属性,以及属性变化的前后值 /// </summary> /// <param name="newdata">最新的实体对象,它将与附加实体的状态对比</param> /// <returns>key: 属性名, value: [旧值, 新值]</returns> Dictionary<string, object[]> CompareState(TEntity newdata);repo.DbContextOptions.AuditValue 适合与 Ioc AddScoped 信息结合。
如下示例:使用仓储插入/更新时自动使用登陆信息
services.AddSingleton(fsql); services.AddScoped(r => new MyRepositoryOptions { AuditValue = e => { var user = r.GetService<User>(); if (user == null) return; if (e.AuditValueType == AuditValueType.Insert && e.Object is IEntityCreated obj1 && obj1 != null) { obj1.CreatedUserId = user.Id; obj1.CreatedUserName = user.Username; } if (e.AuditValueType == AuditValueType.Update && e.Object is IEntityModified obj2 && obj2 != null) { obj2.ModifiedUserId = user.Id; obj2.ModifiedUserName = user.Username; } } }); services.AddScoped(typeof(IBaseRepository<>), typeof(MyRepository<>)); services.AddScoped(typeof(IBaseRepository<,>), typeof(MyRepository<,>)); //以下实现 MyRepository class MyRepository<TEntity, TKey> : BaseRepository<TEntity, TKey> where TEntity : class { public MyRepository(IFreeSql fsql, MyRepositoryOptions options) : base(fsql, null, null) { if (options?.AuditValue != null) DbContextOptions.AuditValue += (_, e) => options.AuditValue(e); } } class MyRepository<TEntity> : MyRepository<TEntity, long> where TEntity : class { public MyRepository(IFreeSql fsql, MyRepositoryOptions options) : base(fsql, options) { } } class MyRepositoryOptions { public Action<DbContextAuditValueEventArgs> AuditValue { get; set; } }假设我们有User(用户)、Topic(主题)两个实体,定义了两个仓储:
var userRepository = fsql.GetRepository<User>(); var topicRepository = fsql.GetRepository<Topic>();在开发过程中,总是担心 topicRepository 的数据安全问题,即有可能查询或操作到其他用户的主题。因此我们在v0.0.7版本增加了 filter lambda 表达式参数。
var userRepository = fsql.GetRepository<User>(a => a.Id == 1); var topicRepository = fsql.GetRepository<Topic>(a => a.UserId == 1);- 在查询/修改/删除时附加此条件,从而达到不会修改其他用户的数据;
- 在添加时,使用表达式验证数据的合法性,若不合法则抛出异常;
FreeSql 提供 AsTable 分表的基础方法,GuidRepository 作为分存式仓储将实现了分表与分库(不支持跨服务器分库)的封装。
var logRepository = fsql.GetGuidRepository<Log>(null, oldname => $"{oldname}_{DateTime.Now.ToString("YYYYMM")}");上面我们得到一个日志仓储按年月分表,使用它 CURD 最终会操作 Log_201903 表。
注意事项:
- v0.11.12以后的版本可以使用 CodeFirst 迁移分表;
- 不可在分表分库的实体类型中使用《延时加载》;
更多请移步《分表分库》
SqlServer 提供的 output inserted 特性,在表使用了自增或数据库定义了默认值的时候,使用它可以快速将 insert 的数据返回。PostgreSQL 也有相应的功能,如此方便但不是每个数据库都支持。
当采用了不支持该特性的数据库(Sqlite/MySql/Oracle/达梦/南大通用/MsAccess),并且实体使用了自增属性,仓储批量插入将变为逐条执行,可以考虑以下改进:
- 使用 uuid 作为主键(即 Guid);
- 避免使用数据库的默认值功能;
请移步文档《联级保存》
| 属性 | 返回值 | 说明 |
|---|---|---|
| EntityType | Type | 仓储正在操作的实体类型,注意它不一定是 TEntity |
| UnitOfWork | IUnitOfWork | 正在使用的工作单元 |
| Orm | IFreeSql | 正在使用的 Orm |
| DbContextOptions | DbContextOptions | 正在使用的 DbContext 设置,修改设置不影响其他 |
| DataFilter | IDataFilter<TEntity> | 仓储过滤器,本对象内生效 |
| UpdateDiy | IUpdate<TEntity> | 准备更新数据,与仓储同事务 |
| Select | ISelect<TEntity> | 准备查询数据 |
| 方法 | 返回值 | 参数 | 说明 |
|---|---|---|---|
| AsType | void | Type | 改变仓储正在操作的实体类型 |
| Get | TEntity | TKey | 根据主键,查询数据 |
| Find | TEntity | TKey | 根据主键,查询数据 |
| Delete | int | TKey | 根据主键删除数据 |
| Delete | int | Lambda | 根据 lambda 条件删除数据 |
| Delete | int | TEntity | 删除数据 |
| Delete | int | IEnumerable<TEntity> | 批量删除数据 |
| DeleteCascadeByDatabase | List<object> | Lambda | 根据导航属性递归数据库删除数据 |
| Insert | - | TEntity | 插入数据,若实体有自增列,插入后的自增值会填充到实体中 |
| Insert | - | IEnumerable<TEntity> | 批量插入数据 |
| Update | - | TEntity | 更新数据 |
| Update | - | IEnumerable<TEntity> | 批量更新数据 |
| InsertOrUpdate | - | TEntity | 插入或更新数据 |
| FlushState | - | 无 | 清除状态管理数据 |
| Attach | - | TEntity | 附加实体到状态管理,可用于不查询就更新或删除 |
| Attach | - | IEnumerable<TEntity> | 批量附加实体到状态管理 |
| AttachOnlyPrimary | - | TEntity | 只附加实体的主键数据到状态管理 |
| SaveMany | - | TEntity, string | 保存实体的指定 ManyToMany/OneToMany 导航属性(完整对比) |
| BeginEdit | - | List<TEntity> | 准备编辑一个 List 实体 |
| EndEdit | int | 无 | 完成编辑数据,进行保存动作 |
状态管理,可实现 Update 只更新变化的字段(不更新所有字段),灵活使用 Attach 和 Update 用起来非常舒服。