none
Implementing a Many-Many relationship on a Dataform using Silverlight and WCF

    Question

  • I need to implement a Many-to-Many relationship.  Any client may be assigned to one or more projects. There are likely to be a thousand or more clients and about 10 projects, increasing very slowly, in the database.

    I have a page with a dataform linked to an query returning an individual client currently working, ignoring the Project side of things.

    Ideally, I'd like to generate a stack or wrap panel with a series of tick boxes in the dataform; one for each project. The user would then be able to tick the approriate projects.

    I am just starting out on the Silverlight journey and have been finding the terrain rocky but achieveable, so far!

     

     Form

     

    I'm trying to achieve something like this:

     Form

     

    Using SQL 2008, Silverlight 3, WCF and VS2008sp1

    Wednesday, February 24, 2010 12:43 PM

Answers

  • On top of my head (haven't tried it), here is what you can try:

    Use a ItemsControl or ListBox (use StackPanel as your ItemsPanel, and set Orientation=Horizontal)  for your List of Projects. Bind the ItemsSource to the DomainContext.Projects list.  Use CheckBox inside the ListBox.  Set listBox.SelectionMode= SelectionMode.Multiple. Make sure when a Checkbox is checked, the item is selected.

    Bind ListBox.SelectedItems to your Contact.Projects field.

    You should be able to submit your Contact data with all the selected Projects.

     

     

    Wednesday, February 24, 2010 5:29 PM
  • I found a little problem with the suggestion I gave you: you can not set binding on ListBox.SelectedItems property.

    It will be a little uglier if you have to display the project list this way (use a Dual select box might be better). Here is the code I have tested:

    I have a Company Entity, each company can have multiple Products.

    <riaControl:DomainDataSource x:Name="DataSource" QueryName="GetCompanies" LoadSize="20" AutoLoad="True" DomainContext="{StaticResource MyDomainContext}">               
    </riaControl:DomainDataSource>

    <riaControl:DomainDataSource x:Name="ProductDataSource" QueryName="GetProducts" AutoLoad="True" DomainContext="{StaticResource MyDomainContext}"/>

    <dataForm:DataForm x:Name="DF" Header="Company Info" ItemsSource="{Binding Data, ElementName=CompanyDataSource}" >
                            <StackPanel dataForm:DataField.IsFieldGroup="True">
                                <dataForm:DataField>
                                    <TextBox Text="{Binding Company_Name, Mode=TwoWay}" />
                                </dataForm:DataField>

                              
                                <dataForm:DataField>
                                    <ListBox SelectionMode="Multiple" ItemsSource="{Binding Data, ElementName=ProductDataSource}" SelectionChanged="ListBox_SelectionChanged">
                                        <ListBox.ItemsPanel>
                                            <ItemsPanelTemplate>
                                            <StackPanel Orientation="Horizontal"/>                                                                           
                                            </ItemsPanelTemplate>
                                        </ListBox.ItemsPanel>
                                        <ListBox.ItemTemplate>
                                            <DataTemplate>
                                                <CheckBox Checked="CheckBox_Checked" Loaded="CheckBox_Loaded" Unchecked="CheckBox_Unchecked" Content="{Binding Product_Name}" Margin="4" />
                                            </DataTemplate>
                                        </ListBox.ItemTemplate>
                                    </ListBox>
                                </dataForm:DataField>
                            </StackPanel>
                        </dataForm:DataForm>

    Here is the code behind:

    private void CheckBox_Checked(object sender, RoutedEventArgs e)
            {
                CheckBox c = sender as CheckBox;
                SL4RIATest.Web.Product p = c.DataContext as SL4RIATest.Web.Product;
                SL4RIATest.Web.Company com = this.DF.CurrentItem as SL4RIATest.Web.Company;
                com.Products.Add(p);
            }

            private void CheckBox_Unchecked(object sender, RoutedEventArgs e)
            {
                CheckBox c = sender as CheckBox;
                SL4RIATest.Web.Product p = c.DataContext as SL4RIATest.Web.Product;
                SL4RIATest.Web.Company com = this.DF.CurrentItem as SL4RIATest.Web.Company;
                com.Products.Remove(p);
            }

            private void CheckBox_Loaded(object sender, RoutedEventArgs e)
            {
                CheckBox c = sender as CheckBox;
                SL4RIATest.Web.Product p = c.DataContext as SL4RIATest.Web.Product;
                SL4RIATest.Web.Company com = this.DF.CurrentItem as SL4RIATest.Web.Company;
                if (com.Products.Contains(p))
                    c.IsChecked = true;
            }

     You might want to wrap the code into a CheckBoxList Control for more generic use. 


    Wednesday, February 24, 2010 10:53 PM
  • It would seem to me that the Company Metadata correctly contains a property public EntityCollection<Products> Products; but on checking the generated code, there does not seem to be such a property generated, though the three other properties are generated. This would be why I cannot obtain the property Compant.products, but I'm not sure why this is happening and how to fix it.

        public partial class Company
        {
    
            // This class allows you to attach custom attributes to properties
            // of the Company class.
            //
            // For example, the following marks the Xyz property as a
            // required field and specifies the format for valid values:
            //    [Required]
            //    [RegularBLOCKED EXPRESSION]
            //    [StringLength(32)]
            //    public string Xyz;
            internal sealed class CompanyMetadata
            {
    
                // Metadata classes are not meant to be instantiated.
                private CompanyMetadata()
                {
                }
    
                public string Company_Account;
    
                public int Company_ID;
    
                public string Company_Name;
    
                public EntityCollection<Products> Products;
            }
        }
     
    Thursday, February 25, 2010 12:14 PM
  • Follow this article:

    http://www.scip.be/index.php?Page=ArticlesNET30#ManyToMany

    I'll try it tonight.

     

     

     

    Thursday, February 25, 2010 5:53 PM
  • OK, I followed 2nd suggestion in the above link. In db Company_Product table I added a dummy field so this table has company_id, product_id and the dummy field. Then I created the Entity Data Model from DB. Now I have Company, Product, Company_Product 3 Entities. Company now has Company_Product EntityCollection. 

    After the Data model is generated, I deleted the Dummy field from DB. Then updated the Model from DB again to get rid of the dummy field in the Model code.

    Here is the CompanyMetadata:

           internal sealed class CompanyMetadata
            {

                // Metadata classes are not meant to be instantiated.
                private CompanyMetadata()
                {
                }
                ...
                [Include]
                public EntityCollection<Company_Product> Company_Product;

            }

    In the Service code:

        public IQueryable<Company> GetCompanies()
            {
                return this.ObjectContext.Companies.Include("Company_Product");
            }

    Code for checkbox need to be changed to the following:

    private void CheckBox_Checked(object sender, RoutedEventArgs e)
            {
                CheckBox c = sender as CheckBox;
                Product p = c.DataContext as Product;
                Company com = this.DF.CurrentItem as Company;
                com.Company_Product.Add(new Company_Product { Product_Id = p.Product_Id });
            }

            private void CheckBox_Unchecked(object sender, RoutedEventArgs e)
            {
                CheckBox c = sender as CheckBox;
                Product p = c.DataContext as Product;
                Company com = this.DF.CurrentItem as Company;
                Company_Product cp = com.Company_Product.SingleOrDefault(o => o.Product_Id == p.Product_Id);
                if(cp!= null)
                    com.Company_Product.Remove(cp);           
            }

            private void CheckBox_Loaded(object sender, RoutedEventArgs e)
            {
                CheckBox c = sender as CheckBox;
                p = c.DataContext as Product;
                Company com = this.DF.CurrentItem as Company;
                if(com != null && com.Company_Product.SingleOrDefault(cp=>cp.Product_Id == p.Product_Id) != null)
                    c.IsChecked = true;
            }                

    I can save data for the First Company in the list. Selected Products for this company is saved. 

    But I still have one problem. When I move to next company record, the ListBox won't get refreshed. Because CheckBox Loaded event is never triggered when the DataForm.CurrentItem changed.  I haven't figure out a solution for this. I may have to code CheckBoxList control first so I can use Binding instead of this heck in the code.

    So in principle, this idea should work. Just a few technical issues need to be solved. If you figured it out, let me know.

     

     

     

    Thursday, February 25, 2010 10:25 PM
  • Did you fix this Update function bug in your Service code?

    http://www.colinblair.com/Blog/post/Bug-in-the-Create-DomainService-wizard.aspx

    Friday, February 26, 2010 9:59 AM
  • OK, I had the same problem. After remove a company_product from company.Company_Products collection, then call Context.SubmitChanges, I would get this error (not server side error, because code has not reach the server yet):

    "The value of key member 'Company_Id' on an instance of an entity of type 'Company_Product' has changed. Entity key members cannot be changed".

    I searched some old post which suggest when removing a child record,  it need to be removed from the Context.Company_Products collection, not company.Company_Products collection.

    I have no idea why they design the code that way, and asked the question in this thread http://forums.silverlight.net/forums/t/162880.aspx Hope to get some answer from MS.

    Any way, here is the code that should work:

     private void CheckBox_Unchecked(object sender, RoutedEventArgs e)
    {
    try
    {
    CheckBox c = sender as CheckBox;
    Products p = c.DataContext as Products;
    Company com = this.dataForm1.CurrentItem as Company;
    CompanyProduct cp = com.CompanyProduct.SingleOrDefault(o => o.Product_ID == p.Product_ID);
    if (cp != null)
    com.CompanyProduct.Remove(cp);
                       YourDomainContext.CompanyProduct.Remove(cp);
    }
    catch (Exception ex)
    {

    MessageBox.Show(ex.Message, "Exception in CheckBox_Unchecked: ", MessageBoxButton.OK);
    }
    finally
    {

    }
    }

     

    Friday, February 26, 2010 3:39 PM
  •  

    But I'm being stupid. I cannot get the checkboxes checked.

    You mean you can not get the initial population right?

     <ListBox x:Name="ProdList" Loaded="ProdList_Loaded" SelectionChanged="ProdList_SelectionChanged" Style="{StaticResource CheckBoxList}" ItemsSource="{Binding YourProductList}" DisplayMemberPath="Product_Name" >
                   <ListBox.ItemsPanel>
                    <ItemsPanelTemplate>
                    <StackPanel Orientation="Horizontal"/>
                   </ItemsPanelTemplate>
               </ListBox.ItemsPanel>                                         
      </ListBox>

    private void ProdList_Loaded(object sender, RoutedEventArgs e)
            {
                ListBox l = sender as ListBox;
                Company com = this.DF.CurrentItem as Company;
                foreach (Product p in ProdList.ItemsSource)
                {
                    Company_Product cp = com.Company_Product.SingleOrDefault(o => o.Product_Id == p.Product_Id);
                    if (cp != null)
                        ProdList.SelectedItems.Add(p);  // Add Company Product to the ProdList.SelectedItems (those will be checked)
                }
            }    

     

    Thursday, April 08, 2010 1:28 PM

All replies

  •  So what is your question or problem?  It's achievable with Silverlight and RIA Domain service.

     

    Wednesday, February 24, 2010 2:11 PM
  • My questions are:

    1) How do I go about generating a list of checkboxes linked to (in this case Project)

    2) How do I bind them to the table (ClientProject) so that I create and delete records as items are checked or unchecked.

    Wednesday, February 24, 2010 4:56 PM
  • On top of my head (haven't tried it), here is what you can try:

    Use a ItemsControl or ListBox (use StackPanel as your ItemsPanel, and set Orientation=Horizontal)  for your List of Projects. Bind the ItemsSource to the DomainContext.Projects list.  Use CheckBox inside the ListBox.  Set listBox.SelectionMode= SelectionMode.Multiple. Make sure when a Checkbox is checked, the item is selected.

    Bind ListBox.SelectedItems to your Contact.Projects field.

    You should be able to submit your Contact data with all the selected Projects.

     

     

    Wednesday, February 24, 2010 5:29 PM
  • I found a little problem with the suggestion I gave you: you can not set binding on ListBox.SelectedItems property.

    It will be a little uglier if you have to display the project list this way (use a Dual select box might be better). Here is the code I have tested:

    I have a Company Entity, each company can have multiple Products.

    <riaControl:DomainDataSource x:Name="DataSource" QueryName="GetCompanies" LoadSize="20" AutoLoad="True" DomainContext="{StaticResource MyDomainContext}">               
    </riaControl:DomainDataSource>

    <riaControl:DomainDataSource x:Name="ProductDataSource" QueryName="GetProducts" AutoLoad="True" DomainContext="{StaticResource MyDomainContext}"/>

    <dataForm:DataForm x:Name="DF" Header="Company Info" ItemsSource="{Binding Data, ElementName=CompanyDataSource}" >
                            <StackPanel dataForm:DataField.IsFieldGroup="True">
                                <dataForm:DataField>
                                    <TextBox Text="{Binding Company_Name, Mode=TwoWay}" />
                                </dataForm:DataField>

                              
                                <dataForm:DataField>
                                    <ListBox SelectionMode="Multiple" ItemsSource="{Binding Data, ElementName=ProductDataSource}" SelectionChanged="ListBox_SelectionChanged">
                                        <ListBox.ItemsPanel>
                                            <ItemsPanelTemplate>
                                            <StackPanel Orientation="Horizontal"/>                                                                           
                                            </ItemsPanelTemplate>
                                        </ListBox.ItemsPanel>
                                        <ListBox.ItemTemplate>
                                            <DataTemplate>
                                                <CheckBox Checked="CheckBox_Checked" Loaded="CheckBox_Loaded" Unchecked="CheckBox_Unchecked" Content="{Binding Product_Name}" Margin="4" />
                                            </DataTemplate>
                                        </ListBox.ItemTemplate>
                                    </ListBox>
                                </dataForm:DataField>
                            </StackPanel>
                        </dataForm:DataForm>

    Here is the code behind:

    private void CheckBox_Checked(object sender, RoutedEventArgs e)
            {
                CheckBox c = sender as CheckBox;
                SL4RIATest.Web.Product p = c.DataContext as SL4RIATest.Web.Product;
                SL4RIATest.Web.Company com = this.DF.CurrentItem as SL4RIATest.Web.Company;
                com.Products.Add(p);
            }

            private void CheckBox_Unchecked(object sender, RoutedEventArgs e)
            {
                CheckBox c = sender as CheckBox;
                SL4RIATest.Web.Product p = c.DataContext as SL4RIATest.Web.Product;
                SL4RIATest.Web.Company com = this.DF.CurrentItem as SL4RIATest.Web.Company;
                com.Products.Remove(p);
            }

            private void CheckBox_Loaded(object sender, RoutedEventArgs e)
            {
                CheckBox c = sender as CheckBox;
                SL4RIATest.Web.Product p = c.DataContext as SL4RIATest.Web.Product;
                SL4RIATest.Web.Company com = this.DF.CurrentItem as SL4RIATest.Web.Company;
                if (com.Products.Contains(p))
                    c.IsChecked = true;
            }

     You might want to wrap the code into a CheckBoxList Control for more generic use. 


    Wednesday, February 24, 2010 10:53 PM
  • Ok, I have tried to reproduce something along the lines you have described but have hit a couple of problems:

     As you can see from the resultant form, I can display a listbox with checkboxes outside the dataform, but it does not seem to populate inside the dataform. Why is this?

    Also, when trying to write the "Checkbox_Loaded" method, I cannot seem to reproduce the line if (com.Products.contains(p))
    c.IsChecked =
    true;

    com.Products does not seem to be property I can select! I can see all the other properties of Company vis-a-vis Company_Name etc. Why has the Products property not appeared?

            private void CheckBox_Loaded(object sender, System.Windows.RoutedEventArgs e)
            {
                CheckBox c = sender as CheckBox;
                Test1.Web.Products p = c.DataContext as Test1.Web.Products;
                Test1.Web.Company com = this.dataForm1.CurrentItem as Test1.Web.Company;
           //     if (com.Products.contains(p)) c.IsChecked = true;
            }
     

     

     
    1    <navigation:Page
    2        x:Class="Test1.Home"
    3        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    4        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    5        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    6        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    7        xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
    8        xmlns:dg="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
    9        xmlns:dds="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Ria"
    10       xmlns:activity="clr-namespace:System.Windows.Controls;assembly=ActivityControl"
    11       xmlns:dc="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data.Toolkit"   
    12       xmlns:df="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data.DataForm.Toolkit"  
    13       xmlns:app="clr-namespace:Test1.Web"
    14       xmlns:appModel="Test"
    15       mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480"  
    16       Style="{StaticResource PageStyle}">
    17   
    18       <Grid x:Name="LayoutRoot">
    19           <dds:DomainDataSource x:Name="CompanyDataSource"
    20                                 QueryName="GetCompanyByName"
    21                                 AutoLoad="True"
    22                                 LoadSize="40">
    23               <dds:DomainDataSource.DomainContext>
    24                   <!--
    25                       This domain Context should be as a static resource
    26                       like
    27                          DomainContext="{StaticResource MyDomainContext}
    28                       but I'm not sure how I can do this.
    29                   -->
    30                   <app:CompanyContext />
    31               </dds:DomainDataSource.DomainContext>
    32           </dds:DomainDataSource>
    33           <dds:DomainDataSource x:Name="ProductDataSource"
    34                                 QueryName="GetProductsByName"
    35                                 AutoLoad="True">
    36               <dds:DomainDataSource.DomainContext>
    37                   <!-- This domain Context should be as a static resource as above.-->
    38                   <app:CompanyContext />
    39               </dds:DomainDataSource.DomainContext>
    40           </dds:DomainDataSource>
    41           
    42           <ScrollViewer x:Name="PageScrollViewer" Style="{StaticResource PageScrollViewerStyle}" >
    43               <activity:Activity IsActive="{Binding IsBusy, ElementName=CompanyDataSource}"
    44                                  VerticalAlignment="Top" HorizontalAlignment="Left"
    45                                  Width="900" Margin="10,5,10,0">
    46                   <Grid>
    47                       <Grid.ColumnDefinitions>
    48                           <ColumnDefinition Width="Auto" />   <!-- DataGrid Column -->
    49                           <ColumnDefinition Width="*" />      <!-- DataForm Column -->
    50                       </Grid.ColumnDefinitions>
    51                       <Grid.RowDefinitions>
    52                           <RowDefinition Height="Auto" />     <!-- Title Row -->
    53                           <RowDefinition Height="*" />        <!-- Data Grid/Form Row -->
    54                       </Grid.RowDefinitions>
    55                       
    56                       <!-- Page Title -->
    57                       <TextBlock x:Name="HeaderText" Style="{StaticResource HeaderTextStyle}" 
    58                                  Text="{Binding Path=ApplicationStrings.HomePageTitle, Source={StaticResource ResourceWrapper}}"
    59                                  Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="0"/>
    60                       
    61                       <StackPanel x:Name="GridStackPanel"
    62                                   Grid.Column="0" Grid.Row="1"
    63                                   Style="{StaticResource ContentStackPanelStyle}">                        
    64                           
    65                           <!-- Data Grid displaying a list of companies -->
    66                           <dg:DataGrid x:Name="dataGrid1"
    67                                    AutoGenerateColumns="True"
    68                                    ItemsSource="{Binding Data, ElementName=CompanyDataSource}"/>
    69                           <dg:DataPager PageSize="20"
    70                                     Source="{Binding Data, ElementName=CompanyDataSource}"/>
    71                           
    72                           <!-- Buttons (Submit) -->
    73                           <StackPanel x:Name="buttonPanel"
    74                                       Margin="0,20,0,0" Orientation="Horizontal">
    75                               <Button x:Name="btnSubmit" Content="Submit"
    76                                       Width="100" Height="30"
    77                                       HorizontalAlignment="Left"
    78                                       Click="btnSubmit_Click"
    79                                       Margin="0,0,20,0" />
    80                           </StackPanel>
    81                       </StackPanel> <!-- GridStackPanel -->
    82                       
    83                       <StackPanel x:Name="FormStackPanel"
    84                                   Grid.Column="1" Grid.Row="1"
    85                                   Style="{StaticResource ContentStackPanelStyle}">
    86                           
    87                           <!-- Dataform displaying selected company -->
    88                           <df:DataForm x:Name="dataForm1"
    89                                        Margin="40,0,0,0"
    90                                        AutoEdit="True" AutoCommit="True"
    91                                        CommandButtonsVisibility="None"
    92                                        Header="Company Details"
    93                                        CurrentItem="{Binding ElementName=dataGrid1, Path=SelectedItem}" >
    94                               <df:DataForm.EditTemplate>
    95                                   <DataTemplate>
    96                                       <StackPanel df:DataField.IsFieldGroup="True">
    97                                           <df:DataField>
    98                                               <TextBlock Text="{Binding Company_ID, Mode=OneWay}" />
    99                                           </df:DataField>
    100                                          <df:DataField>
    101                                              <TextBox Text="{Binding Company_Account, Mode=TwoWay}" />
    102                                          </df:DataField>
    103                                          <df:DataField>
    104                                              <TextBox Text="{Binding Company_Name, Mode=TwoWay}" />
    105                                          </df:DataField>
    106                                          <df:DataField>
    107                                              <!--
    108                                                  This ListBox sadly does not show a list of all products as checkboxes.
    109                                                  The question is: Why Not?
    110                                              -->
    111                                              <ListBox SelectionMode="Multiple" 
    112                                                       ItemsSource="{Binding Data, ElementName=ProductDataSource}">
    113                                                  <ListBox.ItemsPanel>
    114                                                      <ItemsPanelTemplate>
    115                                                          <StackPanel Orientation="Horizontal"/>
    116                                                      </ItemsPanelTemplate>
    117                                                  </ListBox.ItemsPanel>
    118                                                  <ListBox.ItemTemplate>
    119                                                      <DataTemplate>
    120                                                          <CheckBox Content="{Binding Product_Number}" Margin="4"/>
    121                                                      </DataTemplate>
    122                                                  </ListBox.ItemTemplate>
    123                                              </ListBox>
    124                                          </df:DataField>
    125  
    126                                      </StackPanel>
    127                                  </DataTemplate>
    128                              </df:DataForm.EditTemplate>
    129                          </df:DataForm>
    130                          
    131                          <!-- This ListBox shows a list of all products as checkboxes -->
    132                          <ListBox SelectionMode="Multiple" 
    133                                   ItemsSource="{Binding Data, ElementName=ProductDataSource}"
    134                                   Margin="40,20,0,0">
    135                              <ListBox.ItemsPanel>
    136                                  <ItemsPanelTemplate>
    137                                      <StackPanel Orientation="Horizontal"/>
    138                                  </ItemsPanelTemplate>
    139                              </ListBox.ItemsPanel>
    140                              <ListBox.ItemTemplate>
    141                                  <DataTemplate>
    142                                      <CheckBox Content="{Binding Product_Number}" Margin="4" />
    143                                  </DataTemplate>
    144                              </ListBox.ItemTemplate>
    145                          </ListBox>
    146                          
    147                          <!-- Data Grid to show that I am viewing the list of products -->
    148                          <dg:DataGrid x:Name="dataGrid2"
    149                                       AutoGenerateColumns="True"
    150                                       Margin="40,20,0,0"
    151                                       ItemsSource="{Binding Data, ElementName=ProductDataSource}"/>
    152                          
    153                      </StackPanel> <!-- FormStackPanel -->
    154                      
    155                  </Grid>
    156              </activity:Activity>
    157          </ScrollViewer>
    158      </Grid>
    159  
    160  </navigation:Page>
    
     Data Structure being used 

     Data Structure used

    Resultant Form Data Structure used

    Thursday, February 25, 2010 12:03 PM
  • It would seem to me that the Company Metadata correctly contains a property public EntityCollection<Products> Products; but on checking the generated code, there does not seem to be such a property generated, though the three other properties are generated. This would be why I cannot obtain the property Compant.products, but I'm not sure why this is happening and how to fix it.

        public partial class Company
        {
    
            // This class allows you to attach custom attributes to properties
            // of the Company class.
            //
            // For example, the following marks the Xyz property as a
            // required field and specifies the format for valid values:
            //    [Required]
            //    [RegularBLOCKED EXPRESSION]
            //    [StringLength(32)]
            //    public string Xyz;
            internal sealed class CompanyMetadata
            {
    
                // Metadata classes are not meant to be instantiated.
                private CompanyMetadata()
                {
                }
    
                public string Company_Account;
    
                public int Company_ID;
    
                public string Company_Name;
    
                public EntityCollection<Products> Products;
            }
        }
     
    Thursday, February 25, 2010 12:14 PM
  • As you can see from the resultant form, I can display a listbox with checkboxes outside the dataform, but it does not seem to populate inside the dataform. Why is this?

     

    I have seen that problem and it happens randomly. Seems like timing issue with multiple Datasources on the same page, each will load data, some comes back sooner than the other.

    Try this:

    <activity:Activity IsActive="{Binding IsBusy, ElementName=ProductDataDataSource}"> 

    I don't know if it is a bug.  But I usually use ViewModel, so Data loading happens in the ViewModel using DomainContext. So I don't usually see this problem.


     

    Thursday, February 25, 2010 1:02 PM
  •  Put a [Include] tag on Products property in your metadata.cs

    [Include]

    public EntityCollection<Products> Products;
     
    Thursday, February 25, 2010 1:05 PM
  • I can see the logic with [include] but I'm still missing something. 

    Error 1 Invalid Include specification for member 'Company.Products'. Non-projection includes can only be specified on members with the AssociationAttribute applied.

    The Web back end compiles ok but when I try and build the Silverlight front end, I get the error and it won't write the generated code!
     

    1    #pragma warning disable 649    // disable compiler warnings about unassigned fields
    2    
    3    namespace Test1.Web
    4    {
    5        using System;
    6        using System.Collections.Generic;
    7        using System.ComponentModel;
    8        using System.ComponentModel.DataAnnotations;
    9        using System.Data.Objects.DataClasses;
    10       using System.Linq;
    11       using System.Web.DomainServices;
    12       using System.Web.Ria;
    13       using System.Web.Ria.Services;
    14   
    15   
    16       // The MetadataTypeAttribute identifies CompanyMetadata as the class
    17       // that carries additional metadata for the Company class.
    18       [MetadataTypeAttribute(typeof(Company.CompanyMetadata))]
    19       public partial class Company
    20       {
    21   
    22           // This class allows you to attach custom attributes to properties
    23           // of the Company class.
    24           //
    25           // For example, the following marks the Xyz property as a
    26           // required field and specifies the format for valid values:
    27           //    [Required]
    28           //    [RegularBLOCKED EXPRESSION]
    29           //    [StringLength(32)]
    30           //    public string Xyz;
    31           internal sealed class CompanyMetadata
    32           {
    33   
    34               // Metadata classes are not meant to be instantiated.
    35               private CompanyMetadata()
    36               {
    37               }
    38   
    39               public string Company_Account;
    40   
    41               public int Company_ID;
    42   
    43               public string Company_Name;
    44   
    45               [Include]
    46               public EntityCollection<Products> Products;
    47           }
    48       }
    49   
    ...

     I did remember to add .include statements when getting the Company

     

            public IQueryable<Company> GetCompany()
            {
                return this.ObjectContext.Company
                    .Include("Products");
            }
    
            public IQueryable<Company> GetCompanyByName()
            {
                return this.ObjectContext.Company
                    .Include("Products")
                    .OrderBy(company=>company.Company_Name);
            }

     

    Thursday, February 25, 2010 3:16 PM
  •  Do you have FK relationship set up in the DB and in the EntityFramework DataModel?

    Check your DataModel.designer.cs see if your have this Tag for Products:

     [EdmRelationshipNavigationPropertyAttribute("YourModelName", "FK_Product_Company", "Product")]
            public EntityCollection<Product> Products
            {

              ...

             }

    Thursday, February 25, 2010 3:23 PM
  • Is this what you mean?

            /// &lt;summary&gt;
            /// There are no comments for Products in the schema.
            /// &lt;/summary&gt;
            [global::System.Data.Objects.DataClasses.EdmRelationshipNavigationPropertyAttribute("CompanyModel", "CompanyProduct", "Products")]
            [global::System.Xml.Serialization.XmlIgnoreAttribute()]
            [global::System.Xml.Serialization.SoapIgnoreAttribute()]
            [global::System.Runtime.Serialization.DataMemberAttribute()]
            public global::System.Data.Objects.DataClasses.EntityCollection<Products> Products
            {
                get
                {
                    return ((global::System.Data.Objects.DataClasses.IEntityWithRelationships)(this)).RelationshipManager.GetRelatedCollection<Products>("CompanyModel.CompanyProduct", "Products");
                }
                set
                {
                    if ((value != null))
                    {
                        ((global::System.Data.Objects.DataClasses.IEntityWithRelationships)(this)).RelationshipManager.InitializeRelatedCollection<Products>("CompanyModel.CompanyProduct", "Products", value);
                    }
                }
            }
     
    Thursday, February 25, 2010 3:33 PM
  • There is also this... 

            /// &lt;summary&gt;
            /// There are no comments for Company in the schema.
            /// &lt;/summary&gt;
            [global::System.Data.Objects.DataClasses.EdmRelationshipNavigationPropertyAttribute("CompanyModel", "CompanyProduct", "Company")]
            [global::System.Xml.Serialization.XmlIgnoreAttribute()]
            [global::System.Xml.Serialization.SoapIgnoreAttribute()]
            [global::System.Runtime.Serialization.DataMemberAttribute()]
            public global::System.Data.Objects.DataClasses.EntityCollection<Company> Company
            {
                get
                {
                    return ((global::System.Data.Objects.DataClasses.IEntityWithRelationships)(this)).RelationshipManager.GetRelatedCollection<Company>("CompanyModel.CompanyProduct", "Company");
                }
                set
                {
                    if ((value != null))
                    {
                        ((global::System.Data.Objects.DataClasses.IEntityWithRelationships)(this)).RelationshipManager.InitializeRelatedCollection<Company>("CompanyModel.CompanyProduct", "Company", value);
                    }
                }
            }
     
    Thursday, February 25, 2010 3:36 PM
  •  Are you using EntityFrameWork or LinqToSQL?

    Never mind. I saw you are using EntityFramework. And you have this:

     [global::System.Data.Objects.DataClasses.EdmRelationshipNavigationPropertyAttribute("CompanyModel", "CompanyProduct", "Products")]

    So your Company has no direct relationship with Products. It has relationship with CompanyProduct. Then CompanyProduct has direct relationship with Products. That is why.

    Your Company should have a CompanyProducts property. You need to include that instead of Products.


     

     

     

    Thursday, February 25, 2010 3:42 PM
  • Ok, if that hasn't happened automatically, how do I go about adding it in? The Entity Data Model shows the Many-Many relationship, so it's been picked up from the database, which can only happen by identifying the middle CopanyProducts table, surely!

     

    Entity Data Model

    Thursday, February 25, 2010 3:54 PM
  • Follow this article:

    http://www.scip.be/index.php?Page=ArticlesNET30#ManyToMany

    I'll try it tonight.

     

     

     

    Thursday, February 25, 2010 5:53 PM
  • OK, I followed 2nd suggestion in the above link. In db Company_Product table I added a dummy field so this table has company_id, product_id and the dummy field. Then I created the Entity Data Model from DB. Now I have Company, Product, Company_Product 3 Entities. Company now has Company_Product EntityCollection. 

    After the Data model is generated, I deleted the Dummy field from DB. Then updated the Model from DB again to get rid of the dummy field in the Model code.

    Here is the CompanyMetadata:

           internal sealed class CompanyMetadata
            {

                // Metadata classes are not meant to be instantiated.
                private CompanyMetadata()
                {
                }
                ...
                [Include]
                public EntityCollection<Company_Product> Company_Product;

            }

    In the Service code:

        public IQueryable<Company> GetCompanies()
            {
                return this.ObjectContext.Companies.Include("Company_Product");
            }

    Code for checkbox need to be changed to the following:

    private void CheckBox_Checked(object sender, RoutedEventArgs e)
            {
                CheckBox c = sender as CheckBox;
                Product p = c.DataContext as Product;
                Company com = this.DF.CurrentItem as Company;
                com.Company_Product.Add(new Company_Product { Product_Id = p.Product_Id });
            }

            private void CheckBox_Unchecked(object sender, RoutedEventArgs e)
            {
                CheckBox c = sender as CheckBox;
                Product p = c.DataContext as Product;
                Company com = this.DF.CurrentItem as Company;
                Company_Product cp = com.Company_Product.SingleOrDefault(o => o.Product_Id == p.Product_Id);
                if(cp!= null)
                    com.Company_Product.Remove(cp);           
            }

            private void CheckBox_Loaded(object sender, RoutedEventArgs e)
            {
                CheckBox c = sender as CheckBox;
                p = c.DataContext as Product;
                Company com = this.DF.CurrentItem as Company;
                if(com != null && com.Company_Product.SingleOrDefault(cp=>cp.Product_Id == p.Product_Id) != null)
                    c.IsChecked = true;
            }                

    I can save data for the First Company in the list. Selected Products for this company is saved. 

    But I still have one problem. When I move to next company record, the ListBox won't get refreshed. Because CheckBox Loaded event is never triggered when the DataForm.CurrentItem changed.  I haven't figure out a solution for this. I may have to code CheckBoxList control first so I can use Binding instead of this heck in the code.

    So in principle, this idea should work. Just a few technical issues need to be solved. If you figured it out, let me know.

     

     

     

    Thursday, February 25, 2010 10:25 PM
  • I've been working on this all morning with some succes. I have modified it a bit, partly to help me follow through what's going on and then reproduce it in my real application. I solved the "Moving to Next Company record by putting in a Dataform.CurrentItemChanged event, nulling the productChecklist.ItemSource and then setting it back to the productContext. It works by forcing a rebuild of each checkbox, triggering the CheckBox_Loaded event correctly. It now correctly reads the initial setting of CompanyProduct when you select a new Company. It allows me to tick a box (and submit the changes back to the database! Yaye!). Unchecking a box causes no errors until I submit the changes.

    Exception: "The value of key member 'Company_ID' on an instance of an entity of type 'CompanyProduct' has changed. Entity key members cannot be changed."

    and the data is not posted back to the database.

    namespace Test2
    {
        using System.Windows.Controls;
        using System.Windows.Navigation;
        using System;
        using System.Linq;
        using System.Windows;
        using Test2.Resources;
        using Test2.Web;
    
        public partial class Home : Page
        {
            bool loading = false;
            CompanyContext productCTX;
    
            public Home()
            {
                InitializeComponent();
                this.Title = ApplicationStrings.HomePageTitle;
            }
    
            /// &lt;summary&gt;
            ///     Executes when the user navigates to this page.
            /// &lt;/summary&gt;
            protected override void OnNavigatedTo(NavigationEventArgs e)
            {
            }
    
            private void btnSubmit_Click(object sender, System.Windows.RoutedEventArgs e)
            {
                try
                {
                    dataForm1.CommitEdit();
                    CompanyDataSource.SubmitChanges();
                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.Message, "Exception in btnSubmit_Click: ", MessageBoxButton.OK);
                }
                finally
                {
                }
            }
    
            private void CheckBox_Loaded(object sender, RoutedEventArgs e)
            {
                try
                {
                    CheckBox c = sender as CheckBox;
                    Products p = c.DataContext as Products;
                    Company com = this.dataForm1.CurrentItem as Company;
                    CompanyProduct cp = com.CompanyProduct.SingleOrDefault(compProd => compProd.Product_ID == p.Product_ID);
                    if (com != null && cp != null)
                    {
                        loading = true;
                        c.IsChecked = true;
                        loading = false;
                    }
                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.Message, "Exception in CheckBox_Loaded: ", MessageBoxButton.OK);
                }
                finally { }
            }
    
            private void CheckBox_Checked(object sender, RoutedEventArgs e)
            {
                if (!loading)
                {
                    try
                    {
                        CheckBox c = sender as CheckBox;
                        Products p = c.DataContext as Products;
                        Company com = this.dataForm1.CurrentItem as Company;
                        CompanyProduct cp = new CompanyProduct {Product_ID = p.Product_ID };
    
                        com.CompanyProduct.Add(cp);
                    }
                    catch (Exception ex)
                    {
    
                        MessageBox.Show(ex.Message, "Exception in CheckBox_Checked: ", MessageBoxButton.OK);
                    }
                    finally
                    {
    
                    }
                }
            }
    
            private void CheckBox_Unchecked(object sender, RoutedEventArgs e)
            {
                try
                {
                    CheckBox c = sender as CheckBox;
                    Products p = c.DataContext as Products;
                    Company com = this.dataForm1.CurrentItem as Company;
                    CompanyProduct cp = com.CompanyProduct.SingleOrDefault(o => o.Product_ID == p.Product_ID);
                    if (cp != null)
                        com.CompanyProduct.Remove(cp);
                }
                catch (Exception ex)
                {
    
                    MessageBox.Show(ex.Message, "Exception in CheckBox_Unchecked: ", MessageBoxButton.OK);
                }
                finally
                {
    
                }
            }
    
            private void Page_Loaded(object sender, RoutedEventArgs e)
            {
                productCTX = new CompanyContext();
                productCTX.Load(productCTX.GetProductsByNumberQuery());
            }
    
            private void dataForm1_CurrentItemChanged(object sender, EventArgs e)
            {
                productCheckList.ItemsSource = null;
                productCheckList.ItemsSource = productCTX.Products; ;
            }
        }
    }
     

     

    Friday, February 26, 2010 9:51 AM
  • Did you fix this Update function bug in your Service code?

    http://www.colinblair.com/Blog/post/Bug-in-the-Create-DomainService-wizard.aspx

    Friday, February 26, 2010 9:59 AM
  • By the way, make sure you use only one DomainContext for everything (loading and submitChanges for all the DataSources in your page).

     

    Friday, February 26, 2010 11:00 AM
  • I've now modified the code to revert back to July .NET RIA Services.
    I am using two DomainContexts out of ignorance! Looking at your code...

    <riaControl:DomainDataSource x:Name="DataSource" QueryName="GetCompanies" LoadSize="20" AutoLoad="True" DomainContext="{StaticResource MyDomainContext}">                
    </riaControl:DomainDataSource> 
    <riaControl:DomainDataSource x:Name="ProductDataSource" QueryName="GetProducts" AutoLoad="True" DomainContext="{StaticResource MyDomainContext}"/> 

    You use a StaticResource MyDomainContext and then link two DataSources to it.  I'd appreciate the bit of XAML that defines the StaticResource. I should then be able to work out how to link everything back to that.  Resources and their use is something I'm really going to have to work on, but looking at and modifying examples is the way I seem to learn best.

     By the way, Your help in this has been really appreciated!

    Friday, February 26, 2010 11:18 AM
  •  <ria:CompanyContext x:Key="MyDomainContext" />

    Where ria is your DomainContext namespace.

     

     

    Friday, February 26, 2010 11:24 AM
  • Ahh, much neater as well, when I've put that in.

    OK, I've cut down to one DomainContext and sorted the servicecode.  I still have the error, only when deleteing from the CompanyProduct table. I can update the Company Table, but I'm not deleting a record from that table.

    Friday, February 26, 2010 11:48 AM
  •  I have to catch up with you since I'm currently working on several things. Let me try to reproduce the problem in my code and let you know.

    Friday, February 26, 2010 11:54 AM
  • OK, I had the same problem. After remove a company_product from company.Company_Products collection, then call Context.SubmitChanges, I would get this error (not server side error, because code has not reach the server yet):

    "The value of key member 'Company_Id' on an instance of an entity of type 'Company_Product' has changed. Entity key members cannot be changed".

    I searched some old post which suggest when removing a child record,  it need to be removed from the Context.Company_Products collection, not company.Company_Products collection.

    I have no idea why they design the code that way, and asked the question in this thread http://forums.silverlight.net/forums/t/162880.aspx Hope to get some answer from MS.

    Any way, here is the code that should work:

     private void CheckBox_Unchecked(object sender, RoutedEventArgs e)
    {
    try
    {
    CheckBox c = sender as CheckBox;
    Products p = c.DataContext as Products;
    Company com = this.dataForm1.CurrentItem as Company;
    CompanyProduct cp = com.CompanyProduct.SingleOrDefault(o => o.Product_ID == p.Product_ID);
    if (cp != null)
    com.CompanyProduct.Remove(cp);
                       YourDomainContext.CompanyProduct.Remove(cp);
    }
    catch (Exception ex)
    {

    MessageBox.Show(ex.Message, "Exception in CheckBox_Unchecked: ", MessageBoxButton.OK);
    }
    finally
    {

    }
    }

     

    Friday, February 26, 2010 3:39 PM
  • Sweet!

     A mighty big thanks for coaching me through this. I've learned a heck of a lot. This has cracked it. I think I might write it up as a complete solution from beginning to end. Someone else is bound to want to do a Many-Many table relationship. I'll be passing the credit on, of course! The only thing I never solved was getting the List of Checkboxes inside the dataform. It just never loaded! Not the end of the world. Something for another day!

    Friday, February 26, 2010 6:27 PM
  • Hi, I found a better way to do CheckBoxList. Use Style to make a CheckBoxList. Then get selected data from reading ListBox.SelectedItems (ListBox.SelectedItems is read-only property, we can not set binding in XAML)

     <Style x:Key="CheckBoxList" TargetType="ListBox">
                            <Setter Property="SelectionMode" Value="Multiple"/>
                            <Setter Property="BorderBrush" Value="{x:Null}" />
                            <Setter Property="BorderThickness" Value="0" />
                            <Setter Property="ItemContainerStyle">
                                <Setter.Value>
                                    <Style TargetType="ListBoxItem" >
                                        <Setter Property="Margin" Value="2" />
                                        <Setter Property="Template">
                                            <Setter.Value>
                                                <ControlTemplate TargetType="ListBoxItem">
                                                    <Border Background="Transparent">
                                                        <CheckBox IsHitTestVisible="False" IsChecked="{TemplateBinding IsSelected}">
                                                            <ContentPresenter />
                                                        </CheckBox>
                                                    </Border>
                                                </ControlTemplate>
                                            </Setter.Value>
                                        </Setter>
                                    </Style>
                                </Setter.Value>
                            </Setter>
                        </Style>

    <ListBox  Style="{StaticResource CheckBoxList}" ItemsSource="{Binding Data, ElementName=ProductDataSource}" DisplayMemberPath="Product_Name" >

          <ListBox.ItemsPanel>
                  <ItemsPanelTemplate>
                     <StackPanel Orientation="Horizontal"/>
                   </ItemsPanelTemplate>
          </ListBox.ItemsPanel>   

    </ListBox>

     

    Wednesday, April 07, 2010 2:11 PM
  • Ok. I can see the use of styles here, and it looks much better. But I'm being stupid. I cannot get the checkboxes checked. I assume I need to link into the onCurrentItemChanged event on the DataForm. But I'm puzzled how to get a list of the current CompanyProducts and marry that against the list of products. I'll then need to write a Save method, to cycle down the list of selected products and update the CompanyProduct table to reflect this.

     I've been at this all day and am having a Blank moment! Some help please. I think I know where I want to be, but not how to get there.

    Thursday, April 08, 2010 1:17 PM
  •  

    But I'm being stupid. I cannot get the checkboxes checked.

    You mean you can not get the initial population right?

     <ListBox x:Name="ProdList" Loaded="ProdList_Loaded" SelectionChanged="ProdList_SelectionChanged" Style="{StaticResource CheckBoxList}" ItemsSource="{Binding YourProductList}" DisplayMemberPath="Product_Name" >
                   <ListBox.ItemsPanel>
                    <ItemsPanelTemplate>
                    <StackPanel Orientation="Horizontal"/>
                   </ItemsPanelTemplate>
               </ListBox.ItemsPanel>                                         
      </ListBox>

    private void ProdList_Loaded(object sender, RoutedEventArgs e)
            {
                ListBox l = sender as ListBox;
                Company com = this.DF.CurrentItem as Company;
                foreach (Product p in ProdList.ItemsSource)
                {
                    Company_Product cp = com.Company_Product.SingleOrDefault(o => o.Product_Id == p.Product_Id);
                    if (cp != null)
                        ProdList.SelectedItems.Add(p);  // Add Company Product to the ProdList.SelectedItems (those will be checked)
                }
            }    

     

    Thursday, April 08, 2010 1:28 PM
  • Do you need an explicit Mode=TwoWay on the checkbox binding?

    Thursday, April 08, 2010 2:44 PM
  • Do you need an explicit Mode=TwoWay on the checkbox binding?

    No, by using Style to make the CheckBoxList, we do not really bind to the Checkbox. The Checkbox is only a visual representation of that row is selected or not. The ListBox.SelectedMode is set to Multi-Select in the Style. So we can select multi-rows. The only problem with ListBox is that the SelectedItems Property is a read only field, so we can not set binding to it. Otherwise, it should be even simpler.

     

     

    Thursday, April 08, 2010 2:54 PM
  • I added the Selection_Changed event handler as follows

     

            private void productList_SelectionChanged(object sender, SelectionChangedEventArgs e)
            {
                if (!UpdatingSelection) // Don't update if in the middle of setting
                {
                    TestDS ctx = this.Resources["TestDSContext"] as TestDS;
                    Company comp = this.companyForm.CurrentItem as Company;
                    if (comp != null)
                    {
                        foreach (Products p in this.productList.ItemsSource)
                        {
                            CompanyProduct cp = comp.CompanyProduct.SingleOrDefault(o => o.Product_ID == p.Product_ID);
                            if (this.productList.SelectedItems.Contains(p) && (cp == null))
                            { // Add a new company product
                                Debug.WriteLine("Add CompanyProduct");
                                cp = new CompanyProduct();
                                cp.Product_ID = p.Product_ID;
                                cp.Company_ID = comp.Company_ID;
                                comp.CompanyProduct.Add(cp);
                            }
                            else if (!this.productList.SelectedItems.Contains(p) && (cp != null))
                            { // Remove a company product
                                ctx.CompanyProducts.Remove(cp);
                            }
                        }
                    }
                }
    
     

    I found I needed a couple of changes to your CurrentItemChanged Event handler.

    One was to set an UpdatingSelection flag so that the SelectionChanged event didn't make changes when the CurrentItemChanged event was running. I was not sure whether to remove the event handler and then re-add it afterwards for go for the flag system I've used here. I think the flag system will be quicker but not sure if it is clearer or as esthetic.

    The second was to clear the selection before populating it.

            private void companyForm_CurrentItemChanged(object sender, EventArgs e)
            {
                Company comp = this.companyForm.CurrentItem as Company;
                
                if ((comp != null))
                {
                    UpdatingSelection = true;  // Prevent SelectionChanges updating 
                    productList.SelectedItems.Clear();
                    foreach (Products p in this.productList.ItemsSource)
                    {
                        CompanyProduct cp = comp.CompanyProduct.SingleOrDefault(o => o.Product_ID == p.Product_ID);
                        if (cp != null)
                            productList.SelectedItems.Add(p); // Add Company Product to the selected items list
                    }
                    UpdatingSelection = false; // Allow SelectionChanges updating
                }
            }
     
    Thursday, April 15, 2010 6:10 AM
  • I'm rather stuck with this too.

    I can fill my CheckedList in the DataForm and using the CheckBox_Loaded event the correct CheckBoxes get checked fine, but the edit, insert, update and delete doesn't work.

    There is so much useful information in this post, but it's quite difficult to piece it all together.

    Is it possible for someone to create a small testapplication using 2 tables with a many-to-many relationship, using a DataForm and a DataGrid?

    I feel like I'm very close to unraveling this mystery...

    Thanks

    Tuesday, August 24, 2010 8:45 AM
  • I can fill my CheckedList in the DataForm and using the CheckBox_Loaded event the correct CheckBoxes get checked fine, but the edit, insert, update and delete doesn't work.

    What do you mean insert/edit/update/delete doesn't work. Do you get RIA service error? If yes, check if you have Update/Delete/Insert function in your RIA service for the Entity you are trying to edit. You need to have those functions in order for the Entity to be editable.




    Tuesday, August 24, 2010 10:07 AM
  • I created this post just now: http://forums.silverlight.net/forums/t/197811.aspx

    Is it possible I can send my testapplication, so you can take a look?

    Tuesday, August 24, 2010 10:11 AM