PAN.DapperLambdaSQL
1.1.0
See the version list below for details.
dotnet add package PAN.DapperLambdaSQL --version 1.1.0
NuGet\Install-Package PAN.DapperLambdaSQL -Version 1.1.0
<PackageReference Include="PAN.DapperLambdaSQL" Version="1.1.0" />
<PackageVersion Include="PAN.DapperLambdaSQL" Version="1.1.0" />
<PackageReference Include="PAN.DapperLambdaSQL" />
paket add PAN.DapperLambdaSQL --version 1.1.0
#r "nuget: PAN.DapperLambdaSQL, 1.1.0"
#:package PAN.DapperLambdaSQL@1.1.0
#addin nuget:?package=PAN.DapperLambdaSQL&version=1.1.0
#tool nuget:?package=PAN.DapperLambdaSQL&version=1.1.0
PAN.DapperLambdaToSql
PAN.DapperLambdaToSql es una librería ligera que extiende Dapper y Dapper.Contrib, permitiendo realizar operaciones genéricas como UPDATE y EXIST utilizando expresiones lambda (Expression<Func<T, bool>>), al estilo de Entity Framework.
⚠️ Importante: Esta librería está diseñada para usarse exclusivamente con Dapper y Dapper.Contrib. Nota: Esta librería no es compatible con Entity Framework ni con Entity Framework Core. Está enfocada en simplificar el uso de Dapper para operaciones comunes sin necesidad de escribir SQL manualmente.
✨ Características
- ✅ Actualización de entidades genéricas con
UpdateAsync - ✅ Verificación de existencia con
ExistAsync - ✅ Compatible con
DapperyDapper.Contrib: respeta[Table],[Key],[ExplicitKey],[Write(false)]y[Computed] - ✅ Varias convenciones de clave primaria:
Id,IDyClase+Id - ✅ Funciona con SQL Server y MySQL / MariaDB, delimitando los identificadores según el motor
- ✅ Sin boilerplate: elimina la necesidad de escribir SQL manual para cada entidad
💡 Instalación
dotnet add package PAN.DapperLambdaToSql
🚀 Uso
📌 Actualizar cualquier entidad con UpdateAsync
Este método permite actualizar cualquier entidad genérica sin escribir SQL manualmente. Solo necesitas asegurarte de que la entidad tenga una clave primaria reconocible —ver Clave primaria: nombres soportados— y que las propiedades que deseas actualizar no sean null.
// En tu servicio o repositorio genérico
public async Task<bool> UpdateAsync(T entity)
{
return await _context.UpdateAsync(entity);
}
👉 Internamente genera y ejecuta dinámicamente una consulta SQL como esta:
UPDATE NombreTabla SET Columna1 = @Columna1, Columna2 = @Columna2 WHERE Id = @Id
🗄️ Motores soportados
El motor se detecta solo, a partir del tipo de conexión que le pasas. No hay nada que configurar:
| Motor | Se activa con | Identificadores |
|---|---|---|
| SQL Server | SqlConnection (System.Data.SqlClient o Microsoft.Data.SqlClient) |
[Nombre] |
| MySQL / MariaDB | MySqlConnection (MySql.Data o MySqlConnector) |
`Nombre` |
| Otros | cualquier otra conexión | sin delimitar |
// La misma llamada, el SQL correcto para cada motor
using var cn = new SqlConnection(cs); // UPDATE [Roles] SET [Key] = @Key WHERE [Id] = @Id
using var cn = new MySqlConnection(cs); // UPDATE `Roles` SET `Key` = @Key WHERE `Id` = @Id
await cn.UpdateAsync(role);
Delimitar los identificadores permite usar columnas cuyo nombre es palabra reservada —Key, Order, Group, Status—, que sin comillas provocan un error de sintaxis.
Si usas un proveedor que envuelve la conexión real (perfilado, tracing) y por eso no se reconoce, puedes forzar el dialecto:
DapperHelper.Dialect = SqlDialects.SqlServer; // o SqlDialects.MySql
⚠️ Es una configuración global. Si tu aplicación habla con dos motores a la vez, déjala en
nully confía en la detección automática.
PostgreSQL no está soportado. Postgres convierte a minúsculas todo identificador que no vaya entre comillas, así que la decisión de delimitar condiciona cómo debe crearse el esquema; se prefirió no adivinarla.
🔑 Clave primaria: nombres soportados
UpdateAsync resuelve la clave primaria recorriendo esta lista en orden y deteniéndose en la primera coincidencia:
| # | Qué se busca | Ejemplo |
|---|---|---|
| 1 | Propiedad con [Key] o [ExplicitKey] de Dapper.Contrib |
[ExplicitKey] public Guid Codigo { get; set; } |
| 2 | Una propiedad llamada Id, coincidencia exacta |
public int Id { get; set; } |
| 3 | Una propiedad llamada Id sin distinguir mayúsculas |
public int ID { get; set; } |
| 4 | Una propiedad llamada <NombreDeLaClase>Id |
public int UserId { get; set; } en la clase User |
Si ninguna coincide, se lanza una InvalidOperationException indicando la entidad y los nombres que se buscaron.
Los atributos siempre ganan a la convención, así que cualquier caso ambiguo se resuelve anotando la propiedad correcta con [ExplicitKey].
⚠️ Las llaves foráneas nunca se confunden con la primaria
El paso 4 busca un nombre concreto —el de la clase más Id—, nunca "cualquier propiedad que termine en Id". Es una distinción importante, porque las llaves foráneas siguen exactamente ese mismo patrón de nombres:
public class User
{
public int Id { get; set; } // ← clave primaria (resuelta en el paso 2)
public int? GymId { get; set; } // ← foránea: se actualiza como cualquier columna
public int RoleId { get; set; } // ← foránea: se actualiza como cualquier columna
}
En User el nombre buscado por el paso 4 sería UserId, así que ni GymId ni RoleId son candidatas. Y como la resolución se detiene en el paso 2, en este ejemplo el paso 4 ni siquiera llega a evaluarse.
Solo la clave primaria resuelta se excluye del SET. Todas las demás columnas, foráneas incluidas, se siguen actualizando con normalidad.
La clave da nombre al WHERE
La columna del WHERE y el parámetro toman el nombre real de la propiedad resuelta:
public class Producto
{
[ExplicitKey] public string Sku { get; set; }
public string Nombre { get; set; }
}
UPDATE Productos SET Nombre = @Nombre WHERE Sku = @Sku
📌 Actualizar cualquier entidad con UpdateAsync (actualización parcial)
Este método de extensión permite actualizar dinámicamente cualquier entidad genérica sin escribir SQL manualmente.
Solo actualizará las propiedades no nulas que no sean la clave primaria, por lo que es ideal para escenarios de actualización parcial (PATCH).
- ✅ Ventaja: No se sobreescriben columnas con
null,0oDateTime.MinValuesi no las envías.
Ejemplo de entidad:
public class Gym
{
public int Id { get; set; }
public int? Code { get; set; } // Campo que no se actualiza si no se envía
public string Name { get; set; }
public string Address { get; set; }
public string Phone { get; set; }
public DateTime? CreatedAt { get; set; } // Tampoco se actualiza si no se envía
}
Ejemplo de uso en un servicio o handler:
var gym = new Gym
{
Id = 1,
Name = "New Name",
Phone = "999-888-777"
// No enviamos Code ni CreatedAt => no se actualizan
};
bool actualizado = await gymRepository.UpdateGymAsync(gym);
if (actualizado)
{
Console.WriteLine("Actualización parcial exitosa 🚀");
}
else
{
Console.WriteLine("No se actualizó ningún registro.");
}
Ejemplo de uso en un repositorio
public async Task<bool> UpdateGymAsync(Gym gym)
{
return await _dbConnection.UpdateAsync(gym);
}
💡 Notas:
- La clave primaria se usa exclusivamente para el
WHEREen elUPDATE; nunca se incluye en elSET. - Las propiedades con
nullse ignoran y no se incluyen en la sentencia SQL. - Las propiedades marcadas con
[Write(false)]o[Computed]de Dapper.Contrib se excluyen delUPDATE. Es lo que necesitas para las propiedades de navegación que cargas con unJOINy que no existen como columna en la tabla, y para las columnas cuyo valor genera la base de datos (columnas calculadas,DEFAULT,rowversion, triggers). - Funciona con cualquier entidad cuya clave primaria siga alguna de las convenciones soportadas.
📌 Verificar existencia con ExistAsync
Este método permite consultar si existe una entidad que cumpla con una condición específica, usando expresiones lambda al estilo de Entity Framework.
// En tu servicio o repositorio genérico
public async Task<bool> ExistAsync(Expression<Func<T, bool>> predicate)
{
return await _context.ExistAsync(predicate);
}
Ejemplo:
bool existe = await _context.ExistAsync<User>(x => x.Email == "test@example.com");
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
1.1.0
- Compatibilidad con SQL Server y MySQL: los identificadores se delimitan segun el motor,
detectado a partir del tipo de conexion. Permite columnas con nombre reservado (Key, Order...).
- Se respetan [Write(false)] y [Computed] de Dapper.Contrib: las propiedades de navegacion
y las columnas calculadas dejan de incluirse en el UPDATE.
- La clave primaria se resuelve por [Key]/[ExplicitKey] o por convencion (Id, ID, Clase+Id),
en lugar de estar fijada al nombre literal "Id".
- La cache de nombres de tabla pasa a ser segura para concurrencia.