locked
Problema com o Bind usando MVVM, SilverLight 4 e EntityFramework RRS feed

  • 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 Baltazar
    sexta-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.
    quarta-feira, 27 de julho de 2011 14:22