miércoles, 31 de enero de 2018

SQL dinámico con ExpandoObject y Dapper

Crear una consulta SQL con una condición de filtrado dinámico es algo que tarde o temprano sale a la palestra

En la solución que se muestra a continuación, se usa un objeto de tipo ExpandoObject que permite agregar propiedades en tiempo de ejecución. Lo explica muy bien @eiximenis en el post Var, object y dynamic

Si a ExpandoObject le sumamos Dapper, la solución es segura contra SQL injection y flexible en cuanto a su confección

En el ejemplo he procurado que sea vea que podemos tanto agregar propierdades de forma dinámica por el mero hecho de asignar un valor, así como castear el ExpandoObject a un dicccionario para agregar propiedades de forma dinámica

Primero el código SQL para crear una tabla y que el ejemplo funcione

CREATE TABLE [dbo].[Table_1](
	[Id] [int] NOT NULL,
	[TenantId] [int] NOT NULL,
	[C1] [int] NULL,
	[C2] [nvarchar](50) NULL,
	[C3] [datetime2](7) NULL,
 CONSTRAINT [PK_Table_1] PRIMARY KEY CLUSTERED 
(
	[Id] ASC
)

Ahora el código

using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Dynamic;
using System.Linq;
using Dapper;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            QueryExample(new Dictionary<string, object>()
            {
                { "C1", 1 },
                { "C2", "Foo" },
                { "C3", DateTime.Now }
            });
        }

        private static void QueryExample(IDictionary<string, object> criterias)
        {
            const string connectionString = @"Server=(LocalDB)\MSSQLLocalDB;Database=Example;Trusted_Connection=True;";

            var sql = "SELECT * FROM Table_1 WHERE TenantId = @TenantId";

            var columnsWhitelist = new[] { "C1", "C2", "C3" };

            foreach (var criteria in criterias)
            {
                if (!columnsWhitelist.Contains(criteria.Key))
                {
                    throw new ArgumentException($"{criteria.Key} is not allowed as column name", nameof(criteria));
                }
                sql += $" AND {criteria.Key} = @{criteria.Key}";
            }

            dynamic parameters = new ExpandoObject();

            parameters.TenantId = 1;

            var dictionary = (IDictionary<string, object>)parameters;
            foreach (var criteria in criterias)
            {
                dictionary.Add(criteria.Key, criteria.Value);
            }

            using (var connection = new SqlConnection(connectionString))
            {
                connection.Query(sql, (ExpandoObject)parameters);
            }

            Console.ReadKey();
        }
    }
}

El SQL que se ejecuta finalmente es el siguiente

exec sp_executesql N'SELECT * FROM Table_1 WHERE TenantId = @TenantId AND C1 = @C1 AND C2 = @C2 AND C3 = @C3',N'@TenantId int,@C1 int,@C2 nvarchar(4000),@C3 datetime',@TenantId=1,@C1=1,@C2=N'Foo',@C3='2018-01-31 18:58:56.823'

Un saludo!