{"id":13431379,"url":"https://github.com/sapiens/SqlFu","last_synced_at":"2025-03-16T11:31:34.768Z","repository":{"id":3327985,"uuid":"4371480","full_name":"sapiens/SqlFu","owner":"sapiens","description":"Fast and versatile .net core data mapper/micro-orm","archived":false,"fork":false,"pushed_at":"2024-03-29T12:15:05.000Z","size":18423,"stargazers_count":229,"open_issues_count":4,"forks_count":50,"subscribers_count":34,"default_branch":"master","last_synced_at":"2024-04-29T19:45:59.029Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sapiens.png","metadata":{"files":{"readme":"README.markdown","changelog":null,"contributing":null,"funding":null,"license":"License.txt","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":"2012-05-18T17:00:25.000Z","updated_at":"2024-06-18T15:17:32.602Z","dependencies_parsed_at":"2024-06-18T15:27:32.164Z","dependency_job_id":null,"html_url":"https://github.com/sapiens/SqlFu","commit_stats":{"total_commits":564,"total_committers":14,"mean_commits":"40.285714285714285","dds":"0.15248226950354615","last_synced_commit":"d2625ffd3320eef6f2c9846672f516e252caebd1"},"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sapiens%2FSqlFu","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sapiens%2FSqlFu/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sapiens%2FSqlFu/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sapiens%2FSqlFu/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sapiens","download_url":"https://codeload.github.com/sapiens/SqlFu/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243862998,"owners_count":20360246,"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":[],"created_at":"2024-07-31T02:01:02.651Z","updated_at":"2025-03-16T11:31:33.838Z","avatar_url":"https://github.com/sapiens.png","language":"C#","readme":"# Welcome to SqlFu\n\nSqlFu is a **_flexibile_** data mapper (aka micro-ORM) for .Net Core 3 Apache 2.0 license.\n\n[![Appveyor stat](https://ci.appveyor.com/api/projects/status/github/sapiens/sqlfu?svg=true)](https://ci.appveyor.com/project/sapiens/sqlfu) [![NuGet](https://img.shields.io/nuget/v/SqlFu.svg)](https://www.nuget.org/packages/sqlfu)\n\nLatest version: [5.0.0](https://github.com/sapiens/SqlFu/wiki/ChangeLog) \n  \n## Features\n* Think Ado.Net on steroids with the addition of a strongly typed query builder (not LINQ).\n* **Designed to increase developer productivity** while remaining simple to use and fast\n* Runs on any platform implementing NetStandard 2.1\n* All helpers have sync/async versions\n* Dependency Injection support for working with multiple databases/providers in the same app\n* Implicit transient errors resilience\n* Great for CRUD apps and for maintaining and querying the read model of CQRS apps\n* Supports: SqlServer 2012+ (Azure included), Sqlite.\n\n\n## How SqlFu should be used\n\nIt's important to understand that SqlFu is **NOT** a (light) ORM. While an ORM abstracts sql and gives us the illusion of working with a 'object database', SqlFu maps data from a query result to a POCO and provides helpers which use POCOs as a data source. Simply put, an object in SqlFu is a data _source_ or _destination_. There are no relational table to object and back mappings that magically generate sql. \n\nThe strongly typed helpers or sql builders are just that: a specialised string builder which uses expressions, there is no Linq involved. In SqlFu we think Sql but we write it mostly in C#. Think of SqlFu as a **powerful facade for Ado.Net**.\n\nUsually we use a POCO (a defined or anonymous type) to represent a table or a view. SqlFu helpers are flexible enough for most one table queries, but if you need to join tables, you should either write the sql as string (not really recommended) or create a db view (recommended) or a stored procedure.\n\nSqlFu is designed to be used in a cloud environment and it works great inside DDD/CQRS apps or simple CRUD apps.\n\n### Note for contributors\n\nPlease create your pull requests to target the \"v4-devel\" branch. \"Master\" is only for released code. Thank you.\n\n\n\n## Usage\n\n### New!!!\n\n```csharp\n\n//quick query using the default connection\nvar name=SqlFuManager.QueryOver\u003cMyObject\u003e().Select(d =\u003e d.FirstName,criteria: d =\u003e d.FirstName == \"John\").GetValue();\n\n\n//quick update\n//\"schema.table\" is implicit converted to `TableName` instance\n_db.UpdateFrom(new {Name=\"Foo\"},\"myTable\").Where(new{Id=2}).Execute();\n\n```\n\n### Config options\n```csharp\nLogManager.OutputToTrace();\n SqlFuManager.Configure(c =\u003e\n            {\n               //add the default profile (name is 'default')\n               c.AddProfile(new SqlServer2012Provider(SqlClientFactory.Instance.CreateConnection),cnx_string);              \n               \n               //add named profile\n               c.AddProfile(new SqlServer2012Provider(SqlClientFactory.Instance.CreateConnection),cnx_string,\"other\");              \n               \n               //register a type converter for query purposes, obj -\u003e Email\n               c.RegisterConverter(val=\u003enew Email(val.ToString()));\n               \n               //register a custom (manual) mapper\n               c.CustomMappers.Register(reader=\u003e new MyPoco(){ /* init from DbDataReader */});\n               \n               //set the table name to be used when dealing with this POCO. \n             \t\t   \n               c.ConfigureTableForPoco\u003cMyPoco\u003e(info=\u003e\t\t\t\t\t\t\t\t\t\t\t\n\t       {\n\t\tinfo.Table=new TableName(\"my_pocos\");\n\t\t//new in ver. 4.0.0 - additional info used by helpers\n\t\t//Any table name used by helpers will use it by default\n\t\tc.SetDefaultDbSchema(\"foo\");\n\n\t\t//the Insert helper uses it to return inserted id\n\t\t d.Property(f =\u003e f.Id).IsAutoincremented();\n\n\t\t //properties will be always be ignored\n\t\t d.IgnoreProperties(f=\u003ef.Ignored);\n\n\t\t //use it when you want to convert value just before writing to the db\n\t\t // store an enum as a string instead of the default int\n\t\t d.Property(f =\u003e f.Category)\n\t\t .BeforeWritingUseConverter(t =\u003e t.ToString())\n\t\t //the column name in the db is 'Categ'\n\t\t .MapToColumn(\"Categ\");\n\t\t\t\t\t}\n\t\t\t\t);\n               \n              //register a naming convention\n              c.AddNamingConvention(predicate,type=\u003e new TableName(type.Fullname));\n              \n              //used a predefined convention. PostsItem is considered to 'represent' the table/view \"Posts\"\n              c.AddSuffixTableConvention(suffix:\"Item\");\n              \n              //custom logging\n              c.OnException = (cmd,ex)=\u003e Logger.Error(cmd.FormatCommand(),ex);\n            });\n\n````\n**Notes**\n* You need at least one profile configured\n* Each profile is a combination of provider/connection string and it allows to use multiple databases\n* To support CoreClr, each provider needs a DBConnection factory injected. This means that when running on coreclr you need to also install the \"System.Data.SqlClient\" package. SqlFu is decoupled from a specific db provider.\n\n### Get Connection\n\nMost of the time you'll need to inject a db connection factory into your Repository/DAO/Query object . It's **always** better to do that instead of injecting a DbConnection. `IDbFactory` is the predefined factory abstraction in SqlFu.\n\n```csharp\n\npublic class MyRepository\n{\n    public MyRepository(IDbFactory getDb){}\n    \n    public void DoStuff()\n    {\n        using(var db=_getDb.Create())\n        {\n            //use db connection\n        }\n    }\n}\n\n//gets factory for the default profile\nvar factory=SqlFuManager.GetDbFactory();\n\n//get a specific profile\nvar factory=SqlFuManager.GetDbFactory(\"other\");\n\nvar repo=new MyRepository(factory);\n\n```\n### Working with multiple databases/providers\n\nLet's assume I need 2 connections in my app: one for db \"Main\", other for db \"History\". First we we declare specific interfaces that will be used by the objects which need db access, then add the profiles for each db.\nSqlFu automatically generates concrete classes deriving from `DbFactory` and implementing the interfaces. All factories are singletons.\n\n```csharp\n public interface IMainDb:IDbFactory\n{\n    \n}\n\npublic interface IHistoryDb:IDbFactory\n{\n    \n}\n\n//register the db profiles\n SqlFuManager.Configure(c =\u003e\n {\n     //default profile\n     c.AddProfile\u003cIMainDB\u003e(new SqlServer2012Provider(SqlClientFactory.Instance.CreateConnection),MainConnex);              \n     //history profile\n     c.AddProfile\u003cIHistoryDb\u003e(new SqlServer2012Provider(SqlClientFactory.Instance.CreateConnection),HistoryConnex,\"history\");              \n });\n\n\n//get main db factory singleton\nvar main=  SqlFuManager.GetDbFactory\u003cIMainDb\u003e();\n\n//get history db singleton\nvar history=SqlFuManager.GetDbFactory\u003cIHistoryDb\u003e();\n\n//register into DI Container to be injected in a service\n//autofac\nvar cb=new ContainerBuilder();\ncb.Register(c=\u003emain).As\u003cIMainDb\u003e().SingleInstance();\ncb.Register(c=\u003ehistory).As\u003cIHistoryDb\u003e().SingleInstance();\n\n//uses both main db and history db\npublic class MyService\n{\n    public MyService(IMainDb db,IHistoryDb) {}\n}\n\n```\n\n### Transient Errors Resilience\n\nIt's a common scenario, especially using a cloud based db like Azure Sql, to reach connections limit or the opening of a connection to timeout. \nSqlFu automatically employs a simple strategy to retry the operation a number of times. You can configure it like this:\n\n```csharp\n\tSqlFuManager.Configure(c=\u003e\n\t\t\t\t\t{\n\t\t\t\t\t\t//other options are available too\n\t\t\t\t\t\tc.ConfigureDefaultTransientResilience(f=\u003ef.MaxRetries=5);\n\n\t\t\t\t\t\t//if you want to implement your own strategy, you need to create a class implementing `IRetryOnTransientErrorsStrategy`\n\t\t\t\t\t\tc.TransientErrorsStrategyFactory=()=\u003enew MyStrategy();\n\t\t\t\t\t});\n\t\n```\n\n\n### CRUD Helpers\n\nAlmost all helpers have async counterparts\n\n```csharp\n\nDbConnection _db=dbFactory.Create();\n\n//insert\n_db.Insert(new User()\n            {\n                FirstName = \"John\",\n                LastName = \"Doe\"\n            });\n\n//optional, specify what column is PK\n c.ConfigureTableForPoco\u003cMyPoco\u003e(info=\u003e\t\t\t\t\t\t\t\t\t\t\t\n\t       {\n\t\t//the Insert helper uses it to return inserted id\n\t\t d.Property(f =\u003e f.Id).IsAutoincremented();\n\t}\n\n//insert with options\n_db.Insert(new \n            {\n                FirstName = \"John\",\n                LastName = \"Doe\",\n                Bla=0\n            }, cf =\u003e\n            {\n                cf.SetTableName(\"mytable\");\n\t\tc.IdentityColumn = \"Id\";\n                cf.Ignore(d=\u003ed.Bla);\n            });\n\n//Ignores unique key constraints. Useful when updating read models\n_db.InsertIgnore(new User()\n            {\n                FirstName = \"John\",\n                LastName = \"Doe\"\n            });\n\n\n\n//update\n_db.Update\u003cUser\u003e()\n.Set(c=\u003ec.FirstName,\"John\").Set(c=\u003ec.Posts,c.Posts+1)\n.Where(c=\u003ec.Id==userId)\n.Execute();\n\n//update from anonymous\n _db.UpdateFrom(\n                q =\u003e q.Data(new { Firstname = \"John3\", Id = 3 }).Ignore(d =\u003e d.Id)\n                ,o =\u003e o.SetTableName(\"users\")\n                )\n                .Where(d =\u003e d.Firstname == \"John\")\n                .Execute();\n\n\n\n//delete\n_db.DeleteFrom\u003cUser\u003e(d=\u003ed.Id==id);\n_db.DeleteFromAnonymous(\n    new {Category = \"\"}\n    , opt =\u003e opt.SetTableName(\"users\")\n    , d =\u003e d.Category == Type.Page.ToString());\n\n```\n\n### Queries\n\nSqlFu features a quite powerful and flexible query builder that you can use to query one table/view (use views or sprocs when you need joins).\nNote that it can be useful or a big PITA, in doubt go for the simplest thing.\n\n```csharp\n\n//alternative syntax\n_db.WithSql(q =\u003e q.From\u003cUser\u003e()\n            .Where(d=\u003ed.Id==id \u0026\u0026 !d.IsActive)\n            .OrderByIf(c=\u003einput.ShouldSort,d=\u003ed.Name)\n            .SelectAll())\n    .GetRows();\n\n\n //you can use pocos to create the sql but map the result to a different poco\n _db.QueryAs(q =\u003e q.From\u003cUser\u003e().SelectAll().MapTo\u003cOtherPoco\u003e());\n \n//returns one row only\n _db.QueryRow(q=\u003eq.From\u003cUser\u003e().SelectAll());\n _db.WithSql(q=\u003eq.From\u003cUser\u003e().SelectAll()).GetFirstRow();\n\n//returns one value\n _db.QueryValue(q=\u003eq.From\u003cUser\u003e().Select(d=\u003ed.Id));\n _db.WithSql(q=\u003eq.From\u003cUser\u003e().Select(d=\u003ed.Id)).GetValue();\n \n //returns a List\u003cint\u003e\n _db.QueryAs(q=\u003eq.From\u003cUser\u003e().Select(d=\u003ed.Id));\n \n //process a result set row by row. Useful when dealing with a big result set\n _db.QueryAndProcess(q=\u003eq.From\u003cUser\u003e().SelectAll(),user=\u003e{ \n   user.Name=user.Name.ToUpper();\n   return true;//continue processing\n   return false;//query ends here, no other results are read/mapped\n });\n _db.WithSql((q=\u003eq.From\u003cUser\u003e().SelectAll())\n\t.ProcessEachRow(user=\u003e{ \n\t\t   user.Name=user.Name.ToUpper();\n\t\t   return true;//continue processing\n\t\t   return false;//query ends here, no other results are read/mapped\n\t\t }).Execute();\n \n //query using interpolated strings . Variables are converted into query params\n _db.SqlTo\u003cUser\u003e($\"select * from users where id ={id}\").GetRows();          \n_db.SqlTo\u003cUser\u003e(q=\u003eq.Append(\"select * from users\").AppendIf(d=\u003eid\u003e0,$\" where id={id}\")).GetRows()\n\n //do a paged query, useful for pagination. Here we request page 2 with 30 results per page\n var result=_db.QueryPaged\u003cUser\u003e(q=\u003eq.From\u003cUser\u003e.SelectAll(),new Pagination(page:2,pageSize:30));\n //total existing users\n result.Count\n \n //result set with 30 users\n result.Items\n \n\n //execute some sql\n _db.Execute($\"delete from {_db.GetTableName\u003cUser\u003e()} where Id=@0\",userId);\n\n\n\n```\n\n**Notes**\n* The convention is that every extension method starting with `Query` uses the strongly typed sql builder.\n* `HasValueIn` is for `column in (values)` sql.\n* Any IEnumerable variable can use `Contains(column)` to generate `column in (values)` sql;\n* `Select` is about specifying the sql column and an implicit mapping to the projection, however `MapTo` applies after the sql has been built and the query executed.\n* String methods/properties support: Contains, Length, StartsWith, EndsWith, ToUpper, ToLower.\n\n### SProc Support\n\n```csharp\n//execute a sproc\n var result = _db.ExecuteSProc(s =\u003e\n              {\n                  s.ProcName = \"spTest\";\n                  s.Arguments = new { id = 47, _pout = \"\" };\n              });\nresult.ReturnValue.Should().Be(100);\nstring pout = r.OutputValues.pout;\n\n//execute a sproc which returns a result set\nvar res = _db.QuerySProc\u003cMyPoco\u003e(\"spTest\", new { id = 46, _pout = \"\" });\nres.ReturnValue.Should().Be(100);\n//do something with the result set List\u003cMyPoco\u003e\nreturn r.Result;\n  \n```\n\n**Notes**\n\n* Output arguments are identified by the `_` prefix.\n","funding_links":[],"categories":["Frameworks, Libraries and Tools","框架, 库和工具","ORM","C\\#","C# #"],"sub_categories":["ORM","对象关系映射ORM"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsapiens%2FSqlFu","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsapiens%2FSqlFu","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsapiens%2FSqlFu/lists"}