Skip to content

DbContext

28810 edited this page Sep 29, 2019 · 30 revisions

FreeSql.DbContext 实现类似 EFCore 使用方法,跟踪对象状态,最终通过 SaveChanges 方法提交事务。

安装

dotnet add package FreeSql.DbContext

如何使用

0、通用方法,为啥是0???

using (var ctx = fsql.CreateDbContext()) {
    //var db1 = ctx.Set<Song>();
    //var db2 = ctx.Set<Tag>();

    var item = new Song { };
    ctx.Add(item);
    ctx.SaveChanges();
}

注意:DbContext 对象多线程不安全

1、在 OnConfiguring 方法上配置与 IFreeSql 关联

public class SongContext : DbContext {

    public DbSet<Song> Songs { get; set; }
    public DbSet<Song> 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<Tag> 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<Song> Songs { get; set; }
    public virtual ICollection<Tag> Tags { get; set; }
}

使用的时候与 EFCore 类似:

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、注入方式使用

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<IFreeSql>(Fsql);
    services.AddFreeDbContext<SongContext>(options => options.UseFreeSql(Fsql));
}

在 mvc 中获取:

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 为一次。

联级保存

class Cagetory
{
    public Guid Id { get; set; }
    public string Name { get; set; }

    [Navigate("CagetoryId")]
    public List<Goods> Goodss { get; set; }
}
class Goods
{
    public Guid Id { get; set; }
    public Guid CagetoryId { get; set; }
}

上面是【一对多】模型, Catetory 保存时可联级保存 Goodss 集合。出于使用安全考虑我们没做完整对比,只实现 Goodss 集合的添加或更新操作,所以不会删除 Goods 的数据。

完整对比的功能使用起来太危险,试想下面的场景:

  • 保存 Cagetory 的时候,Goodss 是个空列表,如何操作?记录全部删除?
  • 保存 Category 的时候,由于数据库中 Goodss 记录非常之多,那么只想保存 Goodss 部分数据,或者只需要添加,如何操作?

【多对多】模型下,我们对中间表的保存是完整对比操作,对外部实体的操作只作新增(注意不会更新)

  • 属性集合为空时,删除他们的所有关联数据(中间表)
  • 属性集合不为空时,与数据库存在的数据完全对比,计算出应该删除和添加的记录

如何关闭联级保存功能?

全局关闭:

fsql.SetDbContextOptions(opt => opt.EnableAddOrUpdateNavigateList = false);

局部关闭:

var repo = fsql.GetRepository<T>();
repo.DbContextOptions = new DbContextOptions { EnableAddOrUpdateNavigateList = false };

参考资料

Clone this wiki locally