Etiqueta: visual studio code

Unit of Work con Dapper

Como mencioné en el post anterior, recientemente comencé a utilizar Dapper para algunos proyectos en mi trabajo y a causa de ellos tuve la necesidad de migrar algunas operaciones al otro sistema y para ello se deben sincronizar datos entre los dos sistemas.

Contexto

Estos datos que necesitaba sincronizar son listas pero existen algunas que dependen de otra lista, por ejemplo, si se tratara de un punto de venta es probable tengamos entidades como Caja y ésta dependería de Sucursal.

Bajo éste escenario el procedimiento a seguir es el siguiente:

  1. Se tienen dos sistemas a sincronizar (S1 y S2)
  2. Se creará una aplicación de consola que recabará las listas de S1 y las enviará a S2
  3. S2 contará con un WebApi para recibir las listas enviadas por S1

Bien ahora, desde el punto de vista de S2 tenemos los siguientes escenarios:

  1. El elemento recibido es nuevo y hay que registrarlo
  2. El elemento ya existe y se debe actualizar en S2

Desglosando el Problema

Como mencioné anteriormente, hay listas que dependen de otra y esto nos genera un caso “especial” ya que si se agregan elementos nuevos a una lista que tiene dependencias y estas mismas aun no se han registrado esto genera un conflicto en S2 al tratar de registrar el elemento recibido.

Ejemplo: Supongamos que en S1 existen las entidades Sucursal y Caja, se entiende que la caja pertenece a alguna sucursal, dicho en otras palabras, la caja depende de la sucursal, esto implica que para poder registrar una caja ya debe estar registrada la sucursal a la que pertenece, entonces: ¿Qué pasaría si recibimos una caja de una sucursal nueva y aun no se registra la sucursal?, obviamente esto es un error que se soluciona de dos formas:

  1. Asegurarnos de que la sincronización dé prioridad a las listas que tengan dependencias.
  2. Si el elemento recibido tiene una dependencia que no existe, la dependencia se registra “en blanco” y al recibir la lista de la que depende se actualizaría la dependencia “en blanco”.

Para éste caso donde la sincronización será automática no creo conveniente seguir el camino de la opción número 1 ya que si el sistema crece podríamos toparnos con referencias circulares (cosa que ya me ha pasado anteriormente) y esto provocaría otro problema, así que opté por seguir la opción número 2.

Patrón Unit of Work

Ahora que he decidido registrar las dependencias “en blanco” necesito hacer las inserciones de las dependencias en una transacción y para ello utilizaré el patrón UnitOfWork y para ello usare la siguiente clase:

C#
public class UnitOfWork : IDisposable
{
    public IDbConnection Connection { get; }
    public IDbTransaction Transaction { get; }

    public UnitOfWork()
    {
        Connection = Repository.GetConnection();
        Connection.Open();
        Transaction = Connection.BeginTransaction();
    }

    public void Commit() => Transaction.Commit();
    public void Rollback() => Transaction.Rollback();

    public void Dispose()
    {
        Transaction?.Dispose();
        Connection?.Dispose();
    }
}

Esta clase será la encargada crear la conexión a la base de datos y de hacer commit o rollback a la transacción.

A continuación les muestro como usar la clase UnitOfWork:

C#
public static Result<long?> InsertCaja(CajaRequest request)
{
    using (var unit = new UnitOfWork())
    {
        try
        {
            Result<long?> result = InsertCajaInterna(request, unit.Connection, unit.Transaction);
            unit.Commit();
            return result;
        }
        catch (Exception ex)
        {
            unit.Rollback();
            log.Error($"Message: {ex.Message} | InnerException: {ex.InnerException} | StackTrace: {ex.StackTrace}");
            return Result<long?>.Fail("Ocurrio un error al registrar la caja");
        }
    }
}

Como se puede ver creé una función “Interna” pasando como parámetros tanto la conexión como la transacción, de esta manera todas las operaciones de base de datos se harán en conjunto.

NOTA: Estoy utilizando la clase Result para manejar las respuestas de las operaciones, su código es el siguiente:

C#
public class Result
{
    protected Result() { }

    public bool Success { get; protected set; }
    public string Message { get; protected set; }

    public static Result Ok(string message = "") =>
        new Result { Success = true, Message = message };

    public static Result Fail(string message) =>
        new Result { Success = false, Message = message };
}

public class Result<T> : Result
{
    public T Data { get; set; } = default;

    public static Result<T> Ok(T data, string message = "")
        => new Result<T> { Success = true, Message = message, Data = data };

    public new static Result<T> Fail(string message)
        => new Result<T> { Success = false, Message = message, Data = default };
}

A continuación te muestro el código de la función InsertCajaInterna:

C#
private static Result<long?> InsertCajaInterna(CajaRequest request, IDbConnection con, IDbTransaction trx)
{
    long? sucursalId = null;

    // Se obtiene la sucursal
    sucursalId = con.ExecuteScalar<long?>(
        "SELECT SucursalId FROM Sucursal WHERE IdExterno = @IdExternoSucursal"
        , new { IdExternoSucursal = request.sucursalId }, trx);

    // Si no existe la sucursal se registra en blanco
    if (!sucursalId.HasValue)
    {
        Result<long?> resultSucursal = InsertSucursalInterna(new SucursalRequest
            { idExterno = request.sucursalId, IsEmptiInsert = true }, con, trx);

        sucursalId = resultSucursal.Data;

        if (!sucursalId.HasValue)
            return Result<long?>.Fail("No se pudo identificar la sucursal");
    }

    // Se valida si existe una caja con el mismo IdExterno
    Result<long?> cajaId = con.ExecuteScalar<long?>("SELECT CajaId FROM Caja WHERE IdExterno = @IdExterno",
        new { IdExterno = request.idExterno }, trx);

    if (!cajaId.HasValue)
    {
        // Se inserta la caja nueva
        cajaId = con.ExecuteScalar<long?>(
            "INSERT INTO Caja (IdExterno, NombreCaja, SucursalId) " +
            " VALUES (@IdExterno, UPPER(@NombreCaja), @SucursalId); " +
            "SELECT SCOPE_IDENTITY();"
            , new
            {
                IdExterno = request.idExterno,
                NombreCaja = request.nombreCaja,
                SucursalId = sucursalId.Value
            }, trx);
    }
    else
    {
        // Se actualiza la caja existente
        con.Execute(
            "UPDATE Caja SET NombreCaja = UPPER(@NombreCaja), SucursalId = @SucursalId " +
            "WHERE IdExterno = @IdExterno "
            , new
            {
                IdExterno = request.idExterno,
                NombreCaja = request.nombreCaja,
                SucursalId = sucursalId.Value
            }, trx);
    }

    log.Info($"CajaId: {cajaId} | Nombre: '{request.nombreCaja}' | SucursalId: {sucursalId} | IdExterno: {request.idExterno}");

    return Result<long?>.Ok(cajaId, "Caja registrada correctamente");
}

De esta manera he conseguido insertar la caja y su dependencia en una transacción para mantener la integridad de los datos.

¿Cuándo usar Unit of Work?

  1. Cuando se requieren múltiples operaciones dependientes entre sí.
  2. Procesos de negocio complejos.

Conclusión

Implementar el patrón Unit of Work con Dapper no es obligatorio, pero existen escenarios donde se requiere que múltiples operaciones se ejecuten en conjunto, y es aquí donde se vuelve una pieza clave para mantener la integridad de los datos.

Usando Dapper con C# y SQL Server

Recientemente en mi trabajo iniciamos el desarrollo de una aplicación donde varios desarrolladores íbamos a estar trabajando al mismo tiempo, cada quien en módulos diferentes. Y como suele pasar esto implica cambios constantes en la base de datos.

Si has trabajado con Entity Framework en equipo, probablemente ya te has topado con el problema de que el DbContext empieza a desincronizarse, migraciones que chocan, modelos que ya no representan la realidad de la base de datos y terminas perdiendo tiempo en cosas que no deberían ser problema.

Así que decidí buscar una alternativa más simple, más directa y que no presentara este problema. Ahí fue donde me encontré con Dapper.

¿Qué es Dapper?

Dapper es un micro ORM desarrollado por el equipo de Stack Overflow que diferencia de Entity Framework, aquí no hay magia pesada ni abstracciones complejas, escribes tu SQL y Dapper se encarga de mapear los resultados a tus objetos.

¿Por qué decidí usarlo?

En mi caso, lo que necesitaba era:

  • Evitar problemas de sincronización entre modelo y base de datos
  • Tener control total sobre las consultas
  • Algo rápido de implementar
  • Que funcionara bien trabajando en equipo

Aplicación de Control Gastos

Para probar Dapper decidí hacer una aplicación ultra sencilla para registrar gastos ya que necesitaba aprender a usarlo antes de implementarlo en un proyecto en mi trabajo.

Instalación

Nada complicado, como cualquier paquete de NuGet:

Install-Package Dapper

Y para SQL Server:

dotnet add package Microsoft.Data.SqlClient

Configuración básica

Yo normalmente manejo una clase base para obtener la conexión, algo así:

C#
public class BaseRepository
{
    private readonly IConfiguration _configuration;

    public BaseRepository(IConfiguration configuration)
    {
        _configuration = configuration;
    }

    protected SqlConnection GetConnection()
    {
        return new SqlConnection(_configuration.GetConnectionString("DefaultConnection"));
    }
}

Esto me permite reutilizar la conexión en todos mis repositorios.

Modelo de ejemplo

C#
public class Usuario
{
    public int Id { get; set; }
    public string Nombre { get; set; }
    public string Email { get; set; }
}

SELECT

Aquí es donde Dapper brilla por su simplicidad.

C#
public Usuario[] GetUsuarios()
{
    using var con = GetConnection();

    return con.Query<Usuario>("SELECT * FROM Usuarios")
              .ToArray();
}

Con parámetros:

C#
public Usuario? GetUsuarioById(int id)
{
    using var con = GetConnection();

    return con.QueryFirstOrDefault<Usuario>(
        "SELECT * FROM Usuarios WHERE Id = @Id",
        new { Id = id });
}

INSERT

C#
public void InsertUsuario(Usuario usuario)
{
    using var con = GetConnection();

    con.Execute(
        "INSERT INTO Usuarios (Nombre, Email) VALUES (@Nombre, @Email)",
        usuario);
}

UPDATE

C#
public void UpdateUsuario(Usuario usuario)
{
    using var con = GetConnection();

    con.Execute(
        @"UPDATE Usuarios 
          SET Nombre = @Nombre, Email = @Email 
          WHERE Id = @Id",
        usuario);
}

DELETE

C#
public void DeleteUsuario(int id)
{
    using var con = GetConnection();

    con.Execute(
        "DELETE FROM Usuarios WHERE Id = @Id",
        new { Id = id });
}

Transacciones

Aquí es donde ya se pone interesante, porque puedes hacer exactamente lo que necesitas sin pelearte con el ORM.

En uno de mis repositorios, por ejemplo, manejo algo así:

  • Inserto una cuenta
  • Si tiene saldo inicial, inserto también un movimiento
  • Todo dentro de una transacción

Ejemplo simplificado:

C#
using var con = GetConnection();
con.Open();

using var trx = con.BeginTransaction();

try
{
    var cuentaId = con.ExecuteScalar<long>(
        @"INSERT INTO Cuentas (Nombre) 
          VALUES (@Nombre);
          SELECT SCOPE_IDENTITY();",
        new { Nombre = "Cuenta demo" },
        trx);

    con.Execute(
        @"INSERT INTO Movimientos (CuentaId, Importe)
          VALUES (@CuentaId, @Importe)",
        new { CuentaId = cuentaId, Importe = 100 },
        trx);

    trx.Commit();
}
catch
{
    trx.Rollback();
    throw;
}

Este tipo de control es justo lo que estaba buscando.

Ventajas

Después de usarlo en algunos proyectos en mi día a día, estas fueron las principales ventajas:

  • Menos fricción trabajando en equipo
  • No dependes de migraciones
  • Código más predecible
  • Mejor rendimiento
  • Debug mucho más sencillo

¿Reemplaza a Entity Framework?

No necesariamente, Entity Framework sigue siendo muy útil en muchos escenarios, sobre todo cuando:

  • No quieres escribir SQL
  • Tu modelo es muy estable
  • Necesitas rapidez para prototipar

Pero en escenarios como el mío (muchos cambios y varios desarrolladores), creo Dapper se siente mucho más cómodo.

Conclusión

En mi caso, Dapper no solo resolvió un problema que me representaría usar Entity Framework sino que me ayudó a mejorar el flujo de trabajo al no tener que sincronizar mi base de datos con el modelo. Aunque no descarto la idea de usar Entity Framework pero será en una etapa donde los cambios en la base de datos no sean tan recurrentes como ahora.

Solución ERR_PROXY_CONNECTION_FAILED y su Odisea

Hoy les vengo a contar la odisea que viví con el error ERR_PROXY_CONNECTION_FAILED y como fue que dí con la solución.

Estos días me ha pasado algo muy extraño y no se cual sea la razón de que pasara. Con la situación actual y el Home Office, además de mi equipo personal, tengo en casa la computadora de mi trabajo. Y hace poco ví que empezaron a presentarse problemas en ambos equipos bastante extraños.

La caída de Skype

El primer síntoma se presento en Skype en el equipo del trabajo, cuando me conectaba todos los contactos aparecían como desconectados, pero yo podía ver claramente que no era así desde el celular. No le preste mucha atención porque a pesar de eso, podía seguir enviando y recibiendo mensajes, solo que cuando enviaba o recibía algún archivo se tardaba una eternidad. Pero poco poco después me di cuenta de que los mensajes solo me llegaban al celular y que en la computadora siempre aparecía una leyenda que decía “conectado…”. Lo reinstale varias veces y no se solucionó pero como en su mayoría solo lo uso para mensajería decidí usar el celular por mientras Skype arreglaba “su problema” con una actualización.

Le Siguió Visual Studio

Actualmente estoy desarrollando un proyecto en mi equipo personal y en el proceso necesitaba agregar una dependencia desde el administrador de paquetes NuGet. Pero me llevé una sorpresa al ver el siguiente error:

Después buscar soluciones sin éxito en Stack Overflow e intentarlo varias veces por fin se instaló la dependencia y seguí trabajando, pero me pareció bastante extraño que me apareciera ese error tantas veces.

¿Que le pasa a Visual Studio Code?

Poco después para mi proyecto personal quise intentar hacer el backend en Deno, desde hace unos meses sigo el canal de BettaTech en youtube y me pareció interesante. Quise configurar deno en Visual Studio Code y para ello necesitaba instalar un plugin y ¡oh, sorpresa!, no podía hacerlo.

Estuve buscando en internet como solucionarlo y después de un rato, harto de estar batallando con todo, primero con Skype, luego con Visual Studio y ahora Visual Studio Code, me dispuse a dejar todo y ponerme a jugar un rato… sin saber que venia lo peor.

¡¡¿TAMBIEN STEAM?!!

Justo cuando me dispuse a jugar, abrí steam para buscar algo que me ayudara a olvidar la odisea que había estado viviendo los últimos días y me apareció el siguiente error:

Cuando creí que esto no ya no podía ser peor me puse a ver videos en youtube, mientras pensaba si el problema seria mi conexión a internet, cosa que no creía porque podía navegar normalmente, tampoco podía ser cosa de configuración (o eso pensaba, ya lo veremos más adelante) porque el problema se presentaba en dos equipos diferentes sin llegar a ningún lado.

La pista que me llevó a la victoria

Como todo amante de la tecnología cuando se liberó Microsoft Edge basado en Chromium lo instalé para probarlo y accidentalmente un día lo abrí y me apareció el siguiente error ERR_PROXY_CONNECTION_FAILED

La palabra clave aquí es proxy, aunque yo nunca he usado un proxy en mi equipo personal ni en el del trabajo, cuando menos ya tenía una pista de que podría ser, tanto Skype, Edge, Visual Studio y Visual Studio Code son productos que pertenecen a Microsoft así que posiblemente tengan algún componente embebido de Edge o Internet Explorer y que deben estar presentando el mismo problema de conexión y quiero suponer que Steam también. Así que abrí la configuración de proxy como lo sugirió Edge y no vi nada extraño.

Como se puede observar no tengo configurado ningún proxy, pero aun así esta activada la opción “Detectar la configuración automáticamente“, en lo personal me daba la impresión de que por defecto debería estar activada, pero ya que no utilizo ningún proxy procedí a desactivarla y probé nuevamente en steam y ¡volvió a funcionar la tienda!, ¡igual que el marketplace de Visual Studio Code, el Nuget Package Manager de Visual Studio y Skype!.

La verdad es que no sé como fue que paso, quiero suponer que lo causó alguna actualización de Windows, pero lo importante aquí es que ya puedo trabajar y jugar tranquilamente. De ahora en adelante cada ves que no pueda conectarme a algún servicio, el “ERR_PROXY_CONNECTION_FAILED” sera lo primero que se me venga a la mente.