none
How to Query WCF Data Service from Silverlight 4 Model

    Question

  • Hi,

    Hope everyone has had a great weekend!

    I am following a tutorial online to learn more about using Silverlight 4 with WCF Data Services but something is not right in the code and I cannot compile the application.

    This bit of code is not working:

    qry.BeginExecute(r =>
                    {
                        var results = qry.EndExcute(r);
                        if (GamesLoadedCompleted != null)
                        {
                            GamesLoadedCompleted(this,
                                new GamesArgs(results);
                        }
                    }, null);

    Has WCF Data Services changed? If so, could someone please shed some light on how I can change my code so that it works?

    P.S. I have tested the WCF data service - Games.svc - in the browser and it is working fine. I am using an EF.

    THE MODEL
    -----------------

    public interface IGamesModel
    {
            event EventHandler<GamesArgs> GamesLoadedCompleted;
            void GetGamesByGenreAsync(string genre);
    }

    public class GamesModel : IGamesModel
    {
           
            // The model will contain the context
            XBoxGames2005Entities ctx = new XBoxGames2005Entities(
                new Uri("Games.svc", UriKind.Relative));
     
            public event EventHandler<GamesArgs> GamesLoadedCompleted;

            public void GetGamesByGenreAsync(string genre)
            {

                var qry = (from g in ctx.Games
                          where g.Genre1.Name == genre
                          orderby g.ReleaseDate descending
                          select g);

                qry.BeginExecute(r =>
                    {
                        var results = qry.EndExcute(r);
                        if (GamesLoadedCompleted != null)
                        {
                            GamesLoadedCompleted(this,
                                new GamesArgs(results);
                        }
                    }, null);
            }

        }

     GamesArgs
    ------------------

    public class GamesArgs : EventArgs
    {

            Exception ex = null;
            IEnumerable<Game> theGames = null;

            public GamesArgs(IEnumerable<Game> games)
            {
                theGames = games;
            }

            public GamesArgs(Exception ex)
            {
                this.ex = ex;
            }

            public Exception Error { get{return ex;} }
            public IEnumerable<Game> Games { get { return theGames;} }
    }

    Cheers

    P

     

     

    Monday, November 08, 2010 11:17 AM

Answers

  • If you just want to load the Games list with WCF DataService, here is the code:

    DataServiceCollection<Games> GameList = new DataServiceCollection<Games>();

    GameList.LoadCompleted+=new EventHandler<LoadCompletedEventArgs>(GameList_LoadCompleted);

    var qs = Context.Games.Expand("Genre1").Where(g => g.Genre1.Name==genre).OrderByDescending(g.ReleaseData) as DataServiceQuery<Games>;

    GameList.LoadAsync(qs);


    void GameList_LoadCompleted(object sender, LoadCompletedEventArgs e)

    {

          ...

    }


    Monday, November 08, 2010 11:56 AM
  • The result would be the GameList.  After load is complete, the GameList should have all the data you need.

    I would just use the GameList directly instead of using it to populate theGames ObeservableCollection. You certainly can do that, but I don't see why you need to.

    If you bind the GameList directly to your Control,  you can edit data directly and save the data back to server by calling Context.BeginSaveChanges when you need to.  Otherwise, you have to update GemeList with the data in the ObservableCollection before you send it back to Server.

    I don't know why you need write a Model class? The Model should be the Data which is the Games class. You can just load the data in the ViewModel then you are done.  

    Here is what I would do:

    public class GamesListViewModel  : INotifyPropertyChanged
     {

            GamesEntities Context = new GamesEntities(new Uri(Application.Current.Host.Source, "../YourService.svc"));

            public DataServiceCollection<Game> GameList { get; private set; } 

            string _genre;

            public string Genre

           {

              get{return _genre;}

              set{ _genre = value;

                      NotifyPropertyChange("Genre");

            }        

            public GamesListViewModel()

             {

                     GameList = new DataServiceCollection<Game>();

                     GameList.LoadCompleted+=new EventHandler<LoadCompletedEventArgs>(GameList_LoadCompleted);                                this.PropertyChanged += (s, e) =>

                     {

                            if (e.PropertyName == "Genre")

                           {

                                 Load();  // Reload when Genre is changed

                            }

                      }

            }

            void Load()  // Call this when Genere changed:

             {

                   var qs = Context.Games.Expand("Genre1").Where(g =>  g.Genre1.Name==this.Genre).OrderByDescending(g.ReleaseData) as DataServiceQuery<Games>;

                 GameList.LoadAsync(qs);

             }

             void LoadCompleted(object sender, LoadCompletedEventArgs e)
            {
                if (e.Error != null)
                {             
                    MessageBox.Show(e.Error.Message);
                }

                else

                // You can do anything with the list here
               
            }

    }

     }



     


    Monday, November 08, 2010 1:44 PM
  • So If bind the GamesList directly to the ListBox control, I will be able to edit it and save it back to the database?

    Yes. To save the changes, write a SaveCommand, use the following code for this command:

    Context.BeginSaveChanges(SaveChangesOptions.Batch, result =>
                {
                                   
                    try
                    {
                        Context.EndSaveChanges(result);                                
                    }
                    catch (Exception ex)
                    {
                        MessageBox.Show(ex.Message);
                    }             
                }, Context);


    Monday, November 08, 2010 2:59 PM
  • 1) "Expand" in WCF DataServcie is like the "Include" in RIA service.

    Expand will bring back the related entity. If your Games contains Genre_ID field, it should also contains a Navigation Property Genre (I saw you use Genre1, so I assume that would be your Property name for this Navigation Property).  Expland will bring this object back (in case you need to display the Genre.Name), not just Genre_ID. 

    Otherwise you need to have another call to load Genre object by the given Genre_ID.

    2) You can change the PropertyName in the EF model to whatever you want. Do it in the EF designer.

    3) If Ratings is also a Child list of Games, you can use another Expand to include the Ratings. As long as they are direct children of the Main object, you can use Expand to include them.

    Context.Games.Expand("Genre1").Expand("Ratings")....








    Monday, November 08, 2010 3:28 PM
  • What I really would like to do is to update Genre, Games and Ratings on the client and then submit the changes in one go.

    That is exactly what the code I showed you should do: Using Update Opetion = Batch, you save all the changes in one call.

    2 - Does WCF Data Services do Change Tracking and Unit of work like the WCF Ria Services? i.e. if I change the Genre/Rating of a Game would the server side know how to save the changes to the database?


    Try it you will see.




    Monday, November 08, 2010 4:03 PM
  • Yes. that should work.

    If you want to delete a Genre or Delete a Game, make sure you set the Delete Rule in DB. You might get DB error when the Genre already has Games or the Game already has Ratings if you didn't set Cascade Delete in DB.   But that is a DB related topic.  You need to handle this kind of things according to your business rules.  That's not the responsibility of EF, WCF DataService, or Silverlight.



    Monday, November 08, 2010 4:51 PM

All replies

  • If you just want to load the Games list with WCF DataService, here is the code:

    DataServiceCollection<Games> GameList = new DataServiceCollection<Games>();

    GameList.LoadCompleted+=new EventHandler<LoadCompletedEventArgs>(GameList_LoadCompleted);

    var qs = Context.Games.Expand("Genre1").Where(g => g.Genre1.Name==genre).OrderByDescending(g.ReleaseData) as DataServiceQuery<Games>;

    GameList.LoadAsync(qs);


    void GameList_LoadCompleted(object sender, LoadCompletedEventArgs e)

    {

          ...

    }


    Monday, November 08, 2010 11:56 AM
  • Hi sladapter,

    I have tried to use your code in the model but the OrderByDescending(g.ReleaseData) is not working.

    "The g does not exist in the current context."

    Could you please let me know how to fix it?

    Also, is it possible to use the GamesArgs class - public event EventHandler<GamesArgs> GamesLoadedCompleted instead of the LoadCompletedEventArgs? Or what is the advantage of using the LoadCompletedEventArgs?

     

    public class GamesModel : IGamesModel
    {
           
            XBoxGames2005Entities ctx = new XBoxGames2005Entities(
                new Uri("Games.svc", UriKind.Relative));
     
            public event EventHandler<GamesArgs> GamesLoadedCompleted;

            public void GetGamesByGenreAsync(string genre)
            {

                DataServiceCollection<Game> GameList = new DataServiceCollection<Game>();
                GameList.LoadCompleted += new EventHandler<LoadCompletedEventArgs>(GameList_LoadCompleted);
                var qs = ctx.Games.Expand("Genre1").Where(g => g.Genre1.Name ==   genre).OrderByDescending(g.ReleaseData) as DataServiceQuery<Game>;

                GameList.LoadAsync(qs);


            }

    }

    cheers

    P

     

     

    Monday, November 08, 2010 12:15 PM
  • Sorry, that should be:

    OrderByDescending(g=>g.ReleaseData)

    Monday, November 08, 2010 12:28 PM
  • Sorry,

    I forgot to ask how I return the results to the ViewModel?

    void GameList_LoadCompleted(object sender, LoadCompletedEventArgs e)

    {

          .

    }

    This is the view model

     public class GamesListViewModel
     {
            ObservableCollection<Game> theGames = new ObservableCollection<Game>();

          
            IGamesModel theModel = null;

           
            public GamesListViewModel(IGamesModel model)
            {
                theModel = model;
                theModel.GamesLoadedCompleted += new EventHandler<GamesArgs>(theModel_GamesLoadedCompleted);
            }

            void theModel_GamesLoadedCompleted(object sender, GamesArgs e)
            {
                theGames.Clear();
                foreach (var g in e.Games) theGames.Add(g);
            }

            public void LoadShooterGames()
            {
                theModel.LoadGamesByGenreAsync("Shooter");
            }

            public ObservableCollection<Game> Games
            {
                get { return theGames; }
               
            }
     }

     

    Monday, November 08, 2010 12:55 PM
  • as you can see the View Model is using the GamesArgs class.

    theModel.GamesLoadedCompleted += new EventHandler<GamesArgs>(theModel_GamesLoadedCompleted);

    Thanks

    P

     

    Monday, November 08, 2010 12:57 PM
  • The result would be the GameList.  After load is complete, the GameList should have all the data you need.

    I would just use the GameList directly instead of using it to populate theGames ObeservableCollection. You certainly can do that, but I don't see why you need to.

    If you bind the GameList directly to your Control,  you can edit data directly and save the data back to server by calling Context.BeginSaveChanges when you need to.  Otherwise, you have to update GemeList with the data in the ObservableCollection before you send it back to Server.

    I don't know why you need write a Model class? The Model should be the Data which is the Games class. You can just load the data in the ViewModel then you are done.  

    Here is what I would do:

    public class GamesListViewModel  : INotifyPropertyChanged
     {

            GamesEntities Context = new GamesEntities(new Uri(Application.Current.Host.Source, "../YourService.svc"));

            public DataServiceCollection<Game> GameList { get; private set; } 

            string _genre;

            public string Genre

           {

              get{return _genre;}

              set{ _genre = value;

                      NotifyPropertyChange("Genre");

            }        

            public GamesListViewModel()

             {

                     GameList = new DataServiceCollection<Game>();

                     GameList.LoadCompleted+=new EventHandler<LoadCompletedEventArgs>(GameList_LoadCompleted);                                this.PropertyChanged += (s, e) =>

                     {

                            if (e.PropertyName == "Genre")

                           {

                                 Load();  // Reload when Genre is changed

                            }

                      }

            }

            void Load()  // Call this when Genere changed:

             {

                   var qs = Context.Games.Expand("Genre1").Where(g =>  g.Genre1.Name==this.Genre).OrderByDescending(g.ReleaseData) as DataServiceQuery<Games>;

                 GameList.LoadAsync(qs);

             }

             void LoadCompleted(object sender, LoadCompletedEventArgs e)
            {
                if (e.Error != null)
                {             
                    MessageBox.Show(e.Error.Message);
                }

                else

                // You can do anything with the list here
               
            }

    }

     }



     


    Monday, November 08, 2010 1:44 PM
  • sladapter,

    Many thanks for your help and source code.

    There reason why I was using a model is to be able to use a mock later on totest the app.

    I am just following the tutorial so that I can learn how to use WCF Data Services.

    Your suggestions are very good. Thanks for that.

    So If bind the GamesList directly to the ListBox control, I will be able to edit it and save it back to the database?

    That is fantastic!!

    Would I have to add a Save command in the View Model for that? Could you please shed some light on this for completeness?

    Thanks a lot.

    This is what my GameList.xaml looks like
    ------------------------------------------------------------------

    <Grid x:Name="LayoutRoot" Background="White">
            <Grid.ColumnDefinitions>
                <ColumnDefinition/>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>
            <ListBox x:Name="theList" DisplayMemberPath="Name"
                     ItemsSource="{Binding Games}"/>
           

           <!-- I expect the stack panel will have an individual game
            We wnat to use data binding and not bind in code. So we use ElementName
            This will set the DataContext of the container to whatever the user has selecte
            in the list box-->
           

               <StackPanel Grid.Column="1"
                        DataContext="{Binding SelectedItem, ElementName= theList}">
                <TextBlock>Name</TextBlock>
                <TextBox Text="{Binding Name, Mode=TwoWay}"/>
                <TextBlock>Description</TextBlock>
                <TextBox Text="{Binding Description, Mode=TwoWay}"/>
            </StackPanel>
    </Grid>  

    Code Behind
    ------------------------------------

    public partial class GamesList : UserControl
     {
            public GamesList()
            {
                InitializeComponent();
            }
     }

    And this is the MainPage.xaml
    --------------------------------------------------------

    <Grid x:Name="LayoutRoot" Background="White">
            <views:GamesList/>
    </Grid>

     

    Code behind
    --------------------------

    public partial class MainPage : UserControl
    {
            public MainPage()
            {
                InitializeComponent();

                // We are not mocking therefore we pass the real model.
                var vm = new GamesListViewModel(
                    new GamesModel());
                // For now we need to call it from here
                vm.LoadShooterGames();

                // Set data context of entire view to view model.
                this.DataContext = vm;

            }
    }

    Monday, November 08, 2010 2:14 PM
  • So If bind the GamesList directly to the ListBox control, I will be able to edit it and save it back to the database?

    Yes. To save the changes, write a SaveCommand, use the following code for this command:

    Context.BeginSaveChanges(SaveChangesOptions.Batch, result =>
                {
                                   
                    try
                    {
                        Context.EndSaveChanges(result);                                
                    }
                    catch (Exception ex)
                    {
                        MessageBox.Show(ex.Message);
                    }             
                }, Context);


    Monday, November 08, 2010 2:59 PM
  • Thanks again.

    1 - In your service query you used Expand. Could you please explain what that does?

    var qs = Context.Games.Expand("Genre1").Where(g => g.Genre1.Name==genre).OrderByDescending(g.ReleaseData) as DataServiceQuery<Games>;

    2 - Also, when I created the EF for some reason it named the navigation properties Genre1 and Ratings1. Do you know what I have to do to give it a more meaningful name such as GenreGames and RatingsGames. I have tried to edit the navigation properties name but then I broke the .edmx.

    3 - Also, if I wanted to return the games with its genre and ratings associtations how would the query differ?

    When I use Ria Services I do something like this:

    riaContext.Games.Include("Genre").Include("Ratings");

    Cheers

    P

     


     

    Monday, November 08, 2010 3:12 PM
  • 1) "Expand" in WCF DataServcie is like the "Include" in RIA service.

    Expand will bring back the related entity. If your Games contains Genre_ID field, it should also contains a Navigation Property Genre (I saw you use Genre1, so I assume that would be your Property name for this Navigation Property).  Expland will bring this object back (in case you need to display the Genre.Name), not just Genre_ID. 

    Otherwise you need to have another call to load Genre object by the given Genre_ID.

    2) You can change the PropertyName in the EF model to whatever you want. Do it in the EF designer.

    3) If Ratings is also a Child list of Games, you can use another Expand to include the Ratings. As long as they are direct children of the Main object, you can use Expand to include them.

    Context.Games.Expand("Genre1").Expand("Ratings")....








    Monday, November 08, 2010 3:28 PM
  • Thanks Smile

    I see.

    That is the relationship of the EF. So the Games is the many side of the relationship.

    Genre ---> Games <---- Rating

    Genre
    -------------
    GenreID
    Name
    +++
    Navigation Properties
    Games

    Games
    ----------------
    GameID
    Name
    Description
    ReleaseDate
    Genre
    Rating
    +++
    Navigation Properties:
    Genre1
    Rating!

    Rating
    -------------
    RatingID
    Name
    +++
    Navigation Properties
    Games

    What I really would like to do is to update Genre, Games and Ratings on the client and then submit the changes in one go.

    1 - Would that work in WCF Data Services?

    2 - Does WCF Data Services do Change Tracking and Unit of work like the WCF Ria Services? i.e. if I change the Genre/Rating of a Game would the server side know how to save the changes to the database?

    Cheers

    P

     

     

    Monday, November 08, 2010 3:52 PM
  • What I really would like to do is to update Genre, Games and Ratings on the client and then submit the changes in one go.

    That is exactly what the code I showed you should do: Using Update Opetion = Batch, you save all the changes in one call.

    2 - Does WCF Data Services do Change Tracking and Unit of work like the WCF Ria Services? i.e. if I change the Genre/Rating of a Game would the server side know how to save the changes to the database?


    Try it you will see.




    Monday, November 08, 2010 4:03 PM
  • Thanks

    Monday, November 08, 2010 4:37 PM
  • I am very sorry sladapter. I thought my last post was the last one but FrownI forgot to ask one question.

    What I meant to ask before is if I add new Rating and Genre or delete them how would the code look like to update the database?

    Would I have to do something like this:

    Games.Genre.add(genre)

    Games.Rating.add(rating)  

    Cheers

    C

    Monday, November 08, 2010 4:44 PM
  • Yes. that should work.

    If you want to delete a Genre or Delete a Game, make sure you set the Delete Rule in DB. You might get DB error when the Genre already has Games or the Game already has Ratings if you didn't set Cascade Delete in DB.   But that is a DB related topic.  You need to handle this kind of things according to your business rules.  That's not the responsibility of EF, WCF DataService, or Silverlight.



    Monday, November 08, 2010 4:51 PM