Genere y compile nombres para indexar traducción/mapeo para una reutilización más rápida
-
11-12-2019 - |
Pregunta
Supongamos que obtengo datos de un servicio (que no puedo controlar) como:
public class Data
{
// an array of column names
public string[] ColumnNames { get; set; }
// an array of rows that contain arrays of strings as column values
public string[][] Rows { get; get; }
}
y en el nivel medio me gustaría mapear/traducir esto a un IEnumerable<Entity>
donde los nombres de las columnas en Data
tal vez representados como propiedades en mi Entity
clase.Yo dije puede porque es posible que no necesite todos los datos devueltos por el servicio sino solo algunos de ellos.
Transformación
Esta es una abstracción de un algoritmo que haría la traducción:
- crear un
IDictionary<string, int>
deColumnNames
por lo que puedo asignar fácilmente nombres de columnas individuales a índices de matriz en filas individuales. - Usar la reflexión para examinar mi
Entity
nombres de propiedades para poder relacionarlos con nombres de columnas - iterar a través de
Data.Rows
y crear miEntity
objetos y completar propiedades de acuerdo con el mapeo realizado en el n.° 1.Probablemente usando reflexión ySetValue
en las propiedades para configurarlas.
Mejoramiento
El algoritmo superior, por supuesto, funcionaría, pero creo que debido a que utiliza reflexión debería realizar algo de almacenamiento en caché y posiblemente algo de compilación sobre la marcha, lo que podría acelerar las cosas considerablemente.
Cuando finalicen los pasos 1 y 2, podríamos generar un método que tome una matriz de cadenas y cree instancias de mis entidades usando índices directamente y compilarlo. y almacenarlo en caché para su futura reutilización.
Normalmente obtengo una página de resultados, por lo que las solicitudes posteriores reutilizarían el mismo método compilado.
Dato adicional
Esto no es imperativo para la pregunta (y las respuestas), pero también creé dos atributos que ayudan con la asignación de columna a propiedad cuando no coinciden en los nombres.Creé lo más obvio MapNameAttribute
(que toma una cadena y opcionalmente también habilita la distinción entre mayúsculas y minúsculas) y IgnoreMappingAttribute
para propiedades en mi Entity
eso no debería asignarse a ningún dato.Pero estos atributos se leen en el paso 2 del algoritmo superior, por lo que los nombres de las propiedades se recopilan y se les cambia el nombre de acuerdo con estos metadatos declarativos para que coincidan con los nombres de las columnas.
Pregunta
¿Cuál es la mejor y más sencilla forma de generar y compilar dicho método?¿Expresiones lambda? CSharpCodeProvider
¿clase?
¿Quizás tenga un ejemplo de código generado y compilado que haga algo similar?Supongo que los mapeos son un escenario bastante común.
Nota:Mientras tanto, examinaré PetaPoco (y tal vez también Massive) porque, que yo sepa, ambos compilan y almacenan en caché sobre la marcha exactamente con fines de mapeo.
Solución
Sugerencia: obtener FastMember de NuGet
Entonces solo usa:
var accessor = TypeAccessor.Create(typeof(Entity));
Luego, justo en tu bucle, cuando hayas encontrado el memberName
y newValue
para la iteración actual:
accessor[obj, memberName] = newValue;
Esto está diseñado para hacer lo que usted pide;Internamente, mantiene un conjunto de tipos si se ha visto antes.Cuando se ve un nuevo tipo, se crea una nueva subclase de TypeAccessor
sobre la marcha (a través de TypeBuilder
) y lo almacena en caché.Cada uno único TypeAccessor
es consciente de las propiedades para ese tipo, y básicamente simplemente actúa como un:
switch(memberName) {
case "Foo": obj.Foo = (int)newValue;
case "Bar": obj.Bar = (string)newValue;
// etc
}
Debido a que esto está almacenado en caché, usted sólo paga cualquier costo (y no realmente un grande costo) la primera vez que ve tu tipo;el resto del tiempo es gratis.porque usa ILGenerator
directamente, también evita cualquier abstracción innecesaria, por ejemplo mediante Expression
o CodeDom, por lo que es lo más rápido posible.
(También debo aclarar que para dynamic
tipos, es decirtipos que implementan IDynamicMetaObjectProvider
, puede usar una única instancia para admitir cada objeto).
Adicional:
Lo que tu podría hacer es:tomar lo existente FastMember
código y edítelo para procesarlo. MapNameAttribute
y IgnoreMappingAttribute
durante WriteGetter
y WriteSetter
;Entonces todo el vudú sucede en tu datos nombres, en lugar de miembro nombres.
Esto significaría cambiar las líneas:
il.Emit(OpCodes.Ldstr, prop.Name);
y
il.Emit(OpCodes.Ldstr, field.Name);
en ambos WriteGetter
y WriteSetter
, y haciendo un continue
al inicio del foreach
bucles si se debe ignorar.