非常贴心的轮子 FreeSql

FreeSql 项目从2018年11月28日开发至今,版本已发布至 v0.3.12,版本规则:年数-月-日-当日版本号。目前主要包括 FreeSql、FreeSql.Repository 两个项目的维护和开发。这篇文章介绍有哪些贴心功能。

错误:传入的请求具有过多的参数。该服务器支持最多 2100 个参数。请减少参数的数目,然后重新发送该请求。

不知道其他 orm 批量添加实体到 sqlserver 有没有这个错误,FreeSql 不存在。

实体类配置

每款 orm 都会有自己一套实体类配置方法,当项目的实体被多个 orm 同时使用时将成为问题,因为不可能做多套配置,FreeSql 提供了以下几种的方法,免入侵式配置;

1、如果你从数据库生成的实体,FreeSql 提供 IsConfigEntityFromDbFirst 参数,可从数据库导入主键、自键等配置信息;

var orm = new FreeSql.FreeSqlBuilder()
    .UseConnectionString(FreeSql.DataType.MySql, "Data Source=127.0.0.1;Port=3306;User ID=root;Password=root;Initial Catalog=cccddd;Charset=utf8;SslMode=none;Max pool size=10")
    .UseAutoSyncStructure(true)

    //只需要在这里控制,默认为关闭状态
    .UseConfigEntityFromDbFirst(true) 

    .Build();

2、如果你已经使用 EF 建好了实体模式,FreeSql 提供了从 EF 元数据导入;

public static void ConfigEntity(this ICodeFirst codeFirst, IModel efmodel) {
    foreach (var type in efmodel.GetEntityTypes()) {
        codeFirst.ConfigEntity(type.ClrType, a => {
            //表名
            var relationalTableName = type.FindAnnotation("Relational:TableName");
            if (relationalTableName != null) {
                a.Name(relationalTableName.Value?.ToString() ?? type.ClrType.Name);
            }

            foreach (var prop in type.GetProperties()) {
                var freeProp = a.Property(prop.Name);
                //列名
                var relationalColumnName = prop.FindAnnotation("Relational:ColumnName");
                if (relationalColumnName != null) {
                    freeProp.Name(relationalColumnName.Value?.ToString() ?? prop.Name);
                }
                //主键
                freeProp.IsPrimary(prop.IsPrimaryKey());
                //自增
                freeProp.IsIdentity(
                    prop.ValueGenerated == ValueGenerated.Never ||
                    prop.ValueGenerated == ValueGenerated.OnAdd ||
                    prop.GetAnnotations().Where(z =>
                        z.Name == "SqlServer:ValueGenerationStrategy" && z.Value.ToString().Contains("IdentityColumn") //sqlserver 自增
                        || z.Value.ToString().Contains("IdentityColumn") //其他数据库实现未经测试
                    ).Any()
                );
                //可空
                freeProp.IsNullable(prop.AfterSaveBehavior != PropertySaveBehavior.Throw);
                //类型
                var relationalColumnType = prop.FindAnnotation("Relational:ColumnType");
                if (relationalColumnType != null) {
                    var dbType = relationalColumnType.ToString();
                    if (!string.IsNullOrEmpty(dbType)) {
                        var maxLength = prop.FindAnnotation("MaxLength");
                        if (maxLength != null)
                            dbType += $"({maxLength})";
                        freeProp.DbType(dbType);
                    }
                }
            }
        });
    }
}

3、如果你使用了其他 orm,FreeSql 提供 ConfigEntity,使用类似 2 的做法来完成配置导入;

事务

FreeSql 提供了同线程事务、对外开放事务。

同线程事务

假设用户购买了价值100元的商品:

第一步:扣余额;

第二步:扣库存;

第一步成功了,到了第二步发现库存不足时,事务可以回滚,扣余额的数据将不生效。

//假设已经有了其他wiki页的IFreeSql声明
orm.Transaction(() => {

    var affrows = orm.Update<User>().Set(a => a.Wealth - 100)
        .Where(a => a.Wealth >= 100)
        //判断别让用户余额扣成负数
        .ExecuteAffrows();
    if (affrows < 1) {
        throw new Exception("用户余额不足");
        //抛出异常,事务退出
    }

    affrows = orm.Update<Goods>().Set(a => a.Stock - 1)
        .Where(a => a.Stock > 0)
        //判断别让用库存扣成负数
        .ExecuteAffrows();
    if (affrows < 1) {
        throw new Exception("商品库存不足");
        //抛出异常,回滚事务,事务退出
        //用户余额的扣除将不生效
    }

    //程序执行在此处,说明都扣成功了,事务完成并提交
});

注意与说明:

1、数据库事务在线程挂载,每个线程只可开启一个事务连接,重复开启会获取线程已开启的事务;

2、在事务代码过程中,不可使用异步方法,包括FreeSql提供的数据库异步方法,否则线程将会切换事务不生效;

3、orm.Transaction 有防止死锁机制,60秒事务未结束的,将会被其他线程强行提交(不是回滚),可能造成不完整的事务,但仔细一想60秒还没完成的事务是什么原因呢?如果嫌60秒太少了可以在重载方法的参数中设置;

指定事务对象

除了上面提供的同线程事务外,FreeSql 还提供了指定事务对象的方法,将事务对象暴露给外部;

orm.Update<xxx>().WithTransaction(指定事务)
    .Set(a => a.Clicks + 1).ExecuteAffrows();

ISelect、IInsert、IUpdate、IDelete,都支持 WithTransaction 方法。

仓储Repository

dotnet add package FreeSql.Repository

1、IFreeSql 的扩展方法;

var curd1 = orm.GetRepository<Song, int>();
var curd2 = orm.GetGuidRepository<Song>();

2、继承现实;

public class SongRepository : BaseRepository<Song, int> {
    public SongRepository(IFreeSql orm) : base(orm) {}

    //在这里增加 CURD 以外的方法
}

3、Autofac 注入;

public IServiceProvider ConfigureServices(IServiceCollection services) {
    services.AddSingleton<IFreeSql>(orm);
    services.AddMvc();

    var builder = new ContainerBuilder();

    //示范全局过滤的仓储类注入,如果实体中不存在 Title 属性,则条件不生效
    builder.RegisterFreeRepositoryAddFilter<Song>(() => a => a.Title == DateTime.Now.ToString() + System.Threading.Thread.CurrentThread.ManagedThreadId);

    builder.Populate(services);
    var container = builder.Build();
    return new AutofacServiceProvider(container);
}

//在控制器使用
public SongsController(GuidRepository<Song> repos1, GuidRepository<xxxx> repos2) {
}

Autofac 注入方式实现了全局【过滤与验证】的设定,方便租户功能的设计;

表达式函数

In查询

var t1 = orm.Select<xxx>().Where(a => new[] { 1, 2, 3 }.Contains(a.testFieldInt)).ToSql();
//SELECT a.`Id`, a.`Clicks`, a.`TestTypeInfoGuid`, a.`Title`, a.`CreateTime` 
//FROM `tb_topic` a 
//WHERE (a.`Id` in (1,2,3))

查找今天创建的数据

var t2 = orm.Select<xxx>().Where(a => a.CreateTime.Date == DateTime.Now.Date).ToSql();

不提供 SqlFunc 之类的伪函数,所支持的类型基本都可以使用对应的表达式函数,例如 日期、字符串、IN查询、数组(PostgreSQL的数组)、字典(PostgreSQL HStore)等等。

安全性

1、避免死锁的事务,超时自动提交;

2、未设置条件的删除、更新不生效;

3、仓储提供 filter 验证数据,确保数据的安全性;

......

更多特性可前往 wiki 中心查看

github: https://github.com/2881099/FreeSql

wiki: https://github.com/2881099/FreeSql/wiki/

posted @ 2019-03-11 19:46  nicye  阅读(5128)  评论(4编辑  收藏  举报