Gere e compile o nome para indexar a tradução/mapeamento para uma reutilização mais rápida

StackOverflow https://stackoverflow.com//questions/11665503

  •  11-12-2019
  •  | 
  •  

Pergunta

Suponha que eu obtenha dados de um serviço (que não posso 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; }
}

e na camada intermediária eu gostaria de mapear/traduzir isso para um IEnumerable<Entity> onde os nomes das colunas em Data talvez representados como propriedades em meu Entity aula.Eu disse poderia porque posso não precisar de todos os dados retornados pelo serviço, mas apenas de alguns deles.

Transformação

Esta é uma abstração de um algoritmo que faria a tradução:

  1. criar um IDictionary<string, int> de ColumnNames para que eu possa mapear facilmente nomes de colunas individuais para índices de array em linhas individuais.
  2. usar a reflexão para examinar meu Entity nomes de propriedades para que eu possa combiná-los com nomes de colunas
  3. iterar através Data.Rows e criar o meu Entity objetos e preencher propriedades de acordo com o mapeamento feito em #1.Provavelmente usando reflexão e SetValue nas propriedades para defini-las.

Otimização

É claro que o algoritmo superior funcionaria, mas acho que, por usar reflexão, ele deveria fazer algum armazenamento em cache e possivelmente alguma compilação em tempo real, o que poderia acelerar consideravelmente as coisas.

Quando as etapas 1 e 2 forem concluídas, poderíamos gerar um método que pega um array de strings e instancia minhas entidades usando índices diretamente e compila-o e armazená-lo em cache para reutilização futura.

Normalmente recebo uma página de resultados, portanto, as solicitações subsequentes reutilizariam o mesmo método compilado.

Fato adicional

Isso não é obrigatório para a pergunta (e respostas), mas também criei dois atributos que ajudam no mapeamento de coluna para propriedade quando estes não correspondem nos nomes.Eu criei o mais óbvio MapNameAttribute (que usa uma string e, opcionalmente, também habilita a diferenciação de maiúsculas e minúsculas) e IgnoreMappingAttribute para propriedades no meu Entity isso não deve ser mapeado para nenhum dado.Mas esses atributos são lidos na etapa 2 do algoritmo superior para que os nomes das propriedades sejam coletados e renomeados de acordo com esses metadados declarativos para que correspondam aos nomes das colunas.

Pergunta

Qual é a melhor e mais fácil maneira de gerar e compilar tal método?Expressões lambda? CSharpCodeProvider aula?

Você talvez tenha um exemplo de código gerado e compilado que faça algo semelhante?Acho que os mapeamentos são um cenário bastante comum.

Observação:Enquanto isso, examinarei o PetaPoco (e talvez também o Massive) porque ambos fazem compilação e armazenamento em cache dinamicamente exatamente para fins de mapeamento.

Foi útil?

Solução

Sugestão: obter FastMember do NuGet

Depois é só usar:

var accessor = TypeAccessor.Create(typeof(Entity));

Então, apenas no seu loop, quando você encontrar o memberName e newValue para a iteração atual:

accessor[obj, memberName] = newValue;

Isso foi projetado para fazer o que você está pedindo;internamente, ele mantém um conjunto de tipos como já visto antes.Quando um novo tipo é visto, ele cria uma nova subclasse de TypeAccessor em tempo real (através TypeBuilder) e armazena-o em cache.Cada único TypeAccessor está ciente das propriedades desse tipo e basicamente apenas age como um:

switch(memberName) {
    case "Foo": obj.Foo = (int)newValue;
    case "Bar": obj.Bar = (string)newValue;
    // etc
}

Como isso é armazenado em cache, você paga apenas qualquer custo (e não realmente um grande custo) na primeira vez que vê seu tipo;no resto do tempo, é grátis.Porque ele usa ILGenerator diretamente, também evita qualquer abstração desnecessária, por exemplo, via Expression ou CodeDom, então é o mais rápido possível.

(Devo também esclarecer que para dynamic tipos, ou seja,tipos que implementam IDynamicMetaObjectProvider, ele pode usar uma única instância para suportar todos os objetos).


Adicional:

O que você poderia fazer é:pegue o existente FastMember código e edite-o para processar MapNameAttribute e IgnoreMappingAttribute durante WriteGetter e WriteSetter;então todo o vodu acontece no seu dados nomes, em vez de membro nomes.

Isso significaria mudar as linhas:

il.Emit(OpCodes.Ldstr, prop.Name);

e

il.Emit(OpCodes.Ldstr, field.Name);

em ambos WriteGetter e WriteSetter, e fazendo um continue no início do foreach loops se deve ser ignorado.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top