{"id":33191331,"url":"https://github.com/cmeeren/Facil","last_synced_at":"2025-11-21T00:02:07.816Z","repository":{"id":38369131,"uuid":"321706894","full_name":"cmeeren/Facil","owner":"cmeeren","description":"Facil generates F# data access source code from SQL queries and stored procedures. Optimized for developer happiness.","archived":false,"fork":false,"pushed_at":"2025-02-12T11:49:45.000Z","size":1363,"stargazers_count":149,"open_issues_count":7,"forks_count":7,"subscribers_count":7,"default_branch":"main","last_synced_at":"2025-08-30T13:28:51.144Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"F#","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/cmeeren.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":".github/CONTRIBUTING.md","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":"2020-12-15T15:20:09.000Z","updated_at":"2025-06-19T14:42:49.000Z","dependencies_parsed_at":"2023-12-13T13:41:58.329Z","dependency_job_id":"587c9465-ba21-4e63-8354-819ffd354438","html_url":"https://github.com/cmeeren/Facil","commit_stats":{"total_commits":456,"total_committers":3,"mean_commits":152.0,"dds":"0.36184210526315785","last_synced_commit":"833ff9a195cc97f081698f6a58660ed937f8f1cf"},"previous_names":[],"tags_count":93,"template":false,"template_full_name":null,"purl":"pkg:github/cmeeren/Facil","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cmeeren%2FFacil","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cmeeren%2FFacil/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cmeeren%2FFacil/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cmeeren%2FFacil/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cmeeren","download_url":"https://codeload.github.com/cmeeren/Facil/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cmeeren%2FFacil/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":285532343,"owners_count":27187706,"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","status":"online","status_checked_at":"2025-11-20T02:00:05.334Z","response_time":54,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":"2025-11-16T06:00:41.329Z","updated_at":"2025-11-21T00:02:07.802Z","avatar_url":"https://github.com/cmeeren.png","language":"F#","funding_links":[],"categories":["Type Providers"],"sub_categories":["Performance Analysis"],"readme":"Facil\n==============\n\n\u003cimg src=\"https://raw.githubusercontent.com/cmeeren/Facil/master/logo/facil-logo-docs.png\" width=\"300\" align=\"right\" /\u003e\n\n**Facil generates F# data access source code from SQL queries and stored procedures. Optimized for developer happiness.**\n\nPro-tip: Facil works great with [Fling](https://github.com/cmeeren/Fling)!\n\nQuick start\n-----------\n\n### 0. Requirements\n\n* SQL Server 2012 or later at build-time (may work with older versions at runtime; untested)\n* .NET 6.0 or later at build-time (for running the generator)\n\n### 1. Install\n\nInstall Facil from [NuGet](https://www.nuget.org/packages/Facil).\n\n### 2. Edit the new config file\n\nYou now have [a simple facil.yaml](https://github.com/cmeeren/Facil/blob/master/src/Facil.Generator/facil_minimal.yaml)\nfile in your project directory (if not, build your project or just add it manually). Edit it – you should at least set a\nconnection string.\n\nAs an example, the following is the minimum config for generating code for all stored procedures in the database and all\nscripts in your project directory:\n\n```yaml\nrulesets:\n  - connectionString: YOUR CONNECTION STRING HERE\n    procedures:\n      - include: .*\n    scripts:\n      - include: \"**/*.sql\"\n```\n\nYou might consider adding the config file to your project for easy access in Visual Studio.\n\nFor more details about configuration, you can\nfind [the full config reference here](https://github.com/cmeeren/Facil/blob/master/facil_reference.yaml).\n\n### 3. Build your project and add the generated file\n\nBuild your project again. Facil will now generate the code. Add the generated file to your project.\n\n### 4. Use the generated code\n\nFor example:\n\n```f#\nopen DbGen.Procedures.dbo\n\nlet getUser (connStr: string) (UserId userId) : Async\u003cUser option\u003e =\n    GetUserById.WithConnection(connStr).WithParameters(userId).AsyncExecuteSingle()\n\nlet searchProducts (connStr: string) (args: ProductSearchArgs) : Async\u003cResizeArray\u003cProduct\u003e\u003e = async {\n    let dtoWithPrimitiveParams = ProductSearchArgs.toDto args\n\n    return!\n        GetUserById\n            .WithConnection(connStr)\n            // You can load parameters from any object with the right members\n            // instead of passing each parameter manually\n            .WithParameters(dtoWithPrimitiveParams)\n            .AsyncExecute()\n}\n```\n\n### 5. Profit!\n\nThat’s it! Whenever you want to regenerate, simply delete the first line of the generated file and build your project.\n\nFacil will also regenerate on next build if you change the configuration or any scripts. For details, see the FAQ\nentry [When does Facil regenerate files?](#when-does-facil-regenerate-files).\n\n## Elevator pitch\n\nFacil is a friendly, flexible, and full-featured fabricator of files for fluent and frictionless facades facilitating\nthe facile and functional fetching of facts.\n\n(“Facts” referring to data stored in SQL server. It would be better if SQL started with an F. Oh well.)\n\nOkay, elevator pitch without the alliteration: Facil works similarly to type providers\nlike [FSharp.Data.SqlClient](https://github.com/fsprojects/FSharp.Data.SqlClient/) by letting you call SQL scripts and\nstored procedures in a strongly typed manner, but it avoids a range of type provider issues and hiccups by not being a\ntype provider and actually generating F# code that you check in. (Why not a type provider, you\nask? [See the FAQ.](#why-not-a-type-provider))\n\n#### Core features\n\n* Primary goal: Provide the simplest way (yet highly configurable) to call SQL scripts and stored procedures as if they\n  were F# functions, and otherwise get out of your way and let you get on with providing actual business value\n* Can also [generate simple per-table CRUD scripts](#can-facil-generate-sql-scripts)\n* Good API ergonomics – succinct and discoverable fluent-style syntax, no boilerplate\n* Supports SQL Server 2012 and up\n* Targets .NET Standard 2.0\n* Thoroughly tested\n* Built for speed – inner async read loops written in C# using its native `async`/`await`; allows you to read directly\n  to your chosen DTO records to minimize allocations for heavy queries\n* Highly configurable with simple, yet\n  flexible [YAML configuration](https://github.com/cmeeren/Facil/blob/master/facil_reference.yaml)\n* Helpful build-time error messages and warnings\n* At runtime, supply a connection string for simplicity or use your own managed connections\n* Can be configured to use `ValueOption` instead of `Option` (separately for inputs and outputs, for some or all\n  procedures/scripts)\n* Can create DTOs for tables and automatically (or manually) use those DTOs as return values for matching result sets,\n  simplifying your mapping to domain entities\n* Can also map directly to any record type you specify (as mentioned previously)\n* Can accept suitable DTOs instead of a list of parameters, e.g. you can just pass your `UserDto` to your `SaveUser`\n  procedure instead of explicitly supplying all parameters from the DTO – less noise, and one less thing to update each\n  time you add parameters\n* Supports table-valued parameters in both procedures and scripts\n* Supports stored procedure output parameters and return values\n* Supports lazy execution, both sync (returns `seq`) and async (the latter returns `IAsyncEnumerable`, use with\n  e.g. [FSharp.Control.AsyncSeq](https://github.com/fsprojects/FSharp.Control.AsyncSeq))\n* Supports inferring dynamic SQL result sets *without* `WITH RESULT SETS`\n* To some extent [checks your dynamic SQL at build time](#can-facil-check-my-dynamic-sql)\n* Supports [temp tables](#can-i-use-temp-tables)\n\n### Production readiness\n\nFacil is production ready.\n\nFacil contains over 2000 tests verifying most functionality in many different combinations, and is used in several\nmission-critical production services at our company. I’m not claiming it’s perfect, or even bug-free, but it’s well\ntested, and I have a vested interest in keeping it working properly.\n\n\nFAQ\n---\n\n### Can Facil generate SQL scripts?\n\nYes, Facil can automatically generate the following simple per-table CRUD scripts, saving you from both writing and\nconfiguring them:\n\n* Insert a row (supports output columns)\n* Update a row by its primary key (supports output columns)\n* “Upsert” - use MERGE to insert or update a row by its primary key (supports output columns)\n* Insert/update/upsert a batch of rows efficiently (supports output columns)\n* Delete a row by its primary key (supports output columns)\n* Get all rows\n* Get a row by its primary key\n* Get rows by a batch of primary keys (using a TVP)\n* Get a row by a set of arbitrary columns\n* Get rows by a batch of arbitrary columns\n\nFor details, see [the full config reference](https://github.com/cmeeren/Facil/blob/master/facil_reference.yaml) and\nsearch for `tableScripts`.\n\n### Ooh, neat! Can you auto-generate this and that script too?\n\nProbably not.\n\nFacil’s primary focus is allowing you to call your existing TSQL in the simplest fashion possible. There are no plans on\nadding more queries or options than the ones currently implemented. Adding ever new script types or making the existing\nones more flexible is a bottomless rabbit hole of scope creep, so I have to draw the line somewhere.\n\nI welcome suggestions, but due to my limited capacity for open-source maintenance, any additions or improvements will\nlikely need a very high utility-to-maintenance ratio, or scratch a personal itch of mine.\n\nIf the above queries with the available configuration options don’t satisfy your needs, you can, after all, just write\nthe queries manually and consume them using Facil.\n\n### Why not a type provider?\n\nType providers are great in theory, and to a large extent also in practice, but have some notable drawbacks:\n\n* Potentially horrible IDE performance if you use it a lot, killing your productivity\n* No standardized schema caching or “offline” mode where it will use the already generated types without hitting your\n  DB; any offline implementation is entirely up to the type provider implementer, meaning support may be hit-and-miss or\n  completely missing\n* Not necessarily CI/CD friendly; you may be required to ensure that the build server has access to a suitable database,\n  either destroying repeatable/parallel builds (if using an external database) or placing cumbersome constraints on the\n  build agents (if using a DB on the build agent), and in all cases lengthening the build time\n* Limitations for what kind of types can be provided (no union types being a well-known example, though not itself\n  relevant for Facil)\n* May need to reload projects to force the TP to update after changes to external schema\n\nFacil, by generating plain old F# code that you check in, sidesteps all of these issues (but still optionally supports\nforced generation on CI and also optionally failing the build if the generated code has changed).\n\n### When does Facil regenerate files?\n\nFacil will regenerate (hitting your DB) before the compilation step of your build if any of the following are true:\n\n* There are changes to the two first lines of the generated file(s) (this is the simplest way to manually force a\n  rebuild)\n* There are changes in included SQL scripts\n* There are changes in the config file\n* There are changes to Facil itself (i.e., when updating Facil)\n* The environment variable `FACIL_FORCE_REGENERATE` exists\n\nIf none of the above are true, Facil will skip its build step and thus not hit your DB.\n\nNotably, this means that Facil **will not pick up changes to your database**. This is by design. If you update the DB\nthat Facil connects to during build, just open the generated file and delete the first line and Facil will re-generate\nit on the next build.\n\n### Can I force Facil to run during CI build and fail if the generated file is not up to date?\n\nYes. There are two environment variables you can set. You can use either of them on their own, or together:\n\n* `FACIL_FORCE_REGENERATE`: Set this to force Facil to always regenerate during build. Alone, this variable will\n  effectively make Facil mimick a type provider without caching/offline capabilities.\n* `FACIL_FAIL_ON_CHANGED_OUTPUT`: Set this to make Facil fail the build if the output has changed. You can use this to\n  reject commits that does not include up-to-date generated code.\n\n### Why won't the project recompile if I only change `facil.yaml` or a `.sql` file?\n\nDepending on how the `.sql` and `facil.yaml` file are added to your project in the first place, you may need to add\nthese files to the project's up-to-date check. Simply add this in your `.fsproj` file:\n\n```xml\n\n\u003cItemGroup\u003e\n  \u003cUpToDateCheckInput Include=\"**\\*.sql\" /\u003e\n  \u003cUpToDateCheckInput Include=\"facil.yaml\" /\u003e\n\u003c/ItemGroup\u003e\n```\n\n### What can I configure?\n\nSee [the full YAML config reference for details](https://github.com/cmeeren/Facil/blob/master/facil_reference.yaml).\nNote:\n\n* The top-level `rulesets` property is an array, meaning you can generate multiple source files with separate configs (\n  e.g. to generate from multiple DBs) simply by adding another array item with the desired configuration (see the bottom\n  of the reference YAML for details).\n* All regex patterns in the YAML file are case sensitive by default; prefix them with `(?i)` to make them case\n  insensitive (e.g. `(?i)^dbo\\.mytable$`).\n\nBelow are some highlights of what you can configure.\n\nFor each file, you can configure:\n\n* The generated filename\n* The generated namespace/module\n* Arbitrary code to put at the top of the generated file\n* Which stored procedures (regex matching) or scripts (glob matching) to generate code for\n* Which tables to generate DTO records for (which can be used, automatically or manually, for matching procedure/script\n  result sets)\n* The base path for all scripts glob patterns (can be outside your project directory)\n\nFor each procedure/script (or any set of these that matches a specified regex/glob pattern), you can configure:\n\n* The result type: Anonymous record, auto-pick best table DTO (with fallback to anonymous records), or any record type\n  you specify (a table DTO or your own custom type) that is constructible by the generated code\n* Whether to use `ValueOption` instead of `Option` for inputs and/or outputs\n* For single-column results, whether to return a record (as with multiple columns) or just return the scalar value\n* Whether to skip the `inline` DTO parameter overloads (for faster compilation if you don’t use them)\n* Whether to use return values (stored procedures only)\n* For each parameter: Its nullability and the name to use in parameter DTO objects\n* For each script parameter: Its type (to work around type inference limitations for\n  scripts, [see below](#type-inference-limitations-in-scripts))\n* Temp tables ([see below](#can-i-use-temp-tables))\n\nFor each table DTO, you can configure:\n\n* Whether to use `ValueOption` instead of `Option`\n* Whether to add a constructor that accepts an object whose properties are a superset of the properties of the table\n  DTO. This can be useful if you want to use the table DTO with a script that returns additional columns (for example\n  representing the total number of rows, or an additional value to group by).\n\nFor each table type (automatically included by Facil if used in included procedures/scripts), you can configure:\n\n* Whether to use `ValueOption` instead of `Option`\n* Whether to skip the `inline` DTO parameter overloads (for faster compilation if you don’t use them)\n\n### Type inference limitations in scripts\n\nType inference in scripts is limited due to limitations in SQL\nServer's [sp_describe_undeclared_parameters](https://docs.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sp-describe-undeclared-parameters-transact-sql)\n, which Facil uses to get parameter information for scripts. Notably, the following does not work out of the box:\n\n* Parameters used multiple times will give errors\n* Table-valued parameters will give errors\n* Nullability is not inferred (by default, Facil assumes all script parameters are non-nullable)\n\nTo work around this, for each problematic parameter (you don't have to specify the ones that work), you can specify in\nthe config which SQL type the parameter is and whether it is nullable. You can also set this for all parameters at\nonce (and override specific parameters).\n\n### How are default and nullable parameter values handled?\n\nAll stored procedure parameters that have `null` as the default value are treated as nullable and wrapped in `option`\n(or `voption`). All other default values for stored procedure parameters are ignored; the parameters will be required\nand non-nullable.\n\nWhile parameters with default values could conceivably be generated as optional method parameters, this runs the risk of\nforgetting to use them when executing the procedure (I’ve been burned by this a lot), and it also won’t mesh that well\nwith the parameter DTO overloads.\n\nIf Facil’s current approach does not work for you, please open an issue and describe your use-case.\n\n### Can Facil support user-defined functions?\n\nIf you need this, I’m willing to hear you out, but I have limited OSS maintenance resources and this isn’t high on my\npriority list right now. A simple workaround is to simply call the function from a script or stored procedure, and then\nuse Facil with that script/procedure instead.\n\n### Does Facil use column names or ordinals?\n\nFacil uses column names at runtime. This means that you are free to reorder the columns returned by stored procedures\nand scripts (either explicitly, or by reordering table columns returned in a `SELECT *` query). This does not require\nre-compilation and will not break existing running apps.\n\n### Can I use temp tables?\n\nYes, Facil supports temp tables in scripts and procedures. In short, configure your script/procedures like this:\n\n```yaml\nscripts:\n  - include: \"MyScriptUsingTempTables.sql\"\n    tempTables: # You can supply the definition directly as a string.\n      - definition: |\n          CREATE TABLE #myTempTable1(\n            Col1 INT NOT NULL PRIMARY KEY,\n            Col2 NVARCHAR(100) NULL\n          )\n\n      # If using a single line, remember to enclose in quotes since '#' starts a YAML comment\n      - definition: \"CREATE TABLE #myTempTable2(Col1 INT NOT NULL)\"\n\n      # You can also specify a path to a SQL file containing the definition\n      - definition: path/from/project/to/myTempTable3.sql\n```\n\nThen temp tables will work similarly to TVPs:\n\n```f#\nMyScriptUsingTempTables\n    .WithConnection(connStr)\n    .WithParameters(\n        tempTable1 = [ MyScriptUsingTempTables.tempTable1.create (Col1 = 1, Col2 = Some \"test\") ],\n        tempTable2 = [ MyScriptUsingTempTables.tempTable2.create (Col1 = 1) ],\n        tempTable3 = [ (* ... *) ]\n    )\n```\n\nJust like with TVPs, you can use matching DTOs in the calls to `create` (instead of explicitly passing column parameters\nas shown above).\n\nFacil uses [`SqlBulkCopy`](https://docs.microsoft.com/en-us/dotnet/api/microsoft.data.sqlclient.sqlbulkcopy) to load\ntemp tables. You can configure the created `SqlBulkCopy` using `ConfigureBulkCopy`.\n\n```f#\nMyScriptUsingTempTables\n    .WithConnection(connStr)\n    .ConfigureBulkCopy(fun bc -\u003e\n        bc.BatchSize \u003c- 1000\n        bc.BulkCopyTimeout \u003c- 0\n        bc.NotifyAfter \u003c- 2000\n        bc.SqlRowsCopied.Add(fun e -\u003e printfn \"%i rows copied so far\" e.RowsCopied))\n    .WithParameters( (* ... *) )\n```\n\nThe configuration will apply to the loading of all temp tables for the script/procedure; please open an issue if you\nneed separate configuration per temp table.\n\nNote that if a **procedure** uses a **global** temp table (a temp table that starts with two `##` instead of one `#`),\nthat temp table needs to exist at build-time. (This limitation does not apply to scripts.)\n\nNote also that if you plan to use the same temp table name in another command with the same connection, you need to drop\nit. The easiest way to do this is to use `DROP TABLE #myTempTable` as the first line in the temp table definition. (This\nis automatically done for Facil-generated `tableScripts` that use temp tables).\n\n### Why do the `Execute` methods return `ResizeArray` and not an F# `list`?\n\nThe rows have to be read from the DB one at a time without knowing how many rows there are. As far as I know,\na `ResizeArray\u003c_\u003e` (an alias for `System.Collections.Generic.List\u003c_\u003e`) generally provides the most efficient way (at\nleast in terms of allocations) to build up a collection with an unknown number of items, requiring one allocation and\narray copy each time the internal array is resized. An F# `list` would cause one allocation per cell (item), though as\nfar as I know, it would not require any copies (though it would be built up in reverse and therefore require a full\ntraversal when reversing the list at the end).\n\nF# users would normally want a `list` instead of a `ResizeArray`, but you can get that trivially by just\ncalling `Seq.toList` on the result. This is similar to e.g. FSharp.Data.SqlClient. Facil could provide `Execute`\nvariants that do this for you, but then you’d have twice as many `Execute` methods to choose from, which would add\nconfusion, and the name prefix/suffix would almost be as verbose as just calling `Seq.toList` yourself.\n\nIf you think that building up a `list` directly in the read loop would be more efficient as it would avoid the “copy”\ncost of `Seq.toList`, then 1) I don’t think that’s correct, because (as mentioned above) an F# `list` would have to be\nbuilt up in reverse by prepending each item, and the `List.rev` at the end would cause at least one “copy” anyway, and\n\n2) in the rare case that your use-case is so sensitive to performance that you are concerned about the performance\n   impact of `Seq.toList`, then you should probably just just use the returned `ResizeArray` directly.\n\nNote that since Facil is a general-purpose data access library, I do not know anything about user workloads, databases\nor connections, and I have not benchmarked anything. All of the above is going by intuition (admittedly a dangerous\nthing in the performance world) as well as a desire to keep the internals fairly simple.\n\nIf you believe any of the above is incorrect and have either sound arguments or proper benchmark data (e.g.\nusing [BenchmarkDotNet](https://github.com/dotnet/BenchmarkDotNet)) to back it up, please open an issue and we can\ndiscuss both the public API and the implementation details.\n\n### Can Facil support SSDT (`.sqlproj`) projects, like [SQLProvider](http://fsprojects.github.io/SQLProvider/)?\n\nNo, unfortunately that’s not feasible.\n\nSQLProvider uses information about tables and views and lets you write F# queries against them; it doesn’t provide\nstrongly typed access to stored procedures or scripts. This means that SQLProvider “only” has to know the table/view\nschema, which (at least for tables) can be easily parsed directly from the definition.\n\nFacil, on the other hand, must know the input parameters and output columns of scripts and stored procedures. While it\nwould be fairly easy to get the input parameters of stored procedures by just parsing the procedure definition, Facil\nhas to use SQL Server functionality on the deployed schema to get script input\nparameters (`sp_describe_undeclared_parameters`) and script/procedure output columns (`sp_describe_first_result_set`\nand `SET FMTONLY ON`).\n\nThe only way for Facil to be able to use SSDT projects directly would be if this SQL type inference functionality was\nimplemented directly in the Facil generator. For all but the most trivial schema, this seems too complicated to be worth\nthe implementation and maintenance effort. Therefore, Facil only works with deployed schema.\n\nThe most significant drawback of this limitation is that it is possible to update the SSDT schema but forget to deploy\nand re-generate. However, that is no different than if you were\nusing [FSharp.Data.SqlClient](http://fsprojects.github.io/FSharp.Data.SqlClient/), and can be alleviated by\nusing `FACIL_FORCE_REGENERATE` and `FACIL_FAIL_ON_CHANGED_OUTPUT` on your build server.\n\nA bonus, however, is that having to deploy the updated schema to a database during development ensures that the schema\nactually “compiles”.\n\n### Why does Facil not generate result sets for my dynamic SQL query?\n\nTL;DR: Use `buildValue` in the parameter config for parameters with sort column names etc.\n\nLong version: This can happen when all of the following are true:\n\n* You use dynamic SQL\n* The syntax of the dynamic SQL statement is sensitive to the value of a parameter (e.g. a parameter contains the column\n  name to sort by)\n* You are not using `WITH RESULT SETS`\n* You are not using `buildValue` for the relevant parameters in `facil.yaml`\n\nIn this scenario, the output column parser returns no columns. (Unfortunately Facil can’t easily find out whether this\nis an error or if the script actually returns no results, and thus can’t display a warning/error instead of silently\nproducing non-query code.)\n\nThe fix is simple: In the procedure/script config, add a parameter with `buildValue` set to a value you know will work:\n\n```yaml\n- for: MyProcedure\n  params:\n    mySortColParam:\n      buildValue: MySortColumn\n```\n\nAnother workaround is to use `WITH RESULT SETS`. This will in many cases enable Facil to use another method of\ndetermining the output columns that does not depend on parameter values. However, compared to using `buildValue`, it is\nmore likely to break (since you must keep it in sync with the `SELECT` list) and likely more verbose.\n\n### Can Facil check my dynamic SQL?\n\nFacil provides some checking of dynamic SQL as long as you don’t use `WITH RESULT SETS`.\n\nAs an example, take the following script:\n\n```sql\nDECLARE @sql NVARCHAR(MAX) = '\nSELECT * FROM dbo.MyTable\nWHERE\n  1 = 1\n'\n\nIF @col1Filter IS NOT NULL\n  SET @sql += '\n  AND Col1 = @col1Filter\n'\n\nIF @requireCol2Zero = 1\n  SET @sql += '\n  AND Col2 = 0\n'\n\nDECLARE @paramList NVARCHAR(MAX) = '\n  @col1Filter NVARCHAR(100)\n'\n\nEXEC sp_executesql @sql, @paramList, @col1Filter\n```\n\nIn order to parse the output columns of dynamic SQL queries, Facil must execute your query and see which columns come\nback. At build time, Facil generally passes `1` (or `\"1\"` etc.) for all parameters when executing the query. In the\ncommon case of dynamic filters as shown above, where you use `IS NOT NULL` or `@param = 1` to add filters to the\nexecuted SQL, this means that your dynamic SQL will be executed with all the optional filters.\n\nFacil may not completely check your dynamic SQL. For example, you may have a parameter that is used to choose one of\nseveral different `ORDER BY` clauses. In this case, only one of them will be used at build time (and you may be able to\nspecify the parameter value by using `buildValue` as described previously).\n\nWhen executing a query at build time as described above, it is executed in a transaction that is rolled back, to ensure\nthat no changes are made to the database during build time.\n\n### Can Facil make it easy to save/load domain entities to/from multiple tables?\n\nThis is exactly what [Fling](https://github.com/cmeeren/Fling) does. Fling works great with Facil!\n\nRelease notes\n-------------\n\n[RELEASE_NOTES.md](https://github.com/cmeeren/Facil/blob/master/RELEASE_NOTES.md)\n\n### A note on versioning\n\nFacil follows [SemVer](https://semver.org/).\n\nNote on what a “breaking change” is: A lot of the generated code needs to be public to support inlining, but is still\nconsidered implementation details. These parts of the API are hidden from the IDE using the `[\u003cEditorBrowsable\u003e]`\nattribute to ensure you won’t use them by accident, but there’s nothing stopping you from looking at the generated code\nand referencing these parts of the API in your own code. Don’t do that. These are implementation details and may change\nat any time.\n\nContributing\n------------\n\nContributions and ideas are welcome! Please\nsee [Contributing.md](https://github.com/cmeeren/Facil/blob/master/.github/CONTRIBUTING.md) for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcmeeren%2FFacil","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcmeeren%2FFacil","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcmeeren%2FFacil/lists"}