{"id":16,"date":"2023-03-30T08:01:17","date_gmt":"2023-03-30T06:01:17","guid":{"rendered":"https:\/\/sii.ua\/blog\/?p=16"},"modified":"2024-02-16T09:45:39","modified_gmt":"2024-02-16T08:45:39","slug":"using-table-valued-parameters-with-dapper-in-net","status":"publish","type":"post","link":"https:\/\/sii.ua\/blog\/en\/using-table-valued-parameters-with-dapper-in-net\/","title":{"rendered":"Using Table-Valued Parameters with Dapper in .NET"},"content":{"rendered":"\n<p>If you are working with SQL Server or Azure SQL, you probably heard about Table-Valued Parameters. If by any chance you are a backend developer you will be happy to learn how easily we can utilize them in our application with Dapper.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Table-Valued Parameters<\/strong><\/h2>\n\n\n\n<p>TVP is one of the most useful features of SQL Server. They <a aria-label=\" (opens in a new tab)\" href=\"https:\/\/learn.microsoft.com\/en-us\/dotnet\/framework\/data\/adonet\/sql\/table-valued-parameters\" target=\"_blank\" rel=\"noreferrer noopener\" class=\"ek-link\" rel=\"nofollow\" >provide an easy way to marshal multiple rows of data from a client application to SQL Server<\/a>. That means we can pass the entire table into a Stored Procedure or Function in one single parameter. In order to do anything, we need to start by creating a type on the database.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\nCREATE TYPE &#x5B;dbo].&#x5B;udtt_Project] AS TABLE\n(\n\t&#x5B;Id] int NULL,\n\t&#x5B;Name] nvarchar(4000) NOT NULL,\n\t&#x5B;ProjectStartDate] DateTimeOffset NULL,\n\t&#x5B;Active] bit NOT NULL, \n\t&#x5B;Draft] bit NOT NULL\n)\n<\/pre><\/div>\n\n\n<p>After this step, we can create a Stored Procedure that will accept our User-Defined Table Type as a parameter.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\nCREATE PROCEDURE &#x5B;dbo].&#x5B;ProcedureWhichAcceptsProjectsAsTVP]\n\t@projects &#x5B;dbo].&#x5B;udtt_Project] readonly\nAS\nBEGIN\n\tSELECT count(*)\n\tfrom @projects;\nEND\n<\/pre><\/div>\n\n\n<p>That\u2019s all we need to do in the database. But before we can use our Stored Procedure, we need to learn how to pass Table-Valued Parameter with Dapper\u2026<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Passing Table-Valued Parameter with Dapper<\/strong><\/h2>\n\n\n\n<p>To create a Table-Valued Parameter we need to call the method <em>AsTableValuedParameter<\/em>. This method converts <em>DataTable<\/em> or <em>IEnumerable&lt;T&gt; where T : IDataRecord<\/em> to <em>ICustomQueryParameter<\/em>. We will focus on <em>DataTable<\/em> because I believe it will be easier to understand.<\/p>\n\n\n\n<p>As developers, we don\u2019t usually use these types but rather some Lists of the specified models. To create TVP, we need to convert our List to DataTable and invoke <em>AsTableValuedParameter<\/em>. Assuming that we have C# model that exactly represents our SQL User-Defined Table Type that looks like this:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\npublic class Project\n{\n    public int? Id { get; set; }\n    public string Name { get; set; } = String.Empty;\n    public DateTimeOffset? ProjectStartDate { get; set; }\n    public bool Active { get; set; }\n    public bool Draft { get; set; }\n}\n<\/pre><\/div>\n\n\n<p>the first thing that comes to our mind is to simply create <em>DataTable<\/em>, manually add needed columns, and fill them with data like this:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\nusing var conn = new SqlConnection(connectionString);\nconn.Open();\n\n\/\/ some example data\nList&lt;Project&gt; projects = new()\n{\n    new Project { Id = 1, Name = &quot;Name1&quot;, ProjectStartDate = DateTimeOffset.Parse(&quot;2022-11-01&quot;), Active = true, Draft = false },\n    new Project { Id = 2, Name = &quot;Name2&quot;, ProjectStartDate = DateTimeOffset.Parse(&quot;2022-12-01&quot;), Active = false, Draft = true }\n};\n\n\/\/ create DataTable\nDataTable projectsDT = new();\nprojectsDT.Columns.Add(nameof(Project.Id), typeof(int));\nprojectsDT.Columns.Add(nameof(Project.Name), typeof(string));\nprojectsDT.Columns.Add(nameof(Project.ProjectStartDate), typeof(DateTimeOffset));\nprojectsDT.Columns.Add(nameof(Project.Active), typeof(bool));\nprojectsDT.Columns.Add(nameof(Project.Draft), typeof(bool));\n\n\/\/ add rows to DataTable\nforeach (var project in projects)\n{\n    var row = projectsDT.NewRow();\n    row&#x5B;nameof(Project.Id)] = project.Id ?? (object)DBNull.Value;\n    row&#x5B;nameof(Project.Name)] = project.Name;\n    row&#x5B;nameof(Project.ProjectStartDate)] = project.ProjectStartDate ?? (object)DBNull.Value;\n    row&#x5B;nameof(Project.Active)] = project.Active;\n    row&#x5B;nameof(Project.Draft)] = project.Draft;\n    projectsDT.Rows.Add(row);\n}\n\n\/\/ create parameters\nvar parameters = new\n{\n    projects = projectsDT.AsTableValuedParameter(&quot;&#x5B;dbo].&#x5B;udtt_Project]&quot;)\n};\n\n\/\/ execute Stored Procedure\nreturn await conn.ExecuteScalarAsync&lt;int&gt;(\n    &quot;&#x5B;dbo].&#x5B;ProcedureWhichAcceptsProjectsAsTVP]&quot;,\n    param: parameters,\n    commandType: CommandType.StoredProcedure);\n<\/pre><\/div>\n\n\n<p>This is a perfectly fine solution. In addition, you can include the piece of code responsible for creating <em>DataTable<\/em> as a static method in the <em>Project<\/em> class to reuse it in other places.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Generated SQL script<\/strong><\/h3>\n\n\n\n<p>Now, let\u2019s see what Dapper generated and passed to the database. In the Profiler tool we can see that the following script was executed:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\ndeclare @p1 dbo.udtt_Project\ninsert into @p1 values(1,N&#039;Name1&#039;,&#039;2022-11-01 00:00:00 +01:00&#039;,1,0)\ninsert into @p1 values(2,N&#039;Name2&#039;,&#039;2022-12-01 00:00:00 +01:00&#039;,0,1)\n\nexec &#x5B;dbo].&#x5B;ProcedureWhichAcceptsProjectsAsTVP] @projects=@p1\n<\/pre><\/div>\n\n\n<p>Do you see anything worrying? Well, all the data that is inserted into the <em>@p1<\/em> variable is mapped ordinal. <em>Insert<\/em> statement is not specifying any columns so the whole operation relies on the consistency of column order in C# <em>DataTable<\/em> and <em>User-Defined Table Type<\/em> in SQL. We should always keep in mind, that there is no mapping by column name and we should always manually take care of:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>proper column order,<\/li>\n\n\n\n<li>adding columns that exist in UDTT but do not in the C# model,<\/li>\n\n\n\n<li>skipping props that exist in the C# model but do not in UDTT<\/li>\n<\/ul>\n\n\n\n<p>when creating <em>DataTable<\/em>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Automate the creation of TVP<\/strong><\/h2>\n\n\n\n<p>All in all, we&#8217;ve already learned how to pass TVP using Dapper, so what more can we say? Well, imagine we are using many different UDTTs in the application or there are frequent changes in their structure during development. We are then forced to manually define <em>DataTable<\/em> structure for all UDTTs and correct them if there are any changes. Of course, we don&#8217;t want to waste time on such things, so we&#8217;ll try to automate the process of building <em>DataTable<\/em>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Automated TVP creation \u2013 1<sup>st<\/sup> approach<\/strong><\/h3>\n\n\n\n<p>The first thought that comes to mind is the automatic creation of <em>DataTable<\/em> based on the properties of the model. We won\u2019t have to do it every time a new UDTT appears. Just create a model in C# with the exact same properties and run some method that generates <em>DataTable<\/em>. Looks good? Okay, here we go!<\/p>\n\n\n\n<p>We can define an extension method that converts <em>IEnumerable&lt;T&gt;<\/em> to <em>DataTable<\/em> based on properties defined in <em>T<\/em><\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\npublic static DataTable ToDataTable&lt;T&gt;(this IEnumerable&lt;T&gt; enumerable)\n{\n    var dataTable = new DataTable();\n    var propertyDescriptorCollection = TypeDe-scriptor.GetProperties(typeof(T));\n    for (int i = 0; i &lt; propertyDescriptorCollection.Count; i++)\n    {\n        var propertyDescriptor = propertyDescriptorCollection&#x5B;i];\n        var type = propertyDescriptor.PropertyType;\n\n        if (type.IsGenericType &amp;&amp; type.GetGenericTypeDefinition() == typeof(Nullable&lt;&gt;))\n            type = Nullable.GetUnderlyingType(type)!;\n\n        dataTable.Columns.Add(propertyDescriptor.Name, type);\n    }\n    var values = new object&#x5B;propertyDescriptorCollection.Count];\n    foreach (T iListItem in enumerable)\n    {\n        for (int i = 0; i &lt; values.Length; i++)\n        {\n            values&#x5B;i] = propertyDescriptorCollec-tion&#x5B;i].GetValue(iListItem)!;\n        }\n        dataTable.Rows.Add(values);\n    }\n    return dataTable;\n}\n<\/pre><\/div>\n\n\n<p>and use this method when building TVP:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\nusing var conn = new SqlConnection(connectionString);\nconn.Open();\n\n\/\/ some example data\nList&lt;Project&gt; projects = new()\n{\n    new Project { Id = 1, Name = &quot;Name1&quot;, ProjectStartDate = DateTimeOff-set.Parse(&quot;2022-11-01&quot;), Active = true, Draft = false },\n    new Project { Id = 2, Name = &quot;Name2&quot;, ProjectStartDate = DateTimeOff-set.Parse(&quot;2022-12-01&quot;), Active = false, Draft = true }\n};\n\n\/\/ create DataTable\nvar projectsDT = projects.ToDataTable();\n\n\/\/ create parameters\nvar parameters = new\n{\n    projects = projectsDT.AsTableValuedParameter(&quot;&#x5B;dbo].&#x5B;udtt_Project]&quot;)\n};\n\n\/\/ execute Stored Procedure\nreturn await conn.ExecuteScalarAsync&lt;int&gt;(\n    &quot;&#x5B;dbo].&#x5B;ProcedureWhichAcceptsProjectsAsTVP]&quot;,\n    param: parameters,\n    commandType: CommandType.StoredProcedure);\n<\/pre><\/div>\n\n\n<p>This approach looks promising. We no longer have to define <em>DataTable<\/em> manually. We just need to invoke <em>ToDataTable<\/em> method on any <em>IEnumerable&lt;T&gt;<\/em>. Unfortunately, this approach has its downsides. Do you remember how Dapper creates TVP? It maps columns in a certain order, not by column names. If we think about it, there are several situations where this approach won&#8217;t work:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>If the order of C# model props doesn\u2019t match the UDTT columns order (maybe there will be some inheritance in models that will mess with the order or something else?).<\/li>\n\n\n\n<li>If there is any additional property in the C# model that does not exist in UDTT (we will try to pass too many columns and get an error).<\/li>\n\n\n\n<li>If there is a column in UDTT that does not exist in the C# model (fewer columns than required for example messes with the order and may cause type compatibility error on columns).<\/li>\n<\/ul>\n\n\n\n<p>Considering the above, this approach will work only when the number and order of the columns match exactly between the UDTT and C# model. The obvious advantage is the automatic <em>DataTable<\/em> creation, but considering the disadvantages of this approach, it can be said that it is not as versatile as we would like.<\/p>\n\n\n\n<p>After all, if in your project all C# models match UDTTs exactly and you will make sure that it stays that way, you can apply this approach. If you are even a little concerned about mismapping, I invite you to the next section where we will try to develop a better solution.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Automated TVP creation \u2013 2<sup>nd<\/sup> approach<\/strong><\/h3>\n\n\n\n<p>Our first approach had a lot of disadvantages that we need to take into account and eliminate them. But to prevent columns from getting out of order or to fill in missing columns, we need to know both the model definition in C# and the UDTT in the database. So, we will try to read the UDTT definition from the database and try to match each property to a specific UDTT column by name. No more mess with the columns\u2019 order or errors because the number of columns doesn\u2019t match.<\/p>\n\n\n\n<p>Let\u2019s see the implementation:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\nusing Dapper;\nusing System.Data;\n\nnamespace DapperTVPOrderedExample.DapperExtensions\n{\n    internal static class DapperExtensions\n    {\n        \/\/\/ &lt;summary&gt;\n        \/\/\/ This extension converts enumerable of objects to a Dapper TVP with columns ordered by T-SQL UDTT columns.\n        \/\/\/ Class properties names must correspond to T-SQL UDTT column names.\n        \/\/\/ &lt;\/summary&gt;\n        \/\/\/ &lt;typeparam name=&quot;T&quot;&gt;Type to convert from&lt;\/typeparam&gt;\n        \/\/\/ &lt;param name=&quot;enumerable&quot;&gt;&lt;\/param&gt;\n        \/\/\/ &lt;param name=&quot;typeName&quot;&gt;database type name; if schema is not defi-ned then &#039;dbo&#039; schema is used by default&lt;\/param&gt;\n        \/\/\/ &lt;param name=&quot;connection&quot;&gt;database connection needed to determine column order&lt;\/param&gt;\n        \/\/\/ &lt;param name=&quot;transaction&quot;&gt;optional transaction if conversion is done inside any&lt;\/param&gt;\n        \/\/\/ &lt;returns&gt;Table-Valued Parameter&lt;\/returns&gt;\n        public static async Task&lt;SqlMapper.ICustomQueryParameter&gt; AsTableValu-edParameter&lt;T&gt;(this IEnumerable&lt;T&gt; enumerable, string typeName, IDbConnection connection, IDbTransaction? transaction = null)\n        {\n            \/\/ set type schema and name\n            var schema = &quot;dbo&quot;;\n            var isolatedTypeName = &quot;&quot;;\n            var typeNameParts = typeName.Replace(&quot;&#x5B;&quot;, &quot;&quot;).Replace(&quot;]&quot;, &quot;&quot;).Split(&quot;.&quot;);\n            if (typeNameParts.Length == 1)\n            {\n                isolatedTypeName = typeNameParts&#x5B;0];\n            }\n            else if (typeNameParts.Length == 2)\n            {\n                schema = typeNameParts&#x5B;0];\n                isolatedTypeName = typeNameParts&#x5B;1];\n            }\n            else\n            {\n                throw new ArgumentException($&quot;Argument typeName = {typeName} can have only one dot dividing schema from type name.&quot;);\n            }\n\n            \/\/ query for column names in order\n            var typeColumnsQuery = $@&quot;SELECT c.name\n            FROM sys.columns c\n            join sys.table_types t on t.type_table_object_id = c.object_id\n            join sys.schemas s on s.schema_id = t.schema_id\n            where s.name = &#039;{schema}&#039;\n            and t.&#x5B;name] = &#039;{isolatedTypeName}&#039;\n            order by column_id;&quot;;\n\n            var orderedColumnNames = await connec-tion.QueryAsync&lt;string&gt;(typeColumnsQuery, transaction: transaction);\n            if (orderedColumnNames == null || !orderedColumnNames.Any())\n                throw new Exception($&quot;Table type {typeName} returned no co-lumns&quot;);\n\n            \/\/ create DataTable\n            using var dt = new DataTable();\n            var props = typeof(T).GetProperties();\n\n            \/\/ add columns in specific order\n            foreach (string colName in orderedColumnNames)\n            {\n                var prop = props.SingleOrDefault(p =&gt; p.Name == colName);\n                if (prop == null)\n                    dt.Columns.Add(colName);\n                else\n                    dt.Columns.Add(prop.Name, Nulla-ble.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType);\n            }\n\n            \/\/ add row to DataTable for every item\n            foreach (var item in enumerable)\n            {\n                var row = dt.NewRow();\n                \/\/ find corresponding prop for every column and add its value or null\n                foreach (string colName in orderedColumnNames)\n                {\n                    try\n                    {\n                        var prop = props.SingleOrDefault(p =&gt; p.Name == colNa-me);\n                        if (prop == null)\n                            row&#x5B;colName] = (object)DBNull.Value;\n                        else\n                        {\n                            var value = prop.GetValue(item, null);\n                            row&#x5B;prop.Name] = value ?? (object)DBNull.Value;\n                        }\n                    }\n                    catch (Exception ex)\n                    {\n                        throw new Exception($&quot;Exception when assigning value to DataTable column when colName = &#039;{colName}&#039;&quot;, ex);\n                    }\n\n                }\n                dt.Rows.Add(row);\n            }\n            \/\/ return TVP\n            return dt.AsTableValuedParameter(typeName);\n        }\n\n        \/\/\/ &lt;summary&gt;\n        \/\/\/ This extension converts an object to a 1-row Dapper TVP with co-lumns ordered by T-SQL UDTT columns.\n        \/\/\/ Class properties names must coorrestopond to T-SQL UDTT column names.\n        \/\/\/ &lt;\/summary&gt;\n        \/\/\/ &lt;typeparam name=&quot;T&quot;&gt;Type to convert from&lt;\/typeparam&gt;\n        \/\/\/ &lt;param name=&quot;item&quot;&gt;&lt;\/param&gt;\n        \/\/\/ &lt;param name=&quot;typeName&quot;&gt;database type name; if schema is not defi-ned then &#039;dbo&#039; schema is used by default&lt;\/param&gt;\n        \/\/\/ &lt;param name=&quot;connection&quot;&gt;database connection needed to determine column order&lt;\/param&gt;\n        \/\/\/ &lt;param name=&quot;transaction&quot;&gt;optional transaction if conversion is done inside any&lt;\/param&gt;\n        \/\/\/ &lt;returns&gt;Table-Valued Parameter&lt;\/returns&gt;\n        public static async Task&lt;SqlMapper.ICustomQueryParameter&gt; AsTableValu-edParameter&lt;T&gt;(this T item, string typeName, IDbConnection connection, IDb-Transaction? transaction = null) where T : class\n        {\n            return await (new List&lt;T&gt; { item }).AsEnumerable().AsTableValuedParameter(typeName, connection, transaction);\n        }\n\n        \/\/\/ &lt;summary&gt;\n        \/\/\/ This extension converts list of objects to a Dapper TVP with co-lumns ordered by T-SQL UDTT columns.\n        \/\/\/ Class properties names must coorrestopond to T-SQL UDTT column names.\n        \/\/\/ &lt;\/summary&gt;\n        \/\/\/ &lt;typeparam name=&quot;T&quot;&gt;Type to convert from&lt;\/typeparam&gt;\n        \/\/\/ &lt;param name=&quot;list&quot;&gt;&lt;\/param&gt;\n        \/\/\/ &lt;param name=&quot;typeName&quot;&gt;database type name; if schema is not defi-ned then &#039;dbo&#039; schema is used by default&lt;\/param&gt;\n        \/\/\/ &lt;param name=&quot;connection&quot;&gt;database connection needed to determine column order&lt;\/param&gt;\n        \/\/\/ &lt;param name=&quot;transaction&quot;&gt;optional transaction if conversion is done inside any&lt;\/param&gt;\n        \/\/\/ &lt;returns&gt;Table-Valued Parameter&lt;\/returns&gt;\n        public static async Task&lt;SqlMapper.ICustomQueryParameter&gt; AsTableValu-edParameter&lt;T&gt;(this List&lt;T&gt; list, string typeName, IDbConnection connection, IDbTransaction? transaction = null)\n        {\n            return await list.AsEnumerable().AsTableValuedParameter(typeName, connection, transaction);\n        }\n\n\n    }\n}\n<\/pre><\/div>\n\n\n<p>In the code above we are only using <em>Dapper<\/em> and <em>System.Data<\/em>. Things like namespace, class name, or method names can be changed so feel free to do it if you want to. I named methods <em>AsTableValuedParameter<\/em> (the same name as the original Dapper method) to state clearly that is returns the same thing as Dapper.<\/p>\n\n\n\n<p>It\u2019s worth mentioning that we have 3 static methods where 2 of them only pass data further according to the corresponding type. We will focus then on the 1<sup>st<\/sup> method which contains all the logic.<\/p>\n\n\n\n<p>The 1<sup>st<\/sup> parameter is an <em>Enumerable<\/em> of objects that contain data to convert. Next, we have to specify the name of the UDTT that we want to convert to. It can be passed with or without schema or square brackets. Then we need to have a database connection. It will be used to get the UDTT column order. In the end, we have an optional transaction parameter that can be passed if the conversion occurs as part of any transaction.<\/p>\n\n\n\n<p>The logic is pretty simple. Initially, we are setting schema and the UDTT name string variables. Then we are querying the database to get UDTT column order. Based on the result we are adding columns to <em>DataTable<\/em> in the correct order with the type taken from found prop in the C# model (or without the type if a prop is not found). Later we are adding a row to <em>DataTable<\/em> for every object in the input <em>Enumerable<\/em> and assign values to proper columns based on name (or null if not found). In the end, we are invoking Dapper\u2019s original <em>AsTableValuedParameter<\/em> method and returning its result.<\/p>\n\n\n\n<p>How do we use it? First of all, remember to add <em>using DapperTVPOrderedExample.DapperExtensions<\/em>. Then we can use our methods on any object, IEnumerable, or List of objects directly like this:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\nusing var conn = new SqlConnection(connectionString);\nconn.Open();\n\n\/\/ some example data\nList&lt;Project&gt; projects = new()\n{\n    new Project { Id = 1, Name = &quot;Name1&quot;, ProjectStartDate = DateTimeOffset.Parse(&quot;2022-11-01&quot;), Active = true, Draft = false },\n    new Project { Id = 2, Name = &quot;Name2&quot;, ProjectStartDate = DateTimeOffset.Parse(&quot;2022-12-01&quot;), Active = false, Draft = true }\n};\n\n\/\/ create parameters\nvar parameters = new\n{\n    projects = await projects.AsTableValuedParameter(&quot;&#x5B;dbo].&#x5B;udtt_Project]&quot;, conn)\n};\n\n\/\/ execute Stored Procedure\nreturn await conn.ExecuteScalarAsync&lt;int&gt;(\n    &quot;&#x5B;dbo].&#x5B;ProcedureWhichAcceptsProjectsAsTVP]&quot;,\n    param: parameters,\n    commandType: CommandType.StoredProcedure);\n<\/pre><\/div>\n\n\n<p>And that\u2019s all. We do not have to worry about:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>proper column order,<\/li>\n\n\n\n<li>adding columns that exist in UDTT but do not in the C# model,<\/li>\n\n\n\n<li>skipping props that exist in the C# model but do not in UDTT.<\/li>\n<\/ul>\n\n\n\n<p>However, we need to pay attention to a few other things:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>property names must match UDTT column names,<\/li>\n\n\n\n<li>if there is an additional column in UDTT but not in the C# model, the column in TVP will be added with all null values without any warning,<\/li>\n\n\n\n<li>every conversion to TVP will send an additional query to the database.<\/li>\n<\/ul>\n\n\n\n<p>Of course, we can develop this idea further to eliminate these problems. We can implement an <em>Attribute<\/em> for properties that will hold UDTT column name mapping if we want to name properties differently. We can implement logging that will alert if some UDTT column will not be matched with any class property. Also, we can think about some cache if we use the UDTT quite often in our application to reduce the number of queries to the database for UDTT definition. This code is just <strong>a general idea to inspire you to explore further.<\/strong><\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Conclusion<\/strong><\/h2>\n\n\n\n<p>Supporting TVP in your application can offer great benefits in terms of efficiency. I hope automation of TVP creation presented in the \u201c2<sup>nd<\/sup> approach&#8221; will make your life easier.<\/p>\n\n\n\n<p>Remember, even though this example is using SQL Server\/Azure SQL, you can use it with any other database system. It is just a matter of replacing the query that is responsible for getting UDTT columns in the correct order.<\/p>\n\n\n\n<p><\/p>\n\n\n<div class=\"kk-star-ratings kksr-auto kksr-align-left kksr-valign-bottom\"\n    data-payload='{&quot;align&quot;:&quot;left&quot;,&quot;id&quot;:&quot;16&quot;,&quot;slug&quot;:&quot;default&quot;,&quot;valign&quot;:&quot;bottom&quot;,&quot;ignore&quot;:&quot;&quot;,&quot;reference&quot;:&quot;auto&quot;,&quot;class&quot;:&quot;&quot;,&quot;count&quot;:&quot;0&quot;,&quot;legendonly&quot;:&quot;&quot;,&quot;readonly&quot;:&quot;&quot;,&quot;score&quot;:&quot;0&quot;,&quot;starsonly&quot;:&quot;&quot;,&quot;best&quot;:&quot;5&quot;,&quot;gap&quot;:&quot;2&quot;,&quot;greet&quot;:&quot;&quot;,&quot;legend&quot;:&quot;0\\\/5&quot;,&quot;size&quot;:&quot;30&quot;,&quot;title&quot;:&quot;Using Table-Valued Parameters with Dapper in .NET&quot;,&quot;width&quot;:&quot;0&quot;,&quot;_legend&quot;:&quot;{score}\\\/5&quot;,&quot;font_factor&quot;:&quot;1.25&quot;}'>\n            \n<div class=\"kksr-stars\">\n    \n<div class=\"kksr-stars-inactive\">\n            <div class=\"kksr-star\" data-star=\"1\" style=\"padding-right: 2px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 30px; height: 30px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"2\" style=\"padding-right: 2px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 30px; height: 30px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"3\" style=\"padding-right: 2px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 30px; height: 30px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"4\" style=\"padding-right: 2px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 30px; height: 30px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"5\" style=\"padding-right: 2px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 30px; height: 30px;\"><\/div>\n        <\/div>\n    <\/div>\n    \n<div class=\"kksr-stars-active\" style=\"width: 0px;\">\n            <div class=\"kksr-star\" style=\"padding-right: 2px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 30px; height: 30px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 2px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 30px; height: 30px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 2px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 30px; height: 30px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 2px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 30px; height: 30px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 2px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 30px; height: 30px;\"><\/div>\n        <\/div>\n    <\/div>\n<\/div>\n                \n\n<div class=\"kksr-legend\" style=\"font-size: 24px;\">\n            <span class=\"kksr-muted\"><\/span>\n    <\/div>\n    <\/div>\n","protected":false},"excerpt":{"rendered":"<p>If you are working with SQL Server or Azure SQL, you probably heard about Table-Valued Parameters. If by any chance &hellip; <a class=\"continued-btn\" href=\"https:\/\/sii.ua\/blog\/en\/using-table-valued-parameters-with-dapper-in-net\/\">Continued<\/a><\/p>\n","protected":false},"author":16,"featured_media":65,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_editorskit_title_hidden":false,"_editorskit_reading_time":0,"_editorskit_is_block_options_detached":false,"_editorskit_block_options_position":"{}","inline_featured_image":false,"footnotes":""},"categories":[1],"tags":[29,28,27,26,25],"class_list":["post-16","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-hard-development","tag-net","tag-dapper","tag-sql","tag-table-valued-parameters","tag-udtt"],"acf":[],"aioseo_notices":[],"featured_media_url":"https:\/\/sii.ua\/blog\/wp-content\/uploads\/2023\/03\/Wykorzystanie-Table-Valued-Parameters-z-Dapperem-w-.NET_.jpg","category_names":["Hard development"],"_links":{"self":[{"href":"https:\/\/sii.ua\/blog\/en\/wp-json\/wp\/v2\/posts\/16"}],"collection":[{"href":"https:\/\/sii.ua\/blog\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/sii.ua\/blog\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/sii.ua\/blog\/en\/wp-json\/wp\/v2\/users\/16"}],"replies":[{"embeddable":true,"href":"https:\/\/sii.ua\/blog\/en\/wp-json\/wp\/v2\/comments?post=16"}],"version-history":[{"count":3,"href":"https:\/\/sii.ua\/blog\/en\/wp-json\/wp\/v2\/posts\/16\/revisions"}],"predecessor-version":[{"id":577,"href":"https:\/\/sii.ua\/blog\/en\/wp-json\/wp\/v2\/posts\/16\/revisions\/577"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/sii.ua\/blog\/en\/wp-json\/wp\/v2\/media\/65"}],"wp:attachment":[{"href":"https:\/\/sii.ua\/blog\/en\/wp-json\/wp\/v2\/media?parent=16"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/sii.ua\/blog\/en\/wp-json\/wp\/v2\/categories?post=16"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/sii.ua\/blog\/en\/wp-json\/wp\/v2\/tags?post=16"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}