Cómo hacer una inserción y actualización para un objeto con una propiedad de navegación usando Dapper.Rainbow (u opcionalmente usando Dapper.Contrib)

dapper dapper-rainbow micro-orm orm

Pregunta

Empecé a buscar a Dapper recientemente. Lo estoy probando y pude hacer CRUD básico y lo que quise decir con básico es trabajar en una clase con esta estructura:

public class Product {
    public int Id {get;set;}
    public string Name {get;set;}
}

Ahora estaba buscando algo que haría más fácil hacer inserciones y actualizaciones y encontré Dapper.Rainbow. Lo revisé y pude usarlo para obtener e insertar objetos como se describe arriba. Mi problema es que cuando el Product tiene una propiedad de navegación, no puedo hacer una inserción en ese campo. Entonces si tengo esto:

public class Product {
    public int Id {get;set;}
    public string Name {get;set;}
    public ProductCategory Category {get;set;}
}

No podré hacer esto:

// connection is a valid and opened connection            
 var db = TestDatabase.Init(connection , 300);
 var newId = db.Products.Insert(newProduct);

por esta razón:

The member Category of type ProductCategory  cannot be used as a parameter value

El problema se puede resolver si reemplazo Category con tipo int (el mismo tipo de datos en la base de datos). Sin embargo, si hago eso, no podré consultar un producto con su información de categoría, más que solo el identificador (de categoría).

Entonces, sin recurrir a Dapper sin formato, ¿cómo puedo hacer una inserción y actualización utilizando una clase con una propiedad de navegación? Esperaba poder hacer lo siguiente y decirle a Dapper.Rainbow que ignore la Category cuando inserte o actualice.

public class Product {
    public int Id {get;set;}
    public string Name {get;set;}
    public ProductCategory Category {get;set;}
    public int CategoryId {get;set;} // this will be the same field name in the database
}

Este escenario es posible con NHibernate, donde puedo tener un objeto proxy de Category y asignarlo al Product y guardarlo, y la asignación funciona perfectamente. Pero me encantaría usar Dapper y es por eso que estoy explorando y quiero aprender cómo hacer cosas como esta.

Respuesta aceptada

No con Dapper.Rainbow

Esto no es posible con Dapper.Rainbow en su forma actual, pero mi solicitud de extracción en github lo hace posible.

Me sorprende que nadie sugiera usar Dapper.Contrib . Sé que pregunté si la funcionalidad está en Rainbow. Pero no esperaba que nadie notara esta afirmación (especialmente el texto en negrita):

Ahora estaba buscando algo que haría más fácil hacer inserciones y actualizaciones y encontré Dapper.Rainbow. Lo revisé y pude usarlo para obtener e insertar objetos como se describe arriba. Mi problema es que cuando el Producto tiene una propiedad de navegación, no puedo hacer una inserción en ese campo .

... y sugerir una alternativa, una solución que ya está en la biblioteca Dapper. Creo que debería haber sido más claro con mi pregunta y explícitamente pregunté si existe una solución en alguna parte de la biblioteca completa de Dapper que está en github . Entonces, después de investigar más en la biblioteca, descubrí que hay un apoyo para mi problema.

El camino a Dapper.Contrib

Todo está funcionando bien con mi proyecto y Rainbow hasta que necesité más. Tengo algunas tablas que tienen muchos campos. Si solo le doy mi objeto a Rainbow, se actualizará con todos los campos, eso no está bien. Pero eso no hace que salte del bote rápidamente y regrese a NH. Así que antes de implementar mi propio change tracking , y no quiero reinventar la rueda, especialmente si alguien ya ha hecho un buen trabajo, busqué en Google y encontré este hilo SO . Ese hilo confirmó mi conocimiento de que Rainbow no admite el seguimiento de cambios, pero hay otra bestia que lo hace y se llama Dapper.Contrib . Y entonces comencé a experimentar con eso.

Y así nos encontramos de nuevo

La categoría miembro del tipo ProductCategory no se puede usar como un valor de parámetro

Tengo el mismo problema que tuve con Rainbow . Contrib no es compatible con la propiedad de navegación? Estoy empezando a sentir que estoy perdiendo el tiempo con Dapper , y el rendimiento que ofrece, que estoy buscando mucho después, solo será una ilusión. Hasta...

The WriteAttribute, vino al rescate ...

Esta clase reside en el archivo SqlMapperExtensions.cs que se incluye en el proyecto Dapper.Contrib . No encontré ninguna documentación en esta clase ni tiene ningún comentario que pueda hacerla encontrar fácilmente y gritarme y decir " hey I'm the one you're looking for . Me tropecé con esto cuando aparté Rainbow como describí anteriormente.

El uso de esta clase es el mismo que hice con IgnorePropertyAttribute , es un atributo con el que puedes decorar la propiedad de tu clase. Debería decorar, con este atributo, cualquier propiedad que no desee incluir en el sql que crea Dapper . Entonces, en mi ejemplo, para que yo le diga a Dapper que excluya el campo Category , necesito hacer esto:

public class Product {
    public int Id {get;set;}

    public string Name {get;set;}

    [Write(false)] // tell Dapper to exclude this field from the sql
    public ProductCategory Category {get;set;}

    public int CategoryId {get;set;}
}

Estoy casi allí

Recuerde que la razón por la que voy a Contrib se debe a la funcionalidad de change tracking . Este hilo SO , el mismo enlace que di más arriba, establece que para que el seguimiento de cambios se Contrib , debes tener una interfaz para tu clase y usarla con Contrib . Entonces, para mi clase de ejemplo, necesito tener:

public interface IProduct {
    int Id {get;set;}
    string Name {get;set;}
    ProductCategory Category {get;set;}
    int Category {get;set;}
}

// and implement it on my Product class
public class Product : IProduct {
    public int Id {get;set;}

    public string Name {get;set;}

    [Write(false)]
    public ProductCategory Category {get;set;}

    int Category {get;set;}
}

Pensé que era eso, ¡casi! Es posible que se pregunte por qué tendría que definir Category en mi interfaz si a Dapper no le importa en absoluto. De hecho, eso solo causaría un problema, un problema que luego resolvería.

En mi caso particular, hay ocasiones en que necesito trabajar en el campo Category mientras mantengo el seguimiento de cambios para el objeto Product . Para mantener la capacidad de seguimiento, la llamada a get debe alimentarse con un tipo de interfaz como este:

var product = connection.Get<IProduct>(id);

y con esa llamada no podría acceder al campo Category si no lo definiré en mi interfaz. Pero si lo defino en mi interfaz, entonces obtendría el error familiar

El miembro {miembro} de tipo {tipo} no se puede usar como valor de parámetro.

¿Enserio de nuevo? Haga esta parada por favor.

El veredicto

No hay de qué preocuparse ya que este es fácil de resolver decorando el miembro de la interfaz de la misma forma que lo hicimos para la clase. Entonces la configuración final para hacer que todo funcione debe ser:

public interface IProduct {
    // I will not discuss here what this attribute does
    // as this is documented already in the github source.
    // Just take note that this is needed,
    // both here and in the implementing class.
    [Key]
    int Id {get;set;}

    string Name {get;set;}

    [Write(false)]
    ProductCategory Category {get;set;}

    int Category {get;set;}
}

// and implement it on my Product class
public class Product : IProduct {
    [Key]        
    public int Id {get;set;}

    public string Name {get;set;}

    [Write(false)]
    public ProductCategory Category {get;set;}

    int Category {get;set;}
}

Puede utilizar este enfoque si prefiere trabajar con Contrib que tiene la capacidad de change tracking . Si desea trabajar con Rainbow y tiene problemas con la propiedad de navegación tal como yo lo hice, entonces puede jugar con mi solicitud de extracción . Funciona de la misma manera que WriteAttribute solo que funciona con Rainbow .

Si no eres fanático de decorar tus clases con atributos, entonces los proyectos de extensión no son para ti. Sé que hay otro proyecto de extensión que te permitirá hacer algún tipo de configuración de tipo fluido, pero eso no acompaña (no se incluye como parte central de) la biblioteca de Dapper que está en github. Mi preferencia, que es trabajar solo con la biblioteca principal, me llevó a investigar toda la biblioteca y ver si las cosas ya están allí o si puede mejorarse para satisfacer mis necesidades. Y eso es lo que hice y explicó aquí, tanto para Rainbow como para Contrib .

Espero que esta contribución, la clase muy simple que agregué, los consejos de configuración que mostré y el escenario que me guía, ayude a alguien en el futuro que desee utilizar Dapper, y que tenga una configuración similar a la que tengo. Además, esta respuesta educará a los desarrolladores más de lo que Dapper puede y no puede hacer. Esta gran herramienta llamada Dapper merece una mejor wiki y espero que esta respuesta / artículo aquí en SO ayude incluso de una pequeña manera.


** Y si lo que escribí aquí ya está escrito en alguna parte, que no he encontrado en las dos semanas que he estado esperando una respuesta, entonces me alegrará que alguien me vincule. Han pasado dos semanas y las 29 personas que examinaron mi pregunta no han sugerido ningún vínculo o solución, así que asumí que la información que compartí aquí es nueva para Dapper * :)

NOTA: modifiqué el título de mi pregunta para que otros puedan ver esta posible solución a su problema. El nuevo título se basa en el nuevo conocimiento que obtuve sobre Dapper.



Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow
¿Es esto KB legal? Sí, aprende por qué
Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow
¿Es esto KB legal? Sí, aprende por qué