Paulo Roberto's profileSomos a última geraçãoPhotosBlogListsMore ![]() | Help |
Somos a última geraçãoVida e tecnologia |
||||||||||||||||||||||
October 31 Nova versao do NHibernateacabou de sair uma versao nova! ainda não vi as novidades. mas, vamos baixar! www.nhforge.org October 22 Auditoria de dados, usando NHibernateO NHibernate possui alguns eventos padrão que nos permitem fazer alguma coisa quando um objetos é incluído, excluido ou carregado do banco de dados. São os chamados EventListeners, realmente não tem muita documentação sobre o assunto, então, fazer qualquer coisa sobre isso, demanda uma boa pesquisa. Comecei aqui a implementar algo, então, vou poupar vocês de algum trabalho. No meu caso, eu preciso registrar as mudanças de informações que uma determinada classe sofreu, e quem realizou a mudança. Isso deve ficar armazenado no banco de dados. Logo, defini a seguinte classe: 1: public class Historico 2: {3: private Int64 _id; 4: private string _classe; 5: private string _alteracoes; 6: 7: public virtual Int64 Id 8: {9: get { return _id; } 10: set{ _id = value;} 11: } 12: 13: public virtual string Classe 14: {15: get{ return _classe;} 16: set{ _classe = value;} 17: 18: } 19: 20: public virtual string Alteracoes 21: {22: get { return _alteracoes; } 23: set{ _alteracoes = value;} 24: } 25: 26: public Historico() 27: { 28: Id = 0;29: Classe = string.Empty; 30: Alteracoes = string.Empty; 31: } 32: 33: public Int64 IdObjeto { get; set; } 34: public string Acao { get; set; } 35: public DateTime Data { get; set; } 36: public string Usuario { get; set; } 37: }
Nada complicado. Definimos aí uma propriedade Classe que irá armazenar o nome da classe que sofre alteração, uma propriedade Historico, que irá conter as alterações feitas, IdObjeto que guarda o Id do objeto que sofre alteração; Acao, que guarda o tipo de acontecido, Data que representa quando foi alterado e Usuario, que guarda o nome do usuario que fez a alteração. Como essa classe deve ser persistida, é preciso também fazer seu mapeamento. Não vou entrar em detalhes como fazer isso… Agora chegou a hora de implementarmos os eventos. Para registrar o que foi alterado, é preciso implementar a interface IPostUpdateEventListener. Ela disponibiliza o método OnPostUpdate. Esse evento é disparado depois que o objeto foi atualizado. A seguir temos a classe de auditoria. 1: public class LogAlteracao : IPostUpdateEventListener 2: {3: private const string _noValueString = " "; 4: 5: private static string getStringValueFromStateArray(object[] stateArray, int position) 6: {7: var value = stateArray[position]; 8: 9: return value == null || value.ToString() == string.Empty 10: ? _noValueString11: : value.ToString(); 12: } 13: 14: public void OnPostUpdate(PostUpdateEvent @event) 15: {16: if (@event.Entity is Historico) 17: {18: return; 19: } 20: 21: var entityFullName = @event.Entity.GetType().FullName; 22: 23: if (@event.OldState == null) 24: {25: var session = @event.Session.GetSession(EntityMode.Poco); 26: var sb = new StringBuilder(); 27: 28: for (int i = 0; i <= @event.State.Count() - 1; i++) 29: {30: var newValue = getStringValueFromStateArray(@event.State, i); 31: sb.AppendLine("Propriedadade: " + @event.Persister.PropertyNames[i]); 32: sb.AppendLine(" Novo valor..: " + newValue); 33: sb.AppendLine("-------------------------------"); 34: } 35: 36: var historico = new Historico(); 37: historico.Classe = @event.Entity.GetType().Name; 38: historico.IdObjeto = (Int64)@event.Id; 39: historico.Acao = "Atualização"; 40: historico.Alteracoes = sb.ToString(); 41: historico.Data = DateTime.Now; 42: session.Save(historico); 43: }44: else 45: {46: var dirtyFieldIndexes = @event.Persister.FindDirty(@event.State, @event.OldState, @event.Entity, 47: @event.Session); 48: 49: var session = @event.Session.GetSession(EntityMode.Poco); 50: var sb = new StringBuilder(); 51: 52: foreach (var dirtyFieldIndex in dirtyFieldIndexes) 53: { 54: 55: //Aqui tem que testar se for component. 56: //Se for, tem que pegar as propriedades manualmente... por reflection 57: if (@event.Persister.PropertyTypes[dirtyFieldIndex] is ComponentType) 58: {59: // Recupera o estado atual e o anterior da propriedade. 60: var obj = @event.State[dirtyFieldIndex]; 61: var objAnterior = @event.OldState[dirtyFieldIndex]; 62: 63: var propertyInfos = obj.GetType().GetProperties();64: foreach (var info in propertyInfos) 65: {66: // Se a propriedade não pode ser lida, não faz nada. 67: if (!info.CanRead) 68: continue; 69: 70: try 71: {72: // Recupera os valor novo e o antigo da propriedade. 73: var novoValor = info.GetValue(obj, null); 74: var valorAntigo = info.GetValue(objAnterior, null); 75: 76: // Verifica se são mesmo diferentes. 77: if (valorAntigo.ToString().Equals(novoValor.ToString())) 78: continue; 79: 80: sb.AppendLine("Propriedadade: " + info.Name); 81: sb.AppendLine(" Valor antigo: " + valorAntigo); 82: sb.AppendLine(" Novo valor: " + novoValor); 83: sb.AppendLine("-------------------------------"); 84: }85: catch 86: {87: continue; 88: } 89: } 90: }91: else 92: {93: var oldValue = getStringValueFromStateArray(@event.OldState, dirtyFieldIndex); 94: var newValue = getStringValueFromStateArray(@event.State, dirtyFieldIndex); 95: 96: if (oldValue == newValue) 97: {98: continue; 99: } 100: 101: sb.AppendLine("Propriedadade: " + @event.Persister.PropertyNames[dirtyFieldIndex]); 102: sb.AppendLine(" Valor antigo: " + oldValue); 103: sb.AppendLine(" Novo valor..: " + newValue); 104: sb.AppendLine("-------------------------------"); 105: } 106: } 107: 108: var historico = new Historico(); 109: historico.Classe = @event.Entity.GetType().Name; 110: historico.IdObjeto = (Int64) @event.Id; 111: historico.Acao = "Atualização"; 112: historico.Alteracoes = sb.ToString(); 113: historico.Data = DateTime.Now; 114: session.Save(historico); 115: session.Flush(); 116: } 117: } 118: }
Vamos dar uma entendida no código…. Todas informações que vamos precisar estão acessíveis no parêmetro PostUpdateEvent event. A primeira coisa que verificamos é se, o objeto que está vindo é Historico. Se for, não fazemos nada… senão ia ficar em loop, certo? Na sequência verificamos se o objeto possui um OldState. Essa propriedade representa o estado do objeto antes da alteração e só é preenchida se o objeto tiver sido carregado pelo Session que está em uso. Caso o objeto não tenha OldState, percorremos a propriedade State, que contém os valores atuais, e montamos com esses valores uma string para ser salva. Então instanciamos um objeto Historico e salvamos o objeto. Agora, se o objeto tem um OldState, podemos gerar histórico só do que foi alterado. Veja a linha 46. O NHibernate disponibiliza o método FindDirty, que retorna os índices das propriedades que sofreram alterações. Aí é só montar novamente o texto e salvar… Quero apontar um detalhe lá na linha 57. Estamos verificando se a propriedade que vem é do tipo ComponentType. Quando no mapeamento de uma classe, mapeamos outra nela como Component, ela aparece nesse evento PostUpdate como uma única propriedade. Assim, é necessário usarmos reflection pra extrair as propriedades….. Talvez precise melhorar algo aí nessa implementação, mas por enquanto… está funcionando :). Depois posto como registrar as exclusões.
October 05 Configurando o NHibernate por códigoOlá pessoal. Aqui temos a necessidade de flexibilizar a string de conexão do sistema, de tal forma que o usuário possa mudá-la a partir de uma tela de configurações. Outro detalhe que prevemos, é a mudança de banco de dados sem a necessidade de recompilar o projeto. Levando tudo isso em consideração, mais a possibilidade de adicionar algum parâmetro de execução e habilitar ou não a exibição de SQL ou geração de estatísticas, resolver configurar o NHibernate por código. Contudo o desafio em fazer isso estava no fato de que utilizamos o projeto uNHaddins. Até o momento estávamos utilizando a classe DefaultSessionFactoryConfigurationProvider(), responsável por carregar e configurar o NHibernate. A solução foi criar uma classe herdada de AbstractConfigurationProvider. Essa classe abstrata expoem os métodos necessários que devem ser implementados para que possam ter uma inicialização personalizada do NHibernate. Sendo assim temos a seguinte classe: /// <summary> /// Esta classe configura manualmente, por código, as propriedades do NHibernate. /// Estamos fazendo por código para facilitar a troca de connection string e até mesmo /// a troca de banco de dados. /// </summary> public class ByCodeConfigurationProvider: AbstractConfigurationProvider { private readonly string _connectionString; private Configuration _configuration; /// <summary> /// Construtor da classe. Recebe como parâmetro <paramref name="connectionString"/> /// </summary> /// <param name="connectionString">String de conexão do banco de dados em uso</param> public ByCodeConfigurationProvider(string connectionString) { _connectionString = connectionString; } private void ConfigureByCode() { if (_configuration == null) { _configuration = new Configuration(); _configuration.SetProperty("connection.driver_class","NHibernate.Driver.SqlClientDriver"); _configuration.SetProperty("hbm2ddl.auto","update"); _configuration.SetProperty("show_sql","true"); _configuration.SetProperty("connection.connection_string",@"Data Source=SRV2003NET\SQLEXPRESS;Initial Catalog=NotasBD;Persist Security Info=True;User ID=NotasMaster;Password=cont10rol"); _configuration.SetProperty("adonet.batch_size","10"); _configuration.SetProperty("dialect","NHibernate.Dialect.MsSql2005Dialect"); _configuration.SetProperty("use_outer_join","true"); _configuration.SetProperty("command_timeout","10"); _configuration.SetProperty("query.substitutions","true 1, false 0, yes 'Y', no 'N'"); _configuration.SetProperty("current_session_context_class","uNhAddIns.SessionEasier.Conversations.ThreadLocalConversationalSessionContext, uNhAddIns"); _configuration.SetProperty("proxyfactory.factory_class","NHibernate.ByteCode.Castle.ProxyFactoryFactory, NHibernate.ByteCode.Castle"); _configuration.SetProperty("use_proxy_validator","false"); _configuration.AddAssembly(Assembly.GetAssembly(typeof (Pessoa))); var eventos = _configuration.EventListeners; eventos.PreUpdateEventListeners = new List<IPreUpdateEventListener>(eventos.PreUpdateEventListeners){new EventListener()}.ToArray(); eventos.PreInsertEventListeners = new List<IPreInsertEventListener>(eventos.PreInsertEventListeners){new EventListener()}.ToArray(); } } public override IEnumerable<Configuration> Configure() { ConfigureByCode(); return new SingletonEnumerable<NHibernate.Cfg.Configuration>(_configuration); } } Como pode ser visto, o método ConfigureByCode instancia um objeto Configuration do NHibernate e utilizando o método SetProperty, configura suas propriedades. O construtor da classe recebe como parâmetro uma string que contém a connection string do banco, porém não estamos utilizando nesse momento. Mas o detalhe da flexibilização é que como estamos fazendo tudo por código, é muito fácil ler algum arquivo de configuração e mudar essas propriedades como desejarmos, sem termos que recompilar código. Aqui centralizamos a carga de nossos serviços em uma classe tipo ServiceLocator, e nela executamos a carga do NHibernate assim:
public class ServiceLocatorProvider { public static void Initialize() { var container = new WindsorContainer(); container.AddFacility<PersistenceConversationFacility>(); var nhConfigurator = new ByCodeConfigurationProvider(""); var sfp = new SessionFactoryProvider(nhConfigurator); container.Register(Component.For<ISessionFactoryProvider>().Instance(sfp)); container.Register(Component.For<ISessionWrapper>().ImplementedBy<SessionWrapper>()); container.Register(Component.For<IConversationFactory>().ImplementedBy<DefaultConversationFactory>()); container.Register(Component.For<IConversationsContainerAccessor>().ImplementedBy<NhConversationsContainerAccessor>()); container.Register(Component.For<ISessionFactory>().Instance(sfp.GetFactory(null))); container.Register(Component.For(typeof(IDao<>)).ImplementedBy(typeof(BaseDao<>)).LifeStyle.Transient); var sl = new WindsorServiceLocator(container); container.Register(Component.For<IServiceLocator>().Instance(sl)); ServiceLocator.SetLocatorProvider(() => sl); }}
Segue aí a dica! September 28 Gerenciamento de janelas em WPFDesenvolver uma aplicação comercial utilizando WPF é realmente muito legal! A plataforma nos oferece recursos de DataBinding que facilitam nossa vida como desenvolvedores e que permitem a criação de visualizações magníficas para os usuários finais. Mesmo com tantas facilidades ainda nos esbarramos em conceitos que não existem em WPF, como herança visual de XAML e até mesmo o conceito de MDI. Nota: Neste post, não entrarei nos detalhes, contudo, o código estará disponível. Aqui em nossa aplicação um dos requisitos era montar uma interface MDI, que pemitisse que um usuário pudesse abrir várias janelas de forma simultânea e até mesmo a mesma janela mais de uma vez. A solução desevolvida foi a de abstrair o conceito de “janela gerenciada” de tal forma que é possível indicar ao sistema qual janela será gerenciada ou não. Posteriormente foi necessário se criar um mecanismo que mantivesse o estado dessas janelas gerenciadas. Essas janelas são mantidas em uma lista que pode ser exibida ao usuário e ele, ao clicar em um item dessa lista, traz a janela selecionada para o foco ativo. Bem vamos ver algum código. Nota: Esta solução que apresento foi inspirada por este post, apresentando um código parecido, contudo foi ajustado para minha realidade… Logo irei postar tudo detalhado no Code Project. Tenho uma classe que irá gerenciar as janelas, a classe WindowViewStateManager. Ela herda de ViewModel, que é um tipo declarado pelo framework Julmar que facilita o uso padrão M-V-VM. Se você desenvolve em WPF sem utilizar esse padrão (ou algo similar), corra!!! Por que seu código deve estar virando uma bela macarronada! Nessa classe tenho a lista de janela que foram abertas: public ObservableCollection<WindowViewStateInstance> WindowViewStates Agora como que uma janela pode ser adicionada à essa lista? Para isso criamos uma Attached Property que inclue esse comportamento em nossas janelas. Ela está definida na classe a seguir:
public class WindowViewState { public static bool GetIsManaged(DependencyObject obj) { return (bool) obj.GetValue(IsManagedProperty); } public static void SetIsManaged(DependencyObject obj, bool value) { obj.SetValue(IsManagedProperty, value); } // Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc... public static readonly DependencyProperty IsManagedProperty = DependencyProperty.RegisterAttached("IsManaged", typeof (bool), typeof (WindowViewState), new FrameworkPropertyMetadata((bool) false, new PropertyChangedCallback( OnIsManagedChanged))); private static void OnIsManagedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { Window target = (Window) d; if (target != null) { target.Loaded += new RoutedEventHandler(target_Loaded); target.Closed += new EventHandler(target_Closed); } } private static void target_Closed(object sender, EventArgs e) { Window target = (Window) sender; if (target != null) { WindowViewStateManager.Instance.RemoveWindowViewState(target); WindowViewStateManager.Instance.ShowLastWindow(); } } private static void target_Loaded(object sender, RoutedEventArgs e) { Window target = (Window) sender; if (target != null) { WindowViewStateInstance instance = new WindowViewStateInstance(); instance.Title = target.Title; instance.Icon = new VisualBrush(target); instance.Window = target; instance.Number = (WindowViewStateManager.Instance.CountInstancesForWindowTitle(target.Title) + 1); instance.Window.Tag = instance.Number; WindowViewStateManager.Instance.HideAllVisible(instance.WindowId); WindowViewStateManager.Instance.WindowViewStates.Add(instance); } } } Diferentemente da solução original, o meu requisito exige que mesmo podendo abrir mais de uma janela, apenas uma será visível. Isso eu trato no método target_Loaded, que é disparado sempre que uma janela é carregada. Isso é feito pelo método HideAllVisible. Na verdade esse método não “esconde” as janelas, pois ao escondê-las, o VisualBrush da mesma também desaparece. Veja que uso o VisualBrush da janela para que posteriormente tenhamos uma lista das janelas abertas sendo exibidas de forma visual mesmo! Como pode ser visto aqui:
Veja lá ao fundo a miniatura das janelas, é exatamente isso que estamos fazendo. Voltando ao esconder janelas, então, para que sua miniatura também não desapareça, apenas movo a posição da janela para uma área não visível e, posteriormente quando for necessário exibí-la novamente, a trago para o centro do monitor: public void HideAllVisible(int WindowHashCode) { foreach (var view in _windowViewStates) { if (view.WindowId != WindowHashCode) { view.Window.Left = -3000; } } } private void CenterWindow(Window w) { var width = (SystemParameters.PrimaryScreenWidth / 2); var tamanhojanela = w.Width / 2; var height = SystemParameters.PrimaryScreenHeight / 2; var alturajanela = w.Height / 2; w.Left = width - tamanhojanela; w.Top = height - alturajanela; } Ao fechar uma janela, a removemos da lista e pedimos para que a última que foi aberta, fique ativa novamente: private static void target_Closed(object sender, EventArgs e) { Window target = (Window) sender; if (target != null) { WindowViewStateManager.Instance.RemoveWindowViewState(target); WindowViewStateManager.Instance.ShowLastWindow(); } } public bool RemoveWindowViewState(Window window) { var view = (from v in _windowViewStates where (v.Title == window.Title) && (v.Number.ToString() == window.Tag.ToString()) select v).First(); if (view != null) { if (_windowViewStates.Count == 1) { view.Window.IsEnabled = true; view.Window.Owner.Activate(); view.Window.Owner.Focus(); } _windowViewStates.Remove(view); view = null; GC.Collect(); return true; } return false; } public void ShowLastWindow() { if (_windowViewStates.Count > 0) { var w = _windowViewStates[_windowViewStates.Count - 1]; CenterWindow(w.Window); } } Para testar tudo isso foi criado um estilo para um ListBox, onde ele fique na horizontal. Ele irá exibir o conteúdo da lista de janelas gerenciadas. Antes, para definir que uma janela é gerenciada, fazemos uso da AttachedProperty: <Window x:Class="Test.Window2" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:WindowViewState;assembly=WindowViewState" Title="Window2" Height="300" Width="300" Background="Aqua" local:WindowViewState.IsManaged="true"> <Grid> <Button Margin="90,109,114,130" Name="button1">Button</Button> </Grid> </Window> Depois, no construtor da janela principal do projeto de teste, ligamos o datacontext do ListBox à lista de janelas abertas: public Window1() { InitializeComponent(); WindowsList.DataContext = WindowViewStateManager.Instance; } E lá no ListBox utilizo um recurso do Framework Julmar para ligar o evento de SelectionChanged ao comando definido em WindowViewStateManager. <ListBox x:Name="WindowsList" DockPanel.Dock="Top" Style="{DynamicResource WindowsListTemplate}" ItemsSource="{Binding Path=WindowViewStates}" ItemContainerStyle="{DynamicResource WindowsListItem}" IsSynchronizedWithCurrentItem="True" SelectedItem="{Binding Path=SelectedWindow}" ItemTemplate="{StaticResource WindowsListDataTemplate}" Height="140"> <julmar:EventCommander.Mappings> <julmar:CommandEvent Command="{Binding CmdSetFocusWindow}" Event="SelectionChanged" /> </julmar:EventCommander.Mappings> </ListBox> public ICommand CmdSetFocusWindow { get; private set; } public ICommand CmdCloseWindow { get; private set; } private WindowViewStateManager() { CmdCloseWindow = new DelegatingCommand(CloseWindow, CanCloseWindow); CmdSetFocusWindow = new DelegatingCommand(FocusWindow, CanFocusWindow); } private void FocusWindow() { if (SelectedWindow !=null) SetFocusWindow(SelectedWindow.WindowId); } public bool SetFocusWindow(int WindowHashCode) { var view = (from v in _windowViewStates where v.WindowId == WindowHashCode select v).First(); HideAllVisible(view.WindowId); CenterWindow(view.Window); return view.Window.Activate(); } Com isso temos um gerenciador de janelas funcional :) Baixem o código, estudem e comentem !! Sei que aqui não entrei em detalhes profundos, mas qualquer dúvida, é só entrar em contato. Abraço!! |
Agradeço a sua visita!
Renato Lopeswrote:
Boa Tarde !
Li um artigo que você escreveu sobre FastReport, e pensei que talves você possa me ajudar estou com um problema,trabalho com Delphi 2006 e FastReport versão 4.0.11, preciso fazer um lookup dentro do do relatório pegando o campo do segundo select e criando ele no primeiro exixte a possibilidade de isso ser feito. Desde ja agradeço a atenção,
Renato Bruno Ribeiro Lopes
July 12
|
|||||||||||||||||||||
|
|