2010-07-19 1 views
0

J'ai un cadre d'entité avec une relation plusieurs-à-plusieurs entre les clients et les contacts.Problème avec la liaison de données dans xaml via un service de domaine RIA

J'ai généré une classe de service de domaine et ajouté manuellement la méthode suivante.

public Customer GetCustomerById(int Id) 
{ 
    return this.ObjectContext.Customer.Include("Contacts").SingleOrDefault(s => s.Id == Id); 
} 

Je veux maintenant créer une page qui me montre les détails du client et une liste de contacts associés à ce client.

Je suis ce qui suit dans codebehind du clientdetails.xaml pour lire le paramètre Id qui est passé dans la page.

public int CustomerId 
{ 
    get { return (int)this.GetValue(CustomerIdProperty); } 
    set { this.SetValue(CustomerIdProperty, value); } 
} 

public static DependencyProperty CustomerIdProperty = DependencyProperty.Register("CustomerId", typeof(int), typeof(CustomerDetails), new PropertyMetadata(0)); 

// Executes when the user navigates to this page. 
protected override void OnNavigatedTo(NavigationEventArgs e) 
{ 
    if (this.NavigationContext.QueryString.ContainsKey("Id")) 
    { 
     CustomerId = Convert.ToInt32(this.NavigationContext.QueryString["Id"]); 
    } 
} 

J'utilise le XAML suivant pour la page:

<Grid x:Name="LayoutRoot" DataContext="{Binding ElementName=customerByIdSource, Path=Data}"> 
    <riaControls:DomainDataSource Name="customerByIdSource" AutoLoad="True" QueryName="GetCustomerById"> 
     <riaControls:DomainDataSource.QueryParameters> 
      <riaControls:Parameter ParameterName="Id" Value="{Binding ElementName=CustomerDetailsPage, Path=CustomerId}" /> 
     </riaControls:DomainDataSource.QueryParameters> 
     <riaControls:DomainDataSource.DomainContext> 
      <sprint:Customer2DomainContext/> 
     </riaControls:DomainDataSource.DomainContext> 
    </riaControls:DomainDataSource> 
    <StackPanel x:Name="CustomerInfo" Orientation="Vertical"> 
     <StackPanel Orientation="Horizontal" Margin="3,3,3,3"> 
      <TextBlock Text="Id"/> 
      <TextBox x:Name="idTextBox" Text="{Binding Id}" Width="160"/> 
     </StackPanel> 
     <StackPanel Orientation="Horizontal" Margin="3,3,3,3"> 
      <TextBlock Text="Name"/> 
      <TextBox x:Name="nameTextBox" Text="{Binding Name}" Width="160"/> 
     </StackPanel> 

     <ListBox ItemsSource="{Binding Contact}" DisplayMemberPath="FullName" Height="100" /> 
    </StackPanel> 
</Grid> 

Quand je fais les zones de texte se faire joliment peuplé par la liaison de données, mais le reste vide listbox.

Deux questions:

  1. Puis-je spécifier en quelque sorte le retour type de la requête GetCustomerById, donc je peux voir les noms quand je précise la liaison à travers l'interface graphique propriétés?

  2. Qu'est-ce que je fais tort ici? Pourquoi mon ListBox n'est pas rempli? Est-ce que je vais le faire de la bonne manière ou dois-je aussi définir la liaison de données pour la liste dans codebehind? Si c'est le cas, comment? Je n'ai pas trouvé comment accéder par programme à la propriété Contacts via la source de données de domaine.

J'utilise silverlight et Entity Framework 4.

Répondre

0

J'ai trouvé une réponse à ma question 2:

La propriété association Contacts ne sont pas inclus dans les types d'objets de service de domaine qui sont générés. Vous devez spécifier l'attribut [Include] pour qu'ils soient inclus. Cependant, l'attribut include nécessite un attribut [Association]. Vous ne pouvez pas spécifier l'attribut [Association] car il s'agit d'une relation plusieurs à plusieurs et l'attribut d'association vous oblige à spécifier des clés étrangères.

La solution consiste à envelopper vos objets dans un objet de transfert de données (DTO). Je n'ai pas eu à apporter de changements majeurs au code qui figurait déjà dans ma question. La seule chose changé est la récupération du client dans la classe de service de domaine:

public CustomerDTO GetCustomerById(int Id) 
{ 
    return new CustomerDTO(this.ObjectContext.Customers.Include("Contacts").SingleOrDefault(s => s.Id == Id)); 
} 

La partie principale de la solution était de changer ajouter les classes DTO au modèle-cadre de l'entité sous-jacente:

[DataContract] 
public partial class CustomerDTO : Customer 
{ 

    public CustomerDTO() { } 
    public CustomerDTO(Customer customer) 
    { 
     if (customer != null) 
     { 
      Id = customer.Id; 
      Name = customer.Name; 
      CustomerContacts = new Collection<ContactDTO>(); 
      foreach (Contact d in customer.Contacts) 
      { 
       CustomerContacts.Add(new ContactDTO(d, Id)); 
      } 
     } 
    } 

    [DataMember] 
    [Include] 
    [Association("CustomerContacts", "CustomerId", "Id")] 
    public Collection<ContactDTO> CustomerContacts 
    { 
     get; 
     set; 
    } 
} 

[KnownType(typeof(CustomerDTO))] 
public partial class Customer 
{ 
} 

[DataContract()] 
public partial class ContactDTO : Contact 
{ 
    public ContactDTO() { } 
    public ContactDTO(Contact contact, int customerId) 
    { 
     if (contact != null) 
     { 
      Id = contact.Id; 
      FullName = contact.FullName; 
      CustomerId = customerId; 
     } 
    } 

    [DataMember] 
    public int CustomerId { get; set; } 
} 

[KnownType(typeof(ContactDTO))] 
public partial class Contact 
{ 
} 

Les attributs KnownType, DataMember et DataContract étaient requis pour que cela fonctionne. En réalité, l'instanciation des objets nécessitera un peu plus de copie des propriétés dans les constructeurs. Y at-il un moyen facile d'éviter le code qui fait une copie explicite? Je suis ouvert aux suggestions.Je souhaitais éviter l'introduction de classes supplémentaires, mais cela semble inévitable dans le cas d'une relation plusieurs à plusieurs, en raison de l'attribut Association requis qui nécessite une spécification de clé étrangère; dans mon cas Contact.CustomerId.

Quelqu'un peut-il faire mieux (== moins de codage)?