Usuário com melhor resposta
Problema com o Bind usando MVVM, SilverLight 4 e EntityFramework

Pergunta
-
Tenho duas situações onde uma funciona e outra não. Segue os exemplos.
Uma aplicação SilverLight 4...
Na parte Servidor eu criei um Model (Usando o EF) com uma entidade. A entidade chama LocalVirtual e tem 4 atributos (ID_LOCAL, DESCR, DESCR_ABREV, PRZ).
Criei uma classe Domain. Ele já criou as duas altomaticamente (LocalVirtualDoain e LocalVirtualDomain.metadata). Segue o codigo abaixo. Vou colocar o código dessas duas classes no final do post porque são pouco relevantes uma vez que elas são geradas automaticamentes.
Com isso fechamos a parte do servidor. Estou tentando aplicar o padrão MVVM e no cliente eu criei uma classe de Model View, ela implementa InotifyPropertyChange e tem algumas propriedades, nenhuma novidade aqui em relação a MVVM.
LocaisVM
Imports Microsoft.Practices.Composite.Presentation.Commands Imports System.ComponentModel Imports SilverlightApplication1.Web.LOCALVIRTUAL Imports System.Collections.ObjectModel Imports SilverlightApplication1.Web Public Class LocaisVM Implements INotifyPropertyChanged Private _AddCommand As DelegateCommand(Of Object) Private _GetCommand As DelegateCommand(Of Object) Dim Loc As New SilverlightApplication1.Web.LocalVirtualDomain Private _Locais As ObservableCollection(Of LOCALVIRTUAL) Private _Local As LOCALVIRTUAL Public Sub New() AddCommand = New DelegateCommand(Of Object)(AddressOf ExecuteAddCommad) GetCommand = New DelegateCommand(Of Object)(AddressOf ExecuteGetCommad) End Sub Public Property AddCommand() As DelegateCommand(Of Object) Get Return _AddCommand End Get Set(ByVal value As DelegateCommand(Of Object)) _AddCommand = value End Set End Property Public Property GetCommand() As DelegateCommand(Of Object) Get Return _GetCommand End Get Set(ByVal value As DelegateCommand(Of Object)) _GetCommand = value End Set End Property Private Sub ExecuteGetCommad(ByVal parameter As Object) Loc.Load(Loc.GetLOCALVIRTUALQuery) Locais = New ObservableCollection(Of LOCALVIRTUAL)(Loc.LOCALVIRTUALs) End Sub Private Sub ExecuteAddCommad(ByVal parameter As Object) End Sub Public Property Locais As ObservableCollection(Of LOCALVIRTUAL) Get Return _Locais End Get Set(ByVal value As ObservableCollection(Of LOCALVIRTUAL)) _Locais = value NotificaMudancaPropriedade("Locais") End Set End Property Public Event PropertyChanged As PropertyChangedEventHandler Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged Public Sub NotificaMudancaPropriedade(ByVal NomePropriedade As String) RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(NomePropriedade)) End Sub End Class
A View é um controle SilverLight muito simples, uma combo box e um botão (usa para testar e comprovar o problema)
View
<UserControl x:Class="SilverlightApplication1.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:a="clr-namespace:SilverlightApplication1" mc:Ignorable="d" xmlns:interactivity="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" d:DesignHeight="246" d:DesignWidth="501" xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"> <UserControl.Resources> <a:LocaisVM x:Name ="LocalVM"/> </UserControl.Resources> <Grid x:Name="LayoutRoot" Background="White" Height="246" Width="501" DataContext="{Binding Source={StaticResource LocalVM}}"> <interactivity:Interaction.Triggers> <interactivity:EventTrigger EventName="Loaded" > <interactivity:InvokeCommandAction Command="{Binding Path=GetCommand}" /> </interactivity:EventTrigger> </interactivity:Interaction.Triggers> <ComboBox Height="24" HorizontalAlignment="Left" Margin="150,24,0,0" Name="cmbLocais" VerticalAlignment="Top" Width="295" DisplayMemberPath="DESCR" ItemsSource="{Binding Path=Locais, Mode=TwoWay" /> <sdk:Label Content="Locais" Height="24" HorizontalAlignment="Left" Margin="66,28,0,0" Name="Label1" VerticalAlignment="Top" Width="67" HorizontalContentAlignment="Right" FlowDirection="RightToLeft" /> <Button Content="Button" Height="35" HorizontalAlignment="Left" Margin="335,94,0,0" Name="Button1" VerticalAlignment="Top" Width="88" > <interactivity:Interaction.Triggers> <interactivity:EventTrigger EventName="Click" > <interactivity:InvokeCommandAction Command="{Binding Path=GetCommand, Source={StaticResource LocalVM}}" /> </interactivity:EventTrigger> </interactivity:Interaction.Triggers> </Button> </Grid> </UserControl>
Vamos ao problema. O UserControl tem um Resources
<UserControl.Resources> <a:LocaisVM x:Name ="LocalVM"/> </UserControl.Resources>
Ele é uma classe da Model View.
O Grid do Controle tem o DataContext com Binding nesse Recurso.
<Grid x:Name="LayoutRoot" Background="White" Height="246" Width="501" DataContext="{Binding Source={StaticResource LocalVM}}">
O Grid também tem um evento com Binding em um comando da Model View.
<interactivity:Interaction.Triggers> <interactivity:EventTrigger EventName="Loaded" > <interactivity:InvokeCommandAction Command="{Binding Path=GetCommand}" /> </interactivity:EventTrigger> </interactivity:Interaction.Triggers>
O combo Box tem um Binding no ItemsSource na propriedade Locais do Objeto do DataContext da Grid.
<ComboBox Height="24" HorizontalAlignment="Left" Margin="150,24,0,0" Name="cmbLocais" VerticalAlignment="Top" Width="295" DisplayMemberPath="DESCR" ItemsSource="{Binding Path=Locais, Mode=TwoWay" />
Ok. Isso estaria tudo certo dentro de um padrão MVVM usando somente Bindings. O que acontece quando roda a aplicação e se debuga é:
No momento do load do controle ele entra na classe LocaisVM (que está no bind do evento citado acima) e executa o comando apontado no Path (GetCommand). Ele delega para a Sub ExecuteGetCommad dentro da classe e essa Sub tem duas linhas.
Private Sub ExecuteGetCommad(ByVal parameter As Object) Loc.Load(Loc.GetLOCALVIRTUALQuery) Locais = New ObservableCollection(Of LOCALVIRTUAL)(Loc.LOCALVIRTUALs) End Sub
Nesse momento ele executa as duas linhas e ao final da segunda linha o Loc.LOCALVIRTUALs.Count é zero. (Tem 72 registros no banco). Quando termina de executar você vai na tela e o combo box não tem registro. (Lógico, se voltaram zero linhas).
Agora vamos ao Botão que tem o evento Click com Binding no mesmo comando que o controle tem o Loaded. Aí vem o problema, se eu colocar o BreakPoint na primeira linha desse Metodo (Loc.Load(Loc.GetLOCALVIRTUALQuery) e clicar no botão ele vai para ali e se nessa hora você olhar o Loc.LOCALVIRTUALs.Count vai ser igual a 72. Isso é o problema, olhei e olhei mais ainda e descobri o seguinte.
Quando eu debugo a primeira vez que ele entra (no Loaded do UserControl) ele passa as duas linhas e chega no End Sub e se eu continuar ele aí sim vai para o Servidor e executa o método que pega os locais virtuais. Esse método é do LocalVirtualDomain.
'TODO: ' Consider constraining the results of your query method. If you need additional input you can ' add parameters to this method or create additional query methods with different names. 'To support paging you will need to add ordering to the 'LOCALVIRTUAL' query. Public Function GetLOCALVIRTUAL() As IQueryable(Of LOCALVIRTUAL) Return Me.ObjectContext.LOCALVIRTUAL End Function
Por isso quando eu rodo a segunda vez o método (click do botão) ele já tem os 72 e a grid é carregada.
A pergunta é porque ele não carrega isso no momento que executa a primera linha do metodo (Loc.Load (Loc.GetLOCALVIRTUALQuery)) e so executa o servidor depois que sai da Sub?
Continuei a fazer alguns teste e resolvi tirar o ModeView da jogada e fazer tudo direto no load do Form. O load Ficou assim...
Private Sub MainPage_Loaded(sender As Object, e As System.Windows.RoutedEventArgs) Handles Me.Loaded Dim a As New SilverlightApplication1.Web.LocalVirtualDomain a.Load(a.GetLOCALVIRTUALQuery) cmbLocais.ItemsSource = a.LOCALVIRTUALs End Sub
Quando eu debugo o Load ele executa tudo e o a.LOCALVIRTUALs.Count também está zero, só que agora quando o form é carregado o combo está com os valores... (Eu tirei o binding do combo box para fazer isso)
Porque funciona da segunda forma mesmo o Count estando zerado e com o Bind ele não funciona?
LocalVirtualDomain
Option Compare Binary Option Infer On Option Strict On Option Explicit On Imports SilverlightApplication1.Web Imports System Imports System.Collections.Generic Imports System.ComponentModel Imports System.ComponentModel.DataAnnotations Imports System.Data Imports System.Linq Imports System.ServiceModel.DomainServices.EntityFramework Imports System.ServiceModel.DomainServices.Hosting Imports System.ServiceModel.DomainServices.Server 'Implements application logic using the Entities context. ' TODO: Add your application logic to these methods or in additional methods. ' TODO: Wire up authentication (Windows/ASP.NET Forms) and uncomment the following to disable anonymous access ' Also consider adding roles to restrict access as appropriate. '<RequiresAuthentication> _ <EnableClientAccess()> _ Public Class LocalVirtualDomain Inherits LinqToEntitiesDomainService(Of Entities) 'TODO: ' Consider constraining the results of your query method. If you need additional input you can ' add parameters to this method or create additional query methods with different names. 'To support paging you will need to add ordering to the 'LOCALVIRTUAL' query. Public Function GetLOCALVIRTUAL() As IQueryable(Of LOCALVIRTUAL) Return Me.ObjectContext.LOCALVIRTUAL End Function Public Sub InsertLOCALVIRTUAL(ByVal lOCALVIRTUAL As LOCALVIRTUAL) If ((lOCALVIRTUAL.EntityState = EntityState.Detached) _ = false) Then Me.ObjectContext.ObjectStateManager.ChangeObjectState(lOCALVIRTUAL, EntityState.Added) Else Me.ObjectContext.LOCALVIRTUAL.AddObject(lOCALVIRTUAL) End If End Sub Public Sub UpdateLOCALVIRTUAL(ByVal currentLOCALVIRTUAL As LOCALVIRTUAL) Me.ObjectContext.LOCALVIRTUAL.AttachAsModified(currentLOCALVIRTUAL, Me.ChangeSet.GetOriginal(currentLOCALVIRTUAL)) End Sub Public Sub DeleteLOCALVIRTUAL(ByVal lOCALVIRTUAL As LOCALVIRTUAL) If ((lOCALVIRTUAL.EntityState = EntityState.Detached) _ = false) Then Me.ObjectContext.ObjectStateManager.ChangeObjectState(lOCALVIRTUAL, EntityState.Deleted) Else Me.ObjectContext.LOCALVIRTUAL.Attach(lOCALVIRTUAL) Me.ObjectContext.LOCALVIRTUAL.DeleteObject(lOCALVIRTUAL) End If End Sub End Class
LocalVirtualDomain.metadata
Option Compare Binary Option Infer On Option Strict On Option Explicit On Imports System Imports System.Collections.Generic Imports System.ComponentModel Imports System.ComponentModel.DataAnnotations Imports System.Data.Objects.DataClasses Imports System.Linq Imports System.ServiceModel.DomainServices.Hosting Imports System.ServiceModel.DomainServices.Server 'The MetadataTypeAttribute identifies LOCALVIRTUALMetadata as the class ' that carries additional metadata for the LOCALVIRTUAL class. <MetadataTypeAttribute(GetType(LOCALVIRTUAL.LOCALVIRTUALMetadata))> _ Partial Public Class LOCALVIRTUAL 'This class allows you to attach custom attributes to properties ' of the LOCALVIRTUAL class. ' 'For example, the following marks the Xyz property as a ' required property and specifies the format for valid values: ' <Required()> ' <RegularExpression("[A-Z][A-Za-z0-9]*")> ' <StringLength(32)> ' Public Property Xyz As String Friend NotInheritable Class LOCALVIRTUALMetadata 'Metadata classes are not meant to be instantiated. Private Sub New() MyBase.New End Sub Public Property COD_TIP_LOC_VIRT As Nullable(Of Decimal) Public Property DESCR As String Public Property DESCR_ABREV As String Public Property ID_LOCAL As Decimal Public Property IND_BLOQUEIO As String Public Property IND_EXCLUSAO As String Public Property LOCALVIRTUALORGAO As EntityCollection(Of LOCALVIRTUALORGAO) Public Property PRZ As Nullable(Of Decimal) End Class End Class
Ricardo Baltazarsexta-feira, 27 de maio de 2011 21:11
Respostas
-
O que acontece aí é o seguinte: O seu código está criando um ObservableCollection e associando ele ao combo. Esse observable inicia vazio e apenas será inicializado quando o EF fizer a chamada. Chamadas EF em Silverlight são assincronas (assim como qualquer chamada web feita a partir de Silverlight).
Por utilizar Linq, o EF apenas executa a query de fato quando você tentar iterar na coleção dele. Só de passar como parâmetro para o construtor do ObservableCollection não garante isso.
Na segunda vez que você chama a função, o código já executou uma vez, então tem o resultado mais rapidamente e consegue criar um novo ObservableCollection com os dados e é por isso que você consegue ver.
Se eu não estou enganado, a propriedade Loc.LOCALVIRTUALs já é um ObservableCollection. Faça um teste, mudando o código de
Locais = New ObservableCollection(Of LOCALVIRTUAL)(Loc.LOCALVIRTUALs)
para
Locais = Loc.LOCALVIRTUALs
Envie um novo comentário com seu resultado.
Atenciosamente,
Kelps Leite de Sousa | MVP Silverlight
blog: http://kelps.net
twitter : http://twitter.com/kelps
Não se esqueça de "marcar como resposta" o ítem que lhe ajudou.- Marcado como Resposta Ricardo Baltazar Chaves sábado, 9 de junho de 2012 18:03
quarta-feira, 27 de julho de 2011 14:22