{"id":13629684,"url":"https://github.com/kant2002/SqlMarshal","last_synced_at":"2025-04-17T09:35:49.658Z","repository":{"id":40575136,"uuid":"344694114","full_name":"kant2002/SqlMarshal","owner":"kant2002","description":"Generates data access using stored procedures","archived":false,"fork":false,"pushed_at":"2024-08-19T14:02:55.000Z","size":109,"stargazers_count":59,"open_issues_count":4,"forks_count":6,"subscribers_count":4,"default_branch":"main","last_synced_at":"2024-11-07T15:43:21.053Z","etag":null,"topics":["aot","csharp-sourcegenerator","nativeaot","orm"],"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/kant2002.png","metadata":{"files":{"readme":"README.md","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":"2021-03-05T04:29:53.000Z","updated_at":"2024-09-20T00:34:18.000Z","dependencies_parsed_at":"2024-01-06T02:10:12.816Z","dependency_job_id":"d036eef2-1945-4850-b786-3398c24f3a24","html_url":"https://github.com/kant2002/SqlMarshal","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kant2002%2FSqlMarshal","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kant2002%2FSqlMarshal/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kant2002%2FSqlMarshal/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kant2002%2FSqlMarshal/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kant2002","download_url":"https://codeload.github.com/kant2002/SqlMarshal/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":223751373,"owners_count":17196625,"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":["aot","csharp-sourcegenerator","nativeaot","orm"],"created_at":"2024-08-01T22:01:16.552Z","updated_at":"2025-04-17T09:35:49.651Z","avatar_url":"https://github.com/kant2002.png","language":"C#","readme":"SqlMarshal\n===========================\n\n[![NuGet](https://img.shields.io/nuget/v/SqlMarshal.svg?style=flat)](https://www.nuget.org/packages/SqlMarshal/)\n\nNativeAOT-friendly mini-ORM which care about nullability checks.\n\nThis project generates typed functions for accessing custom SQL and stored procedures. Goal of this project to be AOT friendly.\nDatabase connection can be used from the DbContext of DbConnection objects.\n\n# How to use\n\nAdd `SqlMarshal` Nuget package using\n\n```\ndotnet add package SqlMarshal\n```\n\nThen create your data context class inside your project.\n```csharp\npublic class PersonInformation\n{\n    public int PersonId { get; set; }\n\n    public string? PersonName { get; set; }\n}\n\npublic partial class DataContext\n{\n    private DbConnection connection;\n\n    public DataContext(DbConnection connection) =\u003e this.connection = connection;\n\n    [SqlMarshal(\"persons_list\")]\n    public partial IList\u003cPersonInformation\u003e GetPersons();\n\n    [SqlMarshal]\n    public partial IList\u003cPersonInformation\u003e GetPersonFromSql([RawSql]string sql, int id);\n}\n```\n\nYou can have simplified repository generator. \nDetection happens by name, and I plan to automatically support more naming conventions.\n\n```csharp\npublic class Product\n{\n    public int Id { get; set; }\n\n    public string? Code { get; set; }\n    public string? ProductTitle { get; set; }\n    public string? Description { get; set; }\n}\n\npublic partial class DataContext\n{\n    private DbConnection connection;\n\n    public DataContext(DbConnection connection) =\u003e this.connection = connection;\n\n    public partial IList\u003cProduct\u003e FetchAll();\n\n    public partial Product? FetchById(int id);\n\n    public partial int Count();\n\n    public partial void DeleteAll();\n\n    public partial void DeleteById(int id);\n\n    public partial void Insert(int id, string code, string productTitle, string description);\n\n    public partial void Update(int id, string code, string productTitle, string description);\n}\n```\n\nYou can override column names and table names of the entity using attributes from `System.ComponentModel.DataAnnotations.Schema` namespace.\n\n# Temporary limitations or plans\nCurrent version of library has several limitations which not because it cannot be implemented reasonably,\nbut because there was lack of time to think through all options. So I list all current limitations, so any user would be aware about them.\nI think about these options like about plan to implement them.\n\n- No ability to specify length of input/output string parameters, or type `varchar`/`nvarchar`.\n- Simplified ORM for just mapping object properties from DbDataReader\n- Ability to specify fields in code in the order different then returned from SQL.\n- Automatic generation of DbSet\u003cT\u003e inside DbContext, since when working with stored procedures this is most likely burden.\n- FormattableString support not implemented.\n\n# Examples\n\n- [DbConnection examples](#dbconnection-examples)\n    - [Stored procedures which returns resultset](#stored-procedures-which-returns-resultset)\n    - [Adding parameters](#Adding-parameters)\n    - [Executing SQL](#Executing-SQL)\n    - [Output parameters](#Output-parameters)\n    - [Procedure which returns single row](#Procedure-which-returns-single-row)\n    - [Scalar results](#Scalar-results)\n    - [Sequences](#Sequence-results)\n    - [INSERT or UPDATE](#Without-results)\n    - [Join transactions](#Join-transactions)\n- [DbContext examples](#dbcontext-examples)\n    - [Stored procedures which returns resultset](#stored-procedures-which-returns-resultset-1)\n    - [Adding parameters](#Adding-parameters-1)\n    - [Output parameters](#Output-parameters-1)\n    - [Procedure which returns single row](#Procedure-which-returns-single-row-1)\n    - [Scalar results](#Scalar-results-1)\n    - [INSERT or UPDATE](#Without-results-1)\n    - [Join transactions](#Join-transactions-1)\n- [Alternative options](#Alternative-options)\n    - [Async methods](#Async-methods)\n    - [Nullable parameters](#Nullable-parameters)\n    - [Bidirectional parameters](#Bidirectional-parameters)\n    - [Pass connection as parameter](#pass-connection-as-parameter)\n    - [Pass transaction as parameter](#pass-transaction-as-parameter)\n    - [CancellationToken support](#CancellationToken-support)\n\n## Managing connections\n\nGenerated code does not interfere with the connection opening and closing. It is responsibility of developer to properly wrap code in the transaction and open connections.\n\n```csharp\npublic partial class DataContext\n{\n    private DbConnection connection;\n\n    public DataContext(DbConnection connection) =\u003e this.connection = connection;\n\n    [SqlMarshal(\"persons_list\")]\n    public partial IList\u003cItem\u003e GetResult();\n}\n...\n\nvar connection = new SqlConnection(\"......\");\nconnection.Open();\ntry\n{\n    var dataContext = new DataContext(connection);\n    var items = dataContext.GetResult();\n    // Do work on items here.\n}\nfinally\n{\n    connection.Close();\n}\n```\n\nSame rule applies to code which uses DbContext.\n\n## Additional samples\n\nIn the repository located sample application which I use for testing, but they can be helpful as usage examples.\n\n- https://github.com/kant2002/SqlMarshal/tree/main/SqlMarshal.CompilationTests\n\n## Performance\n\nNow I only hope (because no measurements yet) that performance would be on par with [Dapper](https://github.com/StackExchange/Dapper) or better.\nAt least right now generated code is visible and can be reason about.\n\n## DbConnection examples\n\n### Stored procedures which returns resultset\n\n```csharp\npublic partial class DataContext\n{\n    private DbConnection connection;\n\n    [SqlMarshal(\"persons_list\")]\n    public partial IList\u003cItem\u003e GetResult();\n}\n```\n\nThis code translated to `EXEC persons_list`.\nWhen generated code retrieve data reader it starts iterating properties in the `Item` class in the\nsame order as they are declared and read values from the row. Order different then declaration order not supported now.\n\n### Adding parameters\n\n```csharp\npublic partial class DataContext\n{\n    private DbConnection connection;\n\n    [SqlMarshal(\"persons_search\")]\n    public partial IList\u003cItem\u003e GetResults(string name, string city);\n}\n```\n\nThis code translated to `EXEC persons_search @name, @city`. Generated code do not use named parameters.\n\n### Executing SQL\n\nIf stored procedure seems to be overkill, then you can add string parameter with attribute [RawSql]\nand SQL passed to the function would be executed.\n\n```csharp\npublic partial class DataContext\n{\n    private DbConnection connection;\n\n    [SqlMarshal]\n    public partial IList\u003cPersonInformation\u003e GetResultFromSql([RawSql]string sql, int maxId);\n}\n```\n\n### Output parameters\n\n```csharp\npublic partial class DataContext\n{\n    private DbConnection connection;\n\n    [SqlMarshal(\"persons_search_ex\")]\n    public partial IList\u003cItem\u003e GetResults2(string name, string city, out int totalCount);\n}\n```\n\nThis code translated to `EXEC persons_search @name, @city, @total_count OUTPUT`.\nValue returned in the @total_count parameter, saved to the `int totalCount` variable.\n\n### Procedure which returns single row\n\n```csharp\npublic partial class DataContext\n{\n    private DbConnection connection;\n\n    [SqlMarshal(\"persons_by_id\")]\n    public partial Item GetResults(int personId);\n}\n```\n\nThis code translated to `EXEC persons_by_id @person_id`. From mapped result set taken just single item, first one.\n\n### Scalar results\n\n```csharp\npublic partial class DataContext\n{\n    private DbConnection connection;\n\n    [SqlMarshal(\"total_orders\")]\n    public partial int GetTotal(int clientId);\n}\n```\n\nThis code translated to `EXEC total_orders @client_id`. Instead of executing over data reader, ExecuteScalar called. \n\n### Sequence results\n\n```csharp\npublic partial class DataContext\n{\n    private DbConnection connection;\n\n    [SqlMarshal(\"total_orders\")]\n    public partial IList\u003cstring\u003e GetStrings(int clientId);\n}\n```\n\nThis code translated to `EXEC total_orders @client_id`. First columns of the returning result set mapped to the sequence.\nIf you want return more then one columns, and do not want create classes, you can use tuples\n\n```csharp\npublic partial class DataContext\n{\n    private DbConnection connection;\n\n    [SqlMarshal(\"total_orders\")]\n    public partial IList\u003c(string, int)\u003e GetPairs(int clientId);\n}\n```\n\n### Join transactions\n\nNot implemented.\n\n### Without results\n\n```csharp\npublic partial class DataContext\n{\n    private DbConnection connection;\n\n    [SqlMarshal(\"process_data\")]\n    public partial void ProcessData(int year);\n}\n```\n\nThis code translated to `EXEC process_data @year`. No data was returned, ExecuteNonQuery called. \n\n## DbContext examples\n\n### Stored procedures which returns resultset\n\n```csharp\npublic partial class DataContext\n{\n    private CustomDbContext dbContext;\n\n    [SqlMarshal(\"persons_list\")]\n    public partial IList\u003cItem\u003e GetResult();\n}\n```\n\nThis code translated to `EXEC persons_list`.\nUnderlying assumption that in the custom context there definition of the `DbSet\u003cItem\u003e`.\n\n### Adding parameters\n\n```csharp\npublic partial class DataContext\n{\n    private CustomDbContext dbContext;\n\n    [SqlMarshal(\"persons_search\")]\n    public partial IList\u003cItem\u003e GetResults(string name, string city);\n}\n```\n\nThis code translated to `EXEC persons_search @name, @city`. Generated code do not use named parameters.\n\n### Output parameters\n\n```csharp\npublic partial class DataContext\n{\n    private CustomDbContext dbContext;\n\n    [SqlMarshal(\"persons_search_ex\")]\n    public partial IList\u003cItem\u003e GetResults2(string name, string city, out int totalCount);\n}\n```\n\nThis code translated to `EXEC persons_search @name, @city, @total_count OUTPUT`.\nValue returned in the @total_count parameter, saved to the `int totalCount` variable.\n\n### Procedure which returns single row\n\n```csharp\npublic partial class DataContext\n{\n    private CustomDbContext dbContext;\n\n    [SqlMarshal(\"persons_by_id\")]\n    public partial Item GetResults(int personId);\n}\n```\n\nThis code translated to `EXEC persons_by_id @person_id`. From mapped result set taken just single item, first one.\n\n### Scalar results\n\n```csharp\npublic partial class DataContext\n{\n    private CustomDbContext dbContext;\n\n    [SqlMarshal(\"total_orders\")]\n    public partial int GetTotal(int clientId);\n}\n```\n\nThis code translated to `EXEC total_orders @client_id`. Instead of executing over data reader, ExecuteScalar called. \n\n### Without results\n\n```csharp\npublic partial class DataContext\n{\n    private CustomDbContext dbContext;\n\n    [SqlMarshal(\"process_data\")]\n    public partial void ProcessData(int year);\n}\n```\n\nThis code translated to `EXEC process_data @year`. No data was returned, ExecuteNonQuery called. \n\n### Join transactions\n\nGenerated code automatically join any transaction opened using `DbContext.Database.BeginTransaction()`.\n\n\n## Alternative options\n\n### Async methods\n\n```csharp\npublic partial class DataContext\n{\n    private DbConnection connection;\n\n    [SqlMarshal(\"total_orders\")]\n    public partial Task\u003cint\u003e GetTotal(int clientId);\n}\n```\n\nor\n\n```csharp\npublic partial class DataContext\n{\n    private CustomDbContext dbContext;\n\n    [SqlMarshal(\"persons_search\")]\n    public partial Task\u003cIList\u003cItem\u003e\u003e GetResults(string name, string city);\n}\n```\n\n### Nullable parameters\n\nThe codegen honor nullable parameters. If you specify parameter as non-nullable, it will not work with NULL values in the database,\nif you specify that null allowed, it properly convert NULL to null values in C#.\n\n```csharp\npublic partial class DataContext\n{\n    private DbConnection connection;\n\n    [SqlMarshal(\"get_error_message\")]\n    public partial string? GetErrorMessage(int? clientId);\n}\n```\n\n### Bidirectional parameters\n\nIf you have parameters which act as input and output parameters, you can specify them as `ref` values.\nCodegen read values after SQL was executed.\n\n```csharp\npublic partial class DataContext\n{\n    private DbConnection connection;\n\n    [SqlMarshal(\"get_error_message\")]\n    public partial string? GetErrorMessage(ref int? clientId);\n}\n```\n\n### Pass connection as parameter\n\nInstead of having DbConnection as a field of the class, it can be passed as parameter, and even be placed in the extension method.\n\n```csharp\npublic static partial class DataContext\n{\n    [SqlMarshal(\"persons_list\")]\n    public static partial IList\u003cItem\u003e GetResult(DbConnection connection);\n\n    [SqlMarshal(\"persons_by_id\")]\n    public static partial Item GetResults(DbConnection connection, int personId);\n}\n```\n\n### Pass transaction as parameter\n\nIf you want finegrained control over transactions, if you pass `DbTransaction` as parameter, generated code will set it to `DbCommand` or EF context will join that transaction using `Database.UseTransaction`.\n\n```csharp\npublic static partial class DataContext\n{\n    [SqlMarshal(\"persons_list\")]\n    public static partial IList\u003cItem\u003e GetResult(DbTransaction tran);\n\n    [SqlMarshal(\"persons_by_id\")]\n    public static partial Item GetResults(DbTransaction tran, int personId);\n}\n```\n\n### CancellationToken support\n\nYou can add CancellationToken inside your code and it would be propagated inside ADO.NET calls.\nYou can use that with DbContext too.\n\n```csharp\npublic static partial class DataContext\n{\n    [SqlMarshal(\"total_orders\")]\n    public partial Task\u003cint\u003e GetTotal(DbConnection connection, int clientId, CancellationToken cancellationToken);\n}\n```\n","funding_links":[],"categories":["Do not want to test 112 ( old ISourceGenerator )","Source Generators"],"sub_categories":["1. [ThisAssembly](https://ignatandrei.github.io/RSCG_Examples/v2/docs/ThisAssembly) , in the [EnhancementProject](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#enhancementproject) category","Database / ORM"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkant2002%2FSqlMarshal","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkant2002%2FSqlMarshal","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkant2002%2FSqlMarshal/lists"}