Melhorar performance no AutoMapper

204 views
Skip to first unread message

Tiago Salgado

unread,
Sep 17, 2013, 12:11:33 PM9/17/13
to netp...@googlegroups.com
Boas,
Estou a usar o AutoMapper num serviço WCF mas isto está com um grave problema a nivel de performance.

Ou seja, a tentar fazer o map de um IEnumerable<Order> para IEnumerable<DataContracts.Order>, numa lista de 38 registos (espero bem mais no futuro), o tempo que demora é absurdo.

Receio que seja pelo objecto Order ter propriedades que referenciam outros objectos, ou seja, o map delas também terá que ser feito no momento, o que penso eu que esteja a causar esta demora toda.

Existe algo "milagroso" que se consiga fazer neste tipo de situações?

Obrigado

Cumprimentos,
Tiago Salgado

Cristovão Morgado

unread,
Sep 17, 2013, 12:19:31 PM9/17/13
to netp...@googlegroups.com
É nesses momentos que viro-me para templates T4 .... para criar automáticamente código ... runtime mapping pode matar uma app (reflection também)

Não tens por acaso possibilidade de fazer caching disso? 


2013/9/17 Tiago Salgado <tiagos...@gmail.com>

--
--
Você recebeu esta mensagem por fazer parte da lista de discussão oficial da Comunidade NetPonto (http://netponto.org).
 
---
Recebeu esta mensagem porque está inscrito no grupo "NetPonto.org - Lista de Discussao Oficial" dos Grupos do Google.
 
Para anular a subscrição deste grupo e parar de receber emails deste grupo, envie um email para netponto+u...@googlegroups.com.
Para mais opções, consulte https://groups.google.com/groups/opt_out.



--
Cristovao Morgado
@TheSaintr

Alexandre Simões

unread,
Sep 17, 2013, 1:28:12 PM9/17/13
to netp...@googlegroups.com
Pah, IMHO, isso é uma má ideia mesmo que fosse rápido seria uma questao de tempo ate ficar lento...

Fazer esse mapeamente a um objecto... vah... agora a uma lista, ainda por cima tu esperas que sejam mtos mais items? No way!

Para te dar uma soluçao tens de nos dar mais detalhes sobre o teu cenario.

Cheers!
Alex

Alexandre Simoes
m. +41 789 499 957


2013/9/17 Cristovão Morgado <cristova...@gmail.com>

Bruno Lopes

unread,
Sep 17, 2013, 1:35:43 PM9/17/13
to netp...@googlegroups.com
É uma má ideia excepto quando é uma boa ideia. Dependendo do caso, todo o custo implicito de reflecção ou mapeamento automático pode ser abafado por  outros custos: base de dados, serialização, geração de json, comunicação, ou simplesmente ser irrelevante porque o sistema não tem carga suficiente. E nesses casos o tempo de cpu é muito mais barato que o tempo de programador ;)

Tiago, consegues fazer um profiling rápido ao mapeamento? É uma arvore assim tão grande de objetos? Que volume estás a pensar mapear de uma só vez?
Apenas pergunto porque pode estar a ser uma outra coisa qq que está a atrapalhar.


2013/9/17 Alexandre Simões <ma...@alexcode.com>

Alexandre Simões

unread,
Sep 18, 2013, 4:24:20 AM9/18/13
to netp...@googlegroups.com
Sinceramente, qqr bom codigo é mais barato que qqr martelada.

Pode ser mais caro no imediato mas basta que o programador tenha de la voltar 2ª vez pra corrigir a martelada ou alterar o codigo pq "Tá Lento!!" pronto... ja se a poupança.

Pra mim, ente tipo de mapeamento automatico é mesmo só se nao houver outra hipotese... daí pedir mais detalhes da implementaçao.

Abrax

Alexandre Simoes
m. +41 789 499 957


2013/9/17 Bruno Lopes <bruno...@gmail.com>

Tiago Salgado

unread,
Sep 18, 2013, 5:10:40 AM9/18/13
to netp...@googlegroups.com
Boas,

@Cristovão, nunca usei templates T4 por isso nem sequer explorei essa opção ainda. Posso fazer caching, mas neste caso acho que não me resolveria o problema da lentidão.

@Alexandre, eu também não queria muito faze-lo, mas se não for com AutoMapper terei que fazer o mapeamente manualmente para mandar pro cliente atraves do WCF. Ou que alternativas tenho neste caso?

@Bruno, é um pouco grande, algo do tipo:

    [DataContract]
    public partial class Order
    {
        [DataMember]
        public int ID { get; set; }

        [DataMember]
        public int KPClientID { get; set; }
...

        [DataMember]
        public RefKP_Language RefKP_Language { get; set; }

        [DataMember]
        public RefKP_Client RefKP_Client { get; set; }

        [DataMember]
        public RefKP_Status RefKP_Status { get; set; }

        [DataMember]
        public PortalUser PortalUser { get; set; }

        [DataMember]
        public IList<OrderFile> OrderFiles { get; set; }

        [DataMember]
        public IList<OrderService> OrderServices { get; set; }
    }


Dentro depois de cada objecto (OrderService por exemplo) tem mais outros objectos que precisam de ser mapeados igualmente.

O volume a mapear de cada vez é um pouco relativo, dependerá de cliente para cliente.

Neste momento o cenário é o seguinte:

- Portal em DotNetNuke
- WCF
- DataLayer (telerik open access)

Ou seja, tudo que são pedidos à BD pelo Dotnetnuke é feito ao serviço WCF, daí estar a fazer o mapeamento para retornar para o portal novamente.

Obrigado pelas respostas



2013/9/17 Bruno Lopes <bruno...@gmail.com>

Sérgio Agostinho

unread,
Sep 18, 2013, 5:40:35 AM9/18/13
to netp...@googlegroups.com
Eu diria que as listas são um bom candidato a bottleneck, se estiverem a ser regeradas no mapping.
Já consideraste implementar o teu próprio mapper? Além das listas, é possível (especulação minha) que o automapper use reflexão, o que não torna o processo mais ligeiro.
Sérgio Agostinho

Tiago Salgado

unread,
Sep 18, 2013, 5:51:42 AM9/18/13
to netp...@googlegroups.com
Sim, já considerei. Estava a tentar usar o AutoMapper no sentido de ver se me pouparia algum tempo, e evitava assim a implementação pois ainda irá levar algum tempo...e o tempo escassa :)


2013/9/18 Sérgio Agostinho <sergiofa...@gmail.com>

Bruno Lopes

unread,
Sep 18, 2013, 7:02:43 AM9/18/13
to netp...@googlegroups.com
Curiosidade, como é que despistaste que o problema é do automapper? Olhando para esse objeto, e depois do teu comentário de que as classes relacionads têm ainda outros objectos, a minha primeira ideia seria verificar se não estou a carregar os dados da base de dados um a um (o que explicaria estar tudo lento, mesmo com poucos objetos).

O open access tem algum profiler que te permita ver o numero de chamadas à base de dados? Outra hipotese é arrancar o sql profiler e ver os pedidos.

Fazer o mapeamento disso à pata a mim doia-me a alma, sim, e a probabilidade de fazer asneira e trocar um par de campos ou me esquecer de adicionar um mais tarde seria grande...


2013/9/18 Tiago Salgado <tiagos...@gmail.com>

Tiago Salgado

unread,
Sep 18, 2013, 7:12:00 AM9/18/13
to netp...@googlegroups.com
Fazer o mapeamento à pata é também algo que queria evitar por essas mesmas razões, e o tempo que levaria a ter tudo conforme... devido à extensiva lista de objectos (e associações).

Espero não estar a dizer alguma asneira, mas como estou no serviço WCF a retornar a lista de objectos, quando faço o mapping essa lista já está em memória, por isso a chamada à BD não é feita durante o mapping mas sim antes.

O código é algo como isto:
var orders = db.Orders.Where(p => p.CreatedByUserID == userId).ToList();
var _orders = Mapper.Map<IEnumerable<DataAccessModel.Order>IEnumerable<PortalEmailService.Contracts.DataContracts.Order>>(orders);


2013/9/18 Bruno Lopes <bruno...@gmail.com>

Paulo Correia

unread,
Sep 18, 2013, 7:23:04 AM9/18/13
to netp...@googlegroups.com
Encontrei no site da Telerik http://www.telerik.com/products/orm/features/runtime-features.aspx

Lazy Data Loading

OpenAccess uses lazy loading on the client where your application objects are filled with the data upon access. Not all fields will be filled immediately, so a collection or picture fields can be deferred in their population. This has the advantage of saving memory and network bandwidth while preserving easy access: when the field's content is needed, the data is fetched transparently from the database server. Of course, you can decide which fields are to be fetched when, and OpenAccess provides sensible defaults.



E pelos foruns, não dá para desligar


http://www.telerik.com/community/forums/orm/development/lazy-load.aspx



Aposto que o teu bottleneck é nisso.... Mas para tirares a teima, liga o sql profiler e vês que apesar de fazeres a query, só te vai buscar os dados quando fazes o Mapper.Map.



2013/9/18 Paulo Correia <paul...@gmail.com>
Tens a certeza que não usas Lazy Loading?


2013/9/18 Tiago Salgado <tiagos...@gmail.com>

Bruno Lopes

unread,
Sep 18, 2013, 7:29:25 AM9/18/13
to netp...@googlegroups.com
Posso estar a atirar ao lado, mas cheira-me que tens um select n+1 ai pelo meio, e primeiro que tudo garantiria ou é isso (e resolves com fetch plans e strategies, no caso do openaccess) ou que tenho a certeza que não é isso. Segundo este link as colecções são mapeadas com lazyloading (o que faz sentido), pelo que acho que é mesmo isso que te está a acontecer.

Isto quer dizer que para cada order ele vai buscar os order files e depois os order services. Com 30 orders, são 60 hits separados à bd para essas listas. E isto nem considerando os hits para objectos relacionados com order files e services (se cada um tiver 3 ou 4 objetos relacionados, multiplica o 60 por esse numero). Basta a bd demorar 10 ms a responder e tens 600ms em roundtrips à BD. Mete a BD noutra máquina, e esses 10ms passaram facilmente a 100 ou 300 ms e poof 6 segundos para responder a um pedido.

Resolves esse problema carregando logo os dados todos que precisas. Com NHibernate eu criaria um query para carregar as orders num future e outros queries para carregar os objectos relacionados. Isto depende mto da forma das classes, também podia acontecer que conseguia carregar tudo só com um query, e tendo em atenção duplicados causados por joins. Outra hipotese seria colocar um batch size no lazy loading das classes, que apenas transforma o select n+1 num select n/(batch size) + 1, o que pode só por si dar um boost grande com alterações minímas (porque em vez de 11 hits à bd são só 2, um para os objectos principais, outro para carregar as listas todas ao mesmo tempo). Mas não sei se o openaccess tem essa capacidade, e no teu caso não sei se resolveria muito porque os objectos parecem suficientemente complexos.

Hope that helps,
Bruno


2013/9/18 Tiago Salgado <tiagos...@gmail.com>

Paulo Correia

unread,
Sep 18, 2013, 7:19:11 AM9/18/13
to netp...@googlegroups.com
Tens a certeza que não usas Lazy Loading?


2013/9/18 Tiago Salgado <tiagos...@gmail.com>
Fazer o mapeamento à pata é também algo que queria evitar por essas mesmas razões, e o tempo que levaria a ter tudo conforme... devido à extensiva lista de objectos (e associações).

Tiago Salgado

unread,
Sep 18, 2013, 8:50:40 AM9/18/13
to netp...@googlegroups.com
Sim, realmente têm razão... my bad.
Então basicamente, a unica coisa que fica em memória é o objecto com as propriedades (int, string, bool, etc), mas depois referências ao outros tipos, o que faz então as chamadas à BD para cada um quando tento mapea-los (ou usa-los).

Mesmo com fetch plans isto vai ser um bico d'obra do caneco, e o grande problema é o tempo que irá levar em definir tudo em condições... coisa que está a escassar neste momento.

Podes-me explicar a parte de definir um "batch size no lazy loading"?


2013/9/18 Bruno Lopes <bruno...@gmail.com>

Alexandre Simoes

unread,
Sep 18, 2013, 9:03:54 AM9/18/13
to netp...@googlegroups.com
Se bem percebi tas a usar um ORM pra fazer a query a BD e te converter os dados em objectos desse ORM...A seguir, e sem precisares desses objectos pra nada, tens de os converter em outros tipos que sao os que queres retornar pelo serviço WCF... É isto?

Porque é que nao te borrifas no ORM e usas o belo do ADO, que é do mais rapido que podes ter, e com o DataReader hidratas logo directamente os objectos finais em vez de andares a saltar de classe em classe?

Se tens varias listas, fazes uma SP com varios resultsets e de uma assentada populas tudo.

Pragmatismo...

Cheers!



2013/9/18 Bruno Lopes <bruno...@gmail.com>

Bruno Lopes

unread,
Sep 18, 2013, 11:34:52 AM9/18/13
to netp...@googlegroups.com
Eu acho que consegues uma solução mais ou menos boa com um fetch plan do tipo:
Telerik.OpenAccess.FetchOptimization.FetchStrategy fetchStrategy = new Telerik.OpenAccess.FetchOptimization.FetchStrategy();
fetchStrategy.LoadWith<Order>(c => c.PortalUser );
fetchStrategy.LoadWith<Order>(c => c.OrderFiles );
fetchStrategy.LoadWith<Order>(c => c.OrderServices );
fetchStrategy.LoadWith<OrderService>(o => o.<outra lista a carregar no OrderService>);
Só isso talvez te corte o tempo por metade, e pode valer a pena experimentares ( e acho que é muito mais rapido de experimentar do que mudar para um dapper, ou fazer o handroll dos queries com ado e data readers). Se te trouxer a performance para niveis aceitaveis (que eu acho mesmo que é possível), talvez tenha o melhor ROI.


2013/9/18 Tiago Salgado <tiagos...@gmail.com>

Bruno Lopes

unread,
Sep 18, 2013, 11:35:25 AM9/18/13
to netp...@googlegroups.com
E desculpa, a parte de "batch size no lazy loading" é uma possíbilidade em NHibernate, não sei se openaccess tem essa hipotese :S


2013/9/18 Bruno Lopes <bruno...@gmail.com>

Paulo Correia

unread,
Sep 18, 2013, 11:03:45 AM9/18/13
to netp...@googlegroups.com
Boas,

Seguindo a dica do Alexandre, eu tive que fazer algo, mas não era uma estrutura complexa como tu tens. Para não ter que fazer o carregamento do DataReader para a classe, usei o Dapper https://code.google.com/p/dapper-dot-net/ (seguindo as indicações do pessoal da mailing list). E o acesso foi bem rápido.

Talvez uma solução de compromisso, seria tu usares o Dapper (ou outra biblioteca de mini-ORM) para carregares o teu objecto. No entanto, desconheço de qual a melhor forma para o fazer com uma estrutura assim tão complicada. 

A meu ver, tens duas alternativas. 

A do Alexandre, em que recebes vários resultsets, e tens que andar a bater código para carregar a classe a partir dos resultsets no DataReader
Ou usas um mini-ORM, em que tu terias que bater algum código à mesma, mas preenches as classes na ordem que tiver lógica para ti (atenção que esta alternativa vai ter sempre o overhead de teres várias chamadas à BD em vez de uma chamada apenas como a do Alexandre).

No fundo isto é um dos típicos paradigmas da programação. Queres uma cena fácil/ágil/rápida de programar que tenha uma boa performance. E a melhor performance só atinges se perderes algum tempo a fazer código, em vez de confiar em ORM ou outra API com um propósito semelhante.

Mas analisando a situação de outro ponto de vista. Tu tens mesmo que devolver uma estrutura tão complexa como essa? A aplicação cliente que vai consumir esse objecto vai usar isso tudo SEMPRE, todas as propriedades, collections, etc? Será que não podes partir o objecto em várias chamadas a diferentes métodos WCF, de acordo com a necessidade da aplicação cliente?


Abs
Paulo Correia


2013/9/18 Alexandre Simoes <mail.a...@gmail.com>

Tiago Salgado

unread,
Sep 19, 2013, 4:11:46 AM9/19/13
to netp...@googlegroups.com
Boas,
Eu não preciso de trazer toda a estrutura em todos os casos, e aí poderei decidir o que incluir (LoadWith<T>) em cada caso.
Vou explorar então melhor a solução do fetch plan, e ver no que se traduz a nivel de performance.

Obrigado a todos pela ajuda :)

Um abraço 


2013/9/18 Bruno Lopes <bruno...@gmail.com>

Alexandre Simões

unread,
Sep 19, 2013, 7:07:40 AM9/19/13
to netp...@googlegroups.com
Eu continuo na minha... com o tempo que tas a perder a tentar isto e aquilo,  sem ter a certeza se realmente resulta, ja tinhas posto as maos na massa e feito os mapeamentos que queres á mao...
Eu sei que é chato, nao é geek, nem é coisa que faça alguem se sentir orgulhoso mas... funciona sem espinhas.

Abrax

Alexandre Simoes
m. +41 789 499 957


2013/9/19 Tiago Salgado <tiagos...@gmail.com>

Luis Paulino

unread,
Sep 19, 2013, 11:23:47 AM9/19/13
to netp...@googlegroups.com
Eu discordo do Alexandre,

Acho que o Tiago fez bem tentar saber se a comunidade conhecia mais e melhores alternativas ao seu problema.

É também através destas discussões que se recicla conhecimento(s).

Por outro lado, o mapeamento "à pata" pode não ser uma solução viável a longo prazo nos seguintes casos:
  1. Elevado nº de campos a mapear.
  2. Alterações constantes e substanciais num dos dois modelos.
Por alguma razão surgiram esta ferramentas...

Cristovão Morgado

unread,
Sep 19, 2013, 12:06:49 PM9/19/13
to netp...@googlegroups.com
Lá está em vez de aprender mais uma coisa que tras problemas ... OLHEM para T4 ...

Software que cria software isso é ir em frente!


2013/9/19 Luis Paulino <kingstons...@gmail.com>



--
Cristovao Morgado
@TheSaintr

Reply all
Reply to author
Forward
0 new messages