FreeSql.DbContext 实现类似 EFCore 使用习惯,跟踪对象状态,最终通过 SaveChanges 方法提交事务。 ## 安装 > dotnet add package FreeSql.DbContext ## 如何使用 0、通用方法,为啥是0??? ``` using (var ctx = fsql.CreateDbContext()) { //var db1 = ctx.Set(); //var db2 = ctx.Set(); var item = new Song { }; ctx.Add(item); ctx.SaveChanges(); } ``` > 注意:DbContext 对象多线程不安全 1、在 OnConfiguring 方法上配置与 IFreeSql 关联 ```csharp public class SongContext : DbContext { public DbSet Songs { get; set; } public DbSet Tags { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder builder) { builder.UseFreeSql(dbcontext_01.Startup.Fsql); } } public class Song { [Column(IsIdentity = true)] public int Id { get; set; } public DateTime? Create_time { get; set; } public bool? Is_deleted { get; set; } public string Title { get; set; } public string Url { get; set; } public virtual ICollection Tags { get; set; } } public class Song_tag { public int Song_id { get; set; } public virtual Song Song { get; set; } public int Tag_id { get; set; } public virtual Tag Tag { get; set; } } public class Tag { [Column(IsIdentity = true)] public int Id { get; set; } public int? Parent_id { get; set; } public virtual Tag Parent { get; set; } public decimal? Ddd { get; set; } public string Name { get; set; } public virtual ICollection Songs { get; set; } public virtual ICollection Tags { get; set; } } ``` 使用的时候与 EFCore 类似: ```csharp long id = 0; using (var ctx = new SongContext()) { var song = new Song { }; await ctx.Songs.AddAsync(song); id = song.Id; var adds = Enumerable.Range(0, 100) .Select(a => new Song { Create_time = DateTime.Now, Is_deleted = false, Title = "xxxx" + a, Url = "url222" }) .ToList(); await ctx.Songs.AddRangeAsync(adds); for (var a = 0; a < adds.Count; a++) adds[a].Title = "dkdkdkdk" + a; ctx.Songs.UpdateRange(adds); ctx.Songs.RemoveRange(adds.Skip(10).Take(20).ToList()); //ctx.Songs.Update(adds.First()); adds.Last().Url = "skldfjlksdjglkjjcccc"; ctx.Songs.Update(adds.Last()); //throw new Exception("回滚"); await ctx.SaveChangesAsync(); } ``` 2、注入方式使用 ```csharp public void ConfigureServices(IServiceCollection services) { services.AddSingleton(Fsql); services.AddFreeDbContext(options => options.UseFreeSql(Fsql)); } ``` 在 mvc 中获取: ```csharp IFreeSql _orm; public ValuesController(SongContext songContext) { } ``` ## 优先级 OnConfiguring > AddFreeDbContext ## 乐观锁 更新实体数据,在并发情况下极容易造成旧数据将新的记录更新。FreeSql 核心部分已经支持乐观锁。 乐观锁的原理,是利用实体某字段,如:long version,更新前先查询数据,此时 version 为 1,更新时产生的 SQL 会附加 where version = 1,当修改失败时(即 Affrows == 0)抛出异常。 每个实体只支持一个乐观锁,在属性前标记特性:[Column(IsVersion = true)] 即可。 > 无论是使用 FreeSql/FreeSql.Repository/FreeSql.DbContext,每次更新 version 的值都会增加 1 ## 说明 - DbContext 操作的数据在最后 SaveChanges 时才批量保存; - DbContext 内所有操作,使用同一个事务; - 当实体存在自增时,或者 Add/AddRange 的时候主键值为空,会提前开启事务; - 支持同步/异步方法; ## 合并机制 db.Add(new Xxx()); db.Add(new Xxx()); db.Add(new Xxx()); 这三步,会合并成一个批量插入的语句执行,前提是它们没有自增属性。 适用 Guid 主键,Guid 主键的值不用设置,交给 FreeSql 处理即可,空着的 Guid 主键会在插入时获取有序不重值的 Guid 值。 又比如: db.Add(new Xxx()); db.Add(new Xxx()); db.Update(xxx); db.Add(new Xxx()); Guid Id 的情况下,执行三次命令:前两次插入合并执行,update 为一次,后面的 add 为一次。 ## 联级保存 请移步文档[《联级保存》](https://github.com/2881099/FreeSql/wiki/%e8%81%94%e7%ba%a7%e4%bf%9d%e5%ad%98) ## 实体变化事件 全局设置: ```csharp fsql.SetDbContextOptions(opt => { opt.OnEntityChange = report => { Console.WriteLine(report); }; }); ``` 单独设置 DbContext 或者 UnitOfWork: ```csharp var ctx = fsql.CreateDbContext(); ctx.Options.OnEntityChange = report => { Console.WriteLine(report); }; var uow = fsql.CreateUnitOfWork(); uow.OnEntityChange = report => { Console.WriteLine(report); }; ``` 参数 report 是一个 List 集合,集合元素的类型定义如下: ```csharp public class ChangeInfo { public object Object { get; set; } public EntityChangeType Type { get; set; } } public enum EntityChangeType { Insert, Update, Delete, SqlRaw } ``` | 变化类型 | 说明 | | -- | -- | | Insert | 实体对象被插入 | | Update | 实体对象被更新 | | Delete | 实体对象被删除 | | SqlRaw | 执行了SQL语句 | SqlRaw 目前有两处地方比较特殊: - 多对多联级更新导航属性的时候,对中间表的全部删除操作; - 通用仓储类 BaseRepository 有一个 Delete 方法,参数为表达式,而并非实体; ```csharp int Delete(Expression> predicate); ``` DbContext.SaveChanges,或者 Repository 对实体的 Insert/Update/Delete,或者 UnitOfWork.Commit 操作都会最多触发一次该事件。