Usuário com melhor resposta
OutOfMemoryException ao rodar o SaveChanges().

Pergunta
-
Olá pessoal,
Criei uma aplicação winforms, em 3 camadas (cada camada é um projeto diferente) e utilizo o Entity Framework para cuidar da persistência.
Tudo estava indo relativamente bem, o sistema funciona com a massa de dados que tenho atualmente, mas eu topei com um probleminha.
Quando eu tentei fazer um merge de dados entre a minha base do sistema antigo para o novo (eu fiz uma remodelagem completa nas tabelas do BD e preciso desses dados para histórico) eu tive um OutOfMemoryException no momento em que chamo o Método SaveChanges().
Eu criei um projeto novo para fazer o merge e importei minha camada DAL e BLL do sistema, para usar a mesam modelagem e o entity framework.
O método que deu problema é mais ou menos assim.
List<DespesaAntiga> despesasAntigas = new Despesas(); despesasAntigas = BusinessFactory.oDespesaAntigaBLL.SelectAll(); foreach(DespesaAntiga despesaAntiga in despesasAntigas){ Despesa despesa = new Despesa(); despesa.Valor = despesaAntiga.Valor; ... BusinessFactory.oDespesaBLL.Insert(despesa); } BusinessFactory.oDespesaBLL.Save(); //Aqui ocorre o OutOfMemoryException MessageBox.Show("Processo Finalizado com Sucesso.");
A quantidade de dados na tabela de Despesas antiga é bem grande, visto que o sistema roda a seis anos e todo mês temos mais de 10.000 registros de despesas.
Quando fiz as importações de arquivos mensais de despesas a serem cobradas, que vem de um sistema Cobol no padrão do Governo, com um médoto mais ou menos como o que tem a cima, ele funciona tudo bem, faz todas as operações e no fim salva tudo tranquilamente, mas para um massa maior ele simplesmente usa todos os 4 GB da minha máquina.
Fiz o método dessa forma, pois queria garantir que os dados só seriam salvos caso todo o processo rode com sucesso.
O método roda em uma Thread separada da principal, para o usuário conseguir enxergar a barra de status e o progresso do processo.
Minha dúvida é, como eu poderia modificar meu método de forma que eu possa garantir que os dados só serão salvos se não tiver erro em algum registro e eu não tenha um OutOfMemoryException?
- Editado Silvio Caetano terça-feira, 22 de maio de 2012 18:02
Respostas
-
Olá Silvio,
Seu código da camada de acesso a dados deve ficar assim:
public class Operacoes<T> : IOperacoes<T> where T : EntityObject { // Este é seu evento public event EventHandler ItemAdded; public bool Insert(T[] entity) { using (System.Data.Common.DbTransaction transaction = PSAInstanceDB.context.Connection.BeginTransaction()) { try {
int count = 0; foreach (var item in entity) { PSAInstanceDB.context.AddObject(item, entity); // Este ponto é onde vc invoca seu evento if (ItemAdded != null) this.ItemAdded(this, new EventArgs());
count++;
if (count >= 10)
{
PSAInstanceDB.context.SaveChanges();
count = 0;} } PSAInstanceDB.context.SaveChanges(); transaction.Commit(); return true; } catch (Exception e) { transaction.Rollback(); if (e.InnerException == null) m = new Messages(e.Message); else m = new Messages(e.InnerException.Message); return false; } } } }
E sua camada de Controller provavelmente ficará assim:
public void SeuMetodoInclusao() { List<DespesaAntiga> despesasAntigas = new Despesas(); despesasAntigas = BusinessFactory.oDespesaAntigaBLL.SelectAll(); List<Despesa> novositens = new List<Despesa>(); foreach(DespesaAntiga despesaAntiga in despesasAntigas){ Despesa despesa = new Despesa(); despesa.Valor = despesaAntiga.Valor; novositens.Add(despesa); BusinessFactory.oDespesaBL.ItemAdded += operacoes_ItemAdded; } BusinessFactory.oDespesaBLL.Insert(novositens.ToArray()); MessageBox.Show("Processo Finalizado com Sucesso."); } public void operacoes_ItemAdded(object sender, EventArgs e) { // Um item foi executado }
[]s!
Fernando Henrique Inocêncio Borba Ferreira
while(alive){ this.WriteCode(); }
Blog: http://ferhenriquef.com/
Twitter: @ferhenrique- Marcado como Resposta Fernando Henrique Inocêncio Borba FerreiraMicrosoft employee, Moderator quinta-feira, 28 de junho de 2012 17:31
Todas as Respostas
-
Olá Silvio,
Vc chegou a testar algo assim?
List<DespesaAntiga> despesasAntigas = new Despesas(); despesasAntigas = BusinessFactory.oDespesaAntigaBLL.SelectAll(); using (var trans = new TransactionScope()) { int count = 0; foreach(DespesaAntiga despesaAntiga in despesasAntigas){ Despesa despesa = new Despesa(); despesa.Valor = despesaAntiga.Valor; BusinessFactory.oDespesaBLL.Insert(despesa); if (count == 10){ BusinessFactory.oDespesaBLL.Save(); count = 0; } count++; } } MessageBox.Show("Processo Finalizado com Sucesso.");
[]s!Fernando Henrique Inocêncio Borba Ferreira
while(alive){ this.WriteCode(); }
Blog: http://ferhenriquef.com/
Twitter: @ferhenrique -
-
O objetivo é invés de executar apenas um "SaveChanges()" executar uma série deles.
Isto limpa memória do seu DataContext, já que provavelmente ele esta acumulando muita memória na tentativa de comparar as diferenças entre os campos que foram editados com as instâncias contidas no DataContext.
O processo de inclusão segue algum processos e rotinas internas. Provavelmente, uma quantidade muito grande de registros, sendo que as instâncias possuem muitas propriedades, pode estar causando este estouro de memória.
Talvez fique um pouco lento, neste caso aumente o valor da comparação, de 10 para 100 ou 200.
Vale a pena ir testando.
Vamos ver se resolve :)
[]s!
Fernando Henrique Inocêncio Borba Ferreira
while(alive){ this.WriteCode(); }
Blog: http://ferhenriquef.com/
Twitter: @ferhenrique -
Veja esta referência.
A solução propõe uma abordagem semelhante: http://stackoverflow.com/questions/190066/what-is-the-best-way-to-use-the-savechanges-method-in-ado-net-data-services
[]s!
Fernando Henrique Inocêncio Borba Ferreira
while(alive){ this.WriteCode(); }
Blog: http://ferhenriquef.com/
Twitter: @ferhenrique -
Bem, testei o seu código e deu a seguinte exceção.
Exception: O acesso à rede do Gerenciador de Transações Distribuídas (MSDTC) foi desabilitado. Habilite o acesso à rede do DTC na configuração de segurança do MSDTC usando a ferramenta Administrativa dos Serviços de Componentes.
InnerException: O gerenciador de transações desativou seu suporte a transações remotas/de rede. (Exceção de HRESULT: 0x8004D024)
-
Olá Silvio,
Vc tem duas opções:
1 - Comentar o TransactionScope e utilizarmos outros recurso
ou
2 - Acessar esse link e habilitar essa feature no server: http://www.codeproject.com/Articles/54426/MSDTC-Service-enable-issues-when-Using-NET-Transac
[]s!
Fernando Henrique Inocêncio Borba Ferreira
while(alive){ this.WriteCode(); }
Blog: http://ferhenriquef.com/
Twitter: @ferhenrique -
-
Olá Silvio,
Veja se isto ajuda:
List<DespesaAntiga> despesasAntigas = new Despesas(); despesasAntigas = BusinessFactory.oDespesaAntigaBLL.SelectAll(); using (System.Data.Common.DbTransaction transaction= BusinessFactory.Connection.BeginTransaction()) { try { int count = 0; foreach(DespesaAntiga despesaAntiga in despesasAntigas){ Despesa despesa = new Despesa(); despesa.Valor = despesaAntiga.Valor; BusinessFactory.oDespesaBLL.Insert(despesa); if (count == 10){ BusinessFactory.oDespesaBLL.Save(); count = 0; } count++; } BusinessFactory.oDespesaBLL.Save(); transaction.Commit(); } catch(Exception) { transaction.Rollback(); } } MessageBox.Show("Processo Finalizado com Sucesso.");
Sua classe BusinessFactory terá de expor o objeto de conexão para poder criar a transação.
[]s!
Fernando Henrique Inocêncio Borba Ferreira
while(alive){ this.WriteCode(); }
Blog: http://ferhenriquef.com/
Twitter: @ferhenrique
- Editado Fernando Henrique Inocêncio Borba FerreiraMicrosoft employee, Moderator terça-feira, 22 de maio de 2012 20:05
-
Me tira uma dúvida, minha aplicação possuí uma classe de operações generica em um projeto chamado PSA.DAL:
public abstract class Operacoes<T> : IOperacoes<T> where T: EntityObject { /// <summary> /// Propriedade Read Only que recupera o nome da Tabela no Banco de Dados. /// </summary> protected abstract string TableName { get; } /// <summary> /// Propriedade Read Only que recupera o nome da Entidade no modelo edmx. /// </summary> protected abstract string EntityName { get; } /// <summary> /// Mensagem de excessão. /// </summary> public static Messages m; /*Implementação dos Métodos da IOperacoes<T>*/ public bool Insert(T entity) { try { PSAInstanceDB.context.AddObject(EntityName, entity); return true; } catch(Exception e) { if (e.InnerException == null) m = new Messages(e.Message); else m = new Messages(e.InnerException.Message); return false; } } }
Nesse projeto, também fica minha classe estática PSAInstanceDB:
public class PSAInstanceDB { public static PSAEntities context = new PSAEntities(); /// <summary> /// Define o usuário e a senha na conection string do contexto. /// </summary> /// <param name="user">usuário</param> /// <param name="password">senha</param> public static void setConnectionString(string user, string password) { context.Connection.ConnectionString = (@"metadata=res://*/PSAModel.csdl|res://*/PSAModel.ssdl|res://*/PSAModel.msl;provider=System.Data.SqlClient;provider connection string="";data source=SERVER;initial catalog=BANCO_DADOS;user id="+user+";password="+password+@";multipleactiveresultsets=True;App=EntityFramework"";"); } }
E no meu projeto BLL eu tenho a factory:
public static class BusinessFactory { private static string user; public static string User { get { return BusinessFactory.user; } set { BusinessFactory.user = value; } } private static string password; public static string Password { get { return BusinessFactory.password; } set { BusinessFactory.password = value; } } private static DespesaBLL _DespesaBLL; public static DespesaBLL oDespesaBLL { get { if(_DespesaBLL == null) _DespesaBLL = new DespesaBLL(new DespesaDAL()); return _DespesaBLL; } } }
Eu posso cirar um método na Facotory para gerenciar essa transaction?
-
Olá Silvio,
Por questões de arquitetura e uso de dependências, acredito que o melhor seja gerenciar suas transações na camada DAL.
Lá é o local correto.
[]s!
Fernando Henrique Inocêncio Borba Ferreira
while(alive){ this.WriteCode(); }
Blog: http://ferhenriquef.com/
Twitter: @ferhenrique -
-
Olá Silvio,
Conseguimos tratar isso no nível da sua classe de Operacoes.
Assim:
public bool Insert(T entity) { using (System.Data.Common.DbTransaction transaction= PSAInstanceDB.context.Connection.BeginTransaction()) { try { PSAInstanceDB.context.AddObject(EntityName, entity); transaction.Commit(); return true; } catch(Exception e) { transaction.Rollback(); if (e.InnerException == null) m = new Messages(e.Message); else m = new Messages(e.InnerException.Message); return false; } } }
[]s!Fernando Henrique Inocêncio Borba Ferreira
while(alive){ this.WriteCode(); }
Blog: http://ferhenriquef.com/
Twitter: @ferhenrique -
Entendi isso, mas o problema é que eu quero commitar minha transação ao fim do loop, por que se eu chamo meu médoto BusinessFactory.oDespesa.Save() em cada iteração eu não tenho OutOfMemoryException.
Preciso de algo assim:
//Iniciar Transaction try foreach(Classe classe in classesCollection) { //operações de insert e tudo mais } //commit except { //rollback }
Se eu chamar desse jeito que você sugeriu, ele vai incluir o registro a cada insert no banco, ou não?
Valeu pela força que você tá me dando.
-
Olá Silvio,
E se vc criasse uma assinatura para o método Insert da classe Operacao, que recebesse um Array de T, e que fizésse a lógica que discutimos anteriormente de inserir de N em N unidades?
Assim todo o controle de transação fica na sua camada de acesso a dados. Parece até mais lógico isso.
[]s!
Fernando Henrique Inocêncio Borba Ferreira
while(alive){ this.WriteCode(); }
Blog: http://ferhenriquef.com/
Twitter: @ferhenrique -
Concordo com você, mas aí eu caio em um outros pequenos problemas.
Eu tenho situações que não há collections, mas arquivos .txt, para serem importados do bom e velho sistema em Cobol de pessoal da empresa patrocinadora do fundo em que trabalho.
E a tela para o usuário, como ficaria?
No sistema atual eu mostro o progresso para ele.
Eu não conheço bem a mecânica das transações no Entity Framework, mas minha ideia é criar um método dentro da DAL parecido com o da String de Conexão que eu postei ontem.
Será que dessa forma ele aceitaria normal?
-
Entendi seu problema...
Neste caso a sua camada de controle teria conhecimento da tecnologia de acesso a dados.
Nesta funcionalidade de importação de arquivos vc utiliza transação também?
Da pra fazer a transação ser aberta dentro desta sua classe, mas talvez não seja o melhor cenário, ou uma boa prática... =/
Mas se tiver de ser assim, podemos fazer...
[]s!
Fernando Henrique Inocêncio Borba Ferreira
while(alive){ this.WriteCode(); }
Blog: http://ferhenriquef.com/
Twitter: @ferhenrique -
Isso é uma situação meio complicada.
Preciso das transações por que só quero que os dados sejam salvos no banco caso o arquivo esteja perfeito ou caso eu consiga transferir todas as informações sem problemas.
Ao mesmo tempo não posso deixar meu sistema parado em um arquivo de 19000 linhas sem o usuário saber o progresso.
Estou meio sem saida, por que não queria detonar minha arquitetura ao mesmo tempo tenho esse cenário.
-
Olá Silvio,
Nesse caso, o que podemos fazer para manter tudo dentro das melhores práticas é:
1 - Fazer sua classe de acesso a dados receber um array de objetos
2 - Abrir a transação dentro da camada de acesso a dados
3 - Criar um evento que seja disparado toda vez que a um dos itens for iterado, assim vc consegue assinar o evento na sua controller e avisar o usuário sempre que um item for executado.
Acho que desta forma é uma boa solução.
Quer um código de exemplo?
[]s!
Fernando Henrique Inocêncio Borba Ferreira
while(alive){ this.WriteCode(); }
Blog: http://ferhenriquef.com/
Twitter: @ferhenrique -
-
Olá Silvio,
Seu código da camada de acesso a dados deve ficar assim:
public class Operacoes<T> : IOperacoes<T> where T : EntityObject { // Este é seu evento public event EventHandler ItemAdded; public bool Insert(T[] entity) { using (System.Data.Common.DbTransaction transaction = PSAInstanceDB.context.Connection.BeginTransaction()) { try {
int count = 0; foreach (var item in entity) { PSAInstanceDB.context.AddObject(item, entity); // Este ponto é onde vc invoca seu evento if (ItemAdded != null) this.ItemAdded(this, new EventArgs());
count++;
if (count >= 10)
{
PSAInstanceDB.context.SaveChanges();
count = 0;} } PSAInstanceDB.context.SaveChanges(); transaction.Commit(); return true; } catch (Exception e) { transaction.Rollback(); if (e.InnerException == null) m = new Messages(e.Message); else m = new Messages(e.InnerException.Message); return false; } } } }
E sua camada de Controller provavelmente ficará assim:
public void SeuMetodoInclusao() { List<DespesaAntiga> despesasAntigas = new Despesas(); despesasAntigas = BusinessFactory.oDespesaAntigaBLL.SelectAll(); List<Despesa> novositens = new List<Despesa>(); foreach(DespesaAntiga despesaAntiga in despesasAntigas){ Despesa despesa = new Despesa(); despesa.Valor = despesaAntiga.Valor; novositens.Add(despesa); BusinessFactory.oDespesaBL.ItemAdded += operacoes_ItemAdded; } BusinessFactory.oDespesaBLL.Insert(novositens.ToArray()); MessageBox.Show("Processo Finalizado com Sucesso."); } public void operacoes_ItemAdded(object sender, EventArgs e) { // Um item foi executado }
[]s!
Fernando Henrique Inocêncio Borba Ferreira
while(alive){ this.WriteCode(); }
Blog: http://ferhenriquef.com/
Twitter: @ferhenrique- Marcado como Resposta Fernando Henrique Inocêncio Borba FerreiraMicrosoft employee, Moderator quinta-feira, 28 de junho de 2012 17:31
-
-
Olá Silvio,
Alguma evolução nesta questão?
[]s!
Fernando Henrique Inocêncio Borba Ferreira
while(alive){ this.WriteCode(); }
Blog: http://ferhenriquef.com/
Twitter: @ferhenrique