{"id":19449576,"url":"https://github.com/soul-soft/soul.sqlbatis","last_synced_at":"2025-04-25T03:31:54.895Z","repository":{"id":177480901,"uuid":"657000044","full_name":"soul-soft/Soul.SqlBatis","owner":"soul-soft","description":"soul.sqlbatis","archived":false,"fork":false,"pushed_at":"2025-04-19T12:30:01.000Z","size":2366,"stargazers_count":10,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-19T16:46:44.323Z","etag":null,"topics":["database","expression","orm","sqlbatis"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/soul-soft.png","metadata":{"files":{"readme":"README-cn.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-06-22T05:18:16.000Z","updated_at":"2025-04-19T12:30:04.000Z","dependencies_parsed_at":null,"dependency_job_id":"ac71668a-a599-45b1-a7f7-3399c1507243","html_url":"https://github.com/soul-soft/Soul.SqlBatis","commit_stats":null,"previous_names":["soul-soft/soul.sqlbatis"],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/soul-soft%2FSoul.SqlBatis","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/soul-soft%2FSoul.SqlBatis/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/soul-soft%2FSoul.SqlBatis/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/soul-soft%2FSoul.SqlBatis/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/soul-soft","download_url":"https://codeload.github.com/soul-soft/Soul.SqlBatis/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250747968,"owners_count":21480761,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["database","expression","orm","sqlbatis"],"created_at":"2024-11-10T16:33:00.438Z","updated_at":"2025-04-25T03:31:54.190Z","avatar_url":"https://github.com/soul-soft.png","language":"C#","readme":"# Soul.SqlBatis\n\n* 配置灵活简单，支持linq+sql，支持实体更改跟踪\n\n* 支持个性化，具体参考源码\n\n* 源码简短易懂只有4000左右行代码（算法+数据结构）\n\n* 转载格式必须注明：【作者：花间岛，原包：Soul.SqlBatis】\n\n* 作者保留最终所有权，开源协议采用MIT\n\n## 配置DbContext\n\n```` C#\nvar context = new MyDbContext(configure =\u003e\n{\n    //设置日志\n    configure.UseLogger((sql, param) =\u003e\n    {\n        Console.WriteLine(sql);\n        Debug.WriteLine(sql);\n    });\n    //启用查询跟踪\n    configure.UseQueryTracking();\n    //设置连接对象\n    configure.UseConnection(new MySqlConnection(\"Server=127.0.0.1;User ID=root;Password=1024;Database=test\"));\n});\n````\n\n## 配置Model\n\n1. 建议一定要提供一个无参构造器\n2. 默认认为名为Id的字段为主键和自增列\n3. 使用[NotIdentity]可以移除名为Id列的自增特征\n4. 如需个性化，请实现IModel接口\n\n\n```` C#\n\npublic class Student : Entity\n{\n    [Column(\"id\")]\n    public int Id { get; set; }\n\n    [Column(\"name\")]\n    public string Name { get; set; }\n\n    [Column(\"address\")]\n    public Address Address { get; set; }\n\n    [Column(\"create_time\")]\n    public DateTime? CreateTime { get; set; }\n}\n\npublic class Address(string cityName, string areaName)\n{\n    public string CityName { get; } = cityName;\n    public string AreaName { get; } = areaName;\n}\n````\n\n## 查询语法\n\n### 列表\n\n```` C#\nvar list = context.Set\u003cStudent\u003e().ToList();\nvar (list, total) = context.Set\u003cStudent\u003e().ToPageList(1, 10);\n````\n\n### 统计\n\n```` C#\nvar count = context.Set\u003cStudent\u003e().Count();\nvar sum = context.Set\u003cStudent\u003e().Sum(a =\u003e a.Id);\nvar min = context.Set\u003cStudent\u003e().Min(a =\u003e a.Id);\nvar max = context.Set\u003cStudent\u003e().Max(a =\u003e a.Id);\nvar has = context.Set\u003cStudent\u003e().Any(a =\u003e a.Id \u003e 10);\nvar avg = context.Set\u003cStudent\u003e().Average(a =\u003e a.Id);\n````\n\n### IN查询\n\n```` C#\nvar ids = new List\u003cint\u003e() {1, 2, 3};\nvar list = context.Set\u003cStudent\u003e()\n    .Where(a =\u003e ids.Contains(a.Id))\n    .ToList();\nvar list = context.Set\u003cStudent\u003e()\n    .Where(a =\u003e DbOps.In(a.Id, ids))\n    .ToList();\nvar list = context.Set\u003cStudent\u003e()\n    .Where(a =\u003e DbOps.In(a.Id, 1, 2, 3))\n    .ToList();\nvar list = context.Set\u003cStudent\u003e()\n    .Where(a =\u003e DbOps.InSet(a.Id, \"1,2,3\"))\n    .ToList();\nvar list = context.Set\u003cStudent\u003e()\n    .Where(a =\u003e DbOps.InSub(a.Id, \"SELECT stu_id FROM grades WHERE level = 1\"))\n    .ToList();\n````\n\n### 参数化查询\n\n```` C#\nvar parameter = new DynamicParameters();\nparameter.Add(\"Level\", 1);\nvar list = context.Set\u003cStudent\u003e(parameter)\n    .Where(a =\u003e DbOps.InSub(a.Id, \"SELECT stu_id FROM grades WHERE level = @Level\"))\n    .ToList();\n````\n\n### 查询复用\n\n```` C#\n//函数1：可以封装到一个函数里，用于复用\nvar parameter = new DynamicParameters();\nparameter.Add(\"Level\", 1);\nvar query = context.Set\u003cStudent\u003e(parameter)\n    .Where(a =\u003e DbOps.InSub(a.Id, \"SELECT stu_id FROM grades WHERE level = @Level\"))\n    .OrderBy(a =\u003e a.Id);\n\n//函数2：列表查询\nvar (list, total) = query.OrderBy(a =\u003e a.Id).ToPageResult(1, 20);\n\n//函数3：统计查询\nvar (sb, parameters) = query.As(\"stu\").Build();\nvar view = $@\"\nSELECT\n    stu.id,\n    stu.name,\n    math_avg\nFROM\n    student AS stu\nLEFT JOIN (\n    SELECT\n        stu_id,\n        AVG(math) math_avg\n    FROM\n        student_score\n    GROUP BY\n        stu_id\n) AS sc ON stu.id = sc.stu_id\n{sb.WhereSql}\n{sb.OrderSql}\n\";\nvar list = context.Command.Query\u003cStudentAvgDto\u003e(view, parameters);\n````\n\n### SqlBuilder\n\n```` C#\n//查询参数\nvar req  = new \n{\n    Level = (int?)1,\n    StartTime = (DateTime?)DateTime.Now,\n    EndTime = (DateTime?)null\n};\n\n//动态参数\nvar parameter = new DynamicParameters();\nparameter.Add(req);\n\n//查询主体\nvar sb = new SqlBuilder();\nsb.Where(\"math_avg \u003e 89\", req.Level != null);\nsb.Order(\"math_avg_ DESC\");\nsb.Page(1, 10);\n//构建成绩动态查询\nvar sbScore = new SqlBuilder();\nsbScore.Where(\"create_time \u003e= @StartTime\" , req.StartTime != null);\nsbScore.Where(\"create_time \u003c= @EndTime\", req.EndTime != null);\nvar view = $@\"\nSELECT\n    stu.id,\n    stu.name,\n    math_avg\nFROM\n    student AS stu\nLEFT JOIN (\n    SELECT\n        stu_id,\n        AVG(math) math_avg\n    FROM\n        student_score\n    {sbScore.WhereSql}        \n    GROUP BY\n        stu_id\n) AS sc ON stu.id = sc.stu_id\n{sb.WhereSql}\n{sb.OrderSql}\n{sb.LimitSql}\n/**计数语句**/\n;SELECT \n    COUNT(**)\nFROM\n    student AS stu\nLEFT JOIN (\n    SELECT\n        stu_id,\n        AVG(math) math_avg\n    FROM\n        student_score\n    {sbScore.WhereSql}        \n    GROUP BY\n        stu_id\n) AS sc ON stu.id = sc.stu_id\n{sb.WhereSql}\n\";\n//发起查询\nusing(var mutil = context.Command.QueryMultiple(view, parameters))\n{\n    var list = mutil.Read\u003cStudentAvgDto\u003e();\n    var total = mutil.ReadFirst\u003cint\u003e();\n}\n````\n\n\n## 更新查询\n\n```` C#\n var f = context.Set\u003cStudent\u003e()\n    .Where(a =\u003e a.Id == 1)\n    .ExecuteUpdate(setters =\u003e setters\n        .SetProperty(a =\u003e a.Name, \"zs\")\n        .SetProperty(a =\u003e a.State, a =\u003e a.State + 1));\n````\n\n## 删除查询\n\n```` C#\n var f = context.Set\u003cStudent\u003e()\n    .Where(a =\u003e a.Id == 1)\n    .ExecuteDelete();\n````\n\n## 自定义函数\n\n1. 自定义函数用于对数据库函数进行映射\n2. 自定义函数必须定义在静态类中，只需声明无需实现\n3. 函数定义的类或者函数自身带有[DbFunction]特性\n4. 支持参数模板化Format。Format里花括号里是参数占位标记\n5. 函数名默认为最终的数据库函数，可以通过[DbFunction(Name = \"COUNT\")]指定\n\n```` C#\n[DbFunction]\npublic static class DbFunc\n{\n    [DbFunction(Format = \"*\")]\n    public static int Count()\n    {\n        throw new NotImplementedException();\n    }\n\n    [DbFunction(Name = \"COUNT\", Format = \"DISTINCT {column}\")]\n    public static int DistictCount\u003cT\u003e(T column)\n    {\n        throw new NotImplementedException();\n    }\n\n    public static int Count\u003cT\u003e(T column)\n    {\n        throw new NotImplementedException();\n    }\n\n    public static T IF\u003cT\u003e(bool column, T value1, T value2)\n    {\n        throw new NotImplementedException();\n    }\n}\n\nvar list = context.Set\u003cStudent\u003e()\n    .Select(s =\u003e DbFunc.IF(s.State \u003e 10, \"A\", \"S\"))\n    .ToList();\n````\n\n## 自定义类型映射\n\n### 方式一\n\n1. 通过UseTypeMapper方式是通过成员的属性类型来查找的，即UseTypeMapper方法返回bool表示用于处理属性类型是bool的情况\n2. 无需处理Nullable情况，框架内部会自动处理\n3. 方式的优点是配置简单，确定是不够灵活，缺失上下文信息\n4. 该方式优先级低于工程模式，高于默认的模式\n\n```` C#\nvar context = new MyDbContext(configure =\u003e\n{\n    configure.UseEntityMapper(configureOptions =\u003e\n    {\n        //处理bool类型\n        configureOptions.UseTypeMapper((record, i) =\u003e\n        {\n            var result = record.GetInt16(i);\n            return result == 0 ? false : true;\n        });\n        //处理string\n        configureOptions.UseTypeMapper((record, i) =\u003e\n        {\n            return record.GetString(i);\n            throw new InvalidOperationException();\n        });\n        //处理timeSpan\n        configureOptions.UseTypeMapper((record, i) =\u003e\n        {\n            if (record is MySqlDataReader reader)\n            {\n                return reader.GetTimeSpan(i);\n            }\n            throw new InvalidOperationException();\n        });\n        //处理bytes\n        configureOptions.UseTypeMapper((record, i) =\u003e\n        {\n            var buffer = new byte[1024];\n            var count = record.GetBytes(i, 0, buffer, 0, buffer.Length);\n            var span = new Span\u003cbyte\u003e(buffer, 0, (int)count);\n            return span.ToArray();\n        });\n    });\n    configure.UseConnection(new MySqlConnection(\"Server=127.0.0.1;User ID=root;Password=1024;Database=test\"));\n});\n````\n\n### 方式二\n\n1. 可以通过工程模式来进行配置，工厂模式更加的灵活\n2. 方式一和方式二可以同时使用，但是方式二的优先级高于方式一\n3. 返回的TypeMapper必须是一个静态函数，且不能是泛型方法，如果是泛型方法应该make成非泛型方法\n4. 参数的个数只有两个且第一个的类型必须是IDataRecord，第二必须是int\n5. 不要在映射器方法内写无关代码来影响性能\n6. 函数的返回类型，必须要和MemberType一致\n\n```` C#\npublic class TypeMapperFactory : ITypeMapperFactory\n{\n    //必须是静态函数\n    public static T StringToJson\u003cT\u003e(IDataRecord record, int i)\n    {\n        return JsonSerializer.Deserialize\u003cT\u003e(record.GetString(i),new JsonSerializerOptions \n        {\n            PropertyNamingPolicy = JsonNamingPolicy.CamelCase,\n        })\n        ?? throw new NullReferenceException();\n    }\n\n    public static T StringToString(IDataRecord record, int i)\n    {\n        return record.GetString(i);\n    }\n\n    public static T BytesToString(IDataRecord record, int i)\n    {\n        using(var ms = new MemoryStream())\n        {\n            while(true)\n            {\n                var buffer = new int[1024 * 16];\n                var count = (int)record.GetBytes(i, 0, buffer, 0, buffer.Length);\n                if(count \u003e 0)\n                    ms.Write(buffer, 0, count);\n                else\n                    break;\n            }\n            return Encoding.UTF8.GetString(ms.ToArray());\n        }\n    }\n\n    public MethodInfo? GetTypeMapper(TypeMapperContext context)\n    {\n        //通过这个来区分使用那个映射器，如果DB不支持json类型，那么可以给字段添加注解，或者给类型添加注解，用于判断\n        if (\"json\".Equals(context.FieldTypeName, StringComparison.OrdinalIgnoreCase) \u0026\u0026 context.FieldType == typeof(string) \u0026\u0026 context.MemberType != typeof(string))\n        {\n            return GetType().GetMethod(nameof(StringToJson))!.MakeGenericMethod(context.MemberType);\n        }\n        \n        //string to string\n        if(context.FieldType == typeof(string) \u0026\u0026 context.MemberType == typeof(string))\n            return GetType().GetMethod(nameof(StringToString));\n        \n        //byte[] to string\n        if(context.FieldType == typeof(byte[]) \u0026\u0026 context.MemberType == typeof(string))\n            return GetType().GetMethod(nameof(BytesToString));\n        return null;\n    }\n}\n//应用类型映射工厂\nvar context = new MyDbContext(configure =\u003e\n{\n    configure.UseEntityMapper(configureOptions =\u003e\n    {\n          configureOptions.TypeMapperFactory = new TypeMapperFactory();\n    });\n    configure.UseConnection(new MySqlConnection(\"Server=127.0.0.1;User ID=root;Password=1024;Database=test\"));\n});\n````","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsoul-soft%2Fsoul.sqlbatis","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsoul-soft%2Fsoul.sqlbatis","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsoul-soft%2Fsoul.sqlbatis/lists"}