views:

22

answers:

1

I have an entity framework with a many-to-many relationship between Customers and Contacts.

I have generated a Domain Service Class and added the following method manually.

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

I now want to create a page that shows me the customer details and a list of contacts associated with that customer.

I have the following in codebehind of the customerdetails.xaml to read the Id parameter that gets passed into the 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"]);
    }
}

I use the following xaml for the 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>

When I do this the textboxes get nicely populated through the databinding, but the listbox remains empty.

Two questions:

  1. Can I somehow specify the return type of the GetCustomerById query, so I can see the names when I specify the binding through the properties GUI?

  2. What am I doing wrong here? Why isn't my ListBox populated? Am I going about this the correct way or do I need to set the databinding for the listbox in codebehind as well? If so, how? I haven't found how toaccess the Contacts property via the domain data source programmatically.

I use silverlight and entity framework 4.

A: 

I have found an answer to my question 2:

The association property Contacts is not included in the Domain Service Object types that are generated. You have to specify the [Include] attribute for them to be included. However the include attribute requires an [Association] attribute. You can not specify the [Association] attribute because this is a many to many relationship and the association attribute requires you to specify foreign keys.

The solution is to wrap your objects in a data transfer object (DTO). I did not have to make major changes to the code that was already in my question. The only thing changed was the retrieval of the Customer in the domain service class:

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

The main part of the solution was to change add the DTO classes to the underlying entity framework model:

[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
{
}

The KnownType, DataMember and DataContract attributes were required to get this to work. In reality instantiating the objects will require a bit more copying of properties in the constructors. Is there an easy way to avoid code that does an explicit copy? I'm open for suggestions.

I was hoping to avoid the introduction of extra classes, but it seems unavoidable in case of a many to many relationship, because of the required Association attribute that needs a foreign key specification; in my case Contact.CustomerId.

Can anybody do better (== less coding) ?

Jeroen Huinink