Contact Me

Contact Us
For an informal discussion

Everyone's business is different. Our business is software - ask where we can help.
eMail: john at appsolo.com

Archive for July, 2010

RIA and the DB round trip – Synchronising Silverlight Client Controls – Problems solved

Friday, July 23rd, 2010

I’m exploring round trips using Silverlight 4 RIA and SQL Server. I have a complex screen that involves chaining dependencies between Silverlight List boxes and a combo box that belies the complexity of the normalised data model that exists in the SQL database.

Firstly the Keys Combo Box is populated with the Taxonomy Keys of a Particular Author (that has logged in previously). On Choosing a Key a set of Question sets are presented in a list box. On choosing a Question Set, the Questions in that set are retrieved VIA RIA from an SQL Database.These Questions may have images associated with them which are loaded from the local disk and passed up to the database or loaded from the database. Data flows up and down through the model and as can be seen there is a great deal of synchronisation between controls that needs to be triggered on the SL Client when data changes. The controls are populated or Bound (I think is what we are calling it now) using parameterised queries for the DDSs. The interface is shown below.

image thumb4 RIA and the DB round trip – Synchronising Silverlight Client Controls   Problems solved

Here’s an object dependency diagram for the form

 image thumb5 RIA and the DB round trip – Synchronising Silverlight Client Controls   Problems solved

Datasets (as they used to be called in ADO.NET – which I believe is still under there somewhere) or Collections based on DDSs in code or in XAML that are the result of Parameterised Queries are not updatable using Add for ICollectionView (parameter based) or AddNew for IEditableCollectionView (see forum discussion for Hint that this is a framework bug – which led me to Kyle McClellan’s Microsoft Blog which may be useful) which means you cannot add data objects directly to the DDS on the SL4 client and have the data propagate backwards through the EF to the Database. So you have to access the domain service functions directly  in code behind forms. I have found few patterns to be useful when calling functions in the LinqToEntitiesDomainService  to update the data in the database and then propagating the changes down to the client by refreshing the effected controls. First some background

The XAML code for the Combo Box

   1: <ComboBox Name="CboKeys" ItemsSource="{Binding ElementName=AuthorKeyDomainDataSource, Path=Data}" 

   2:                   SelectedValue="{Binding ElementName=AuthorKeyDomainDataSource, Path=Data.CurrentItem.TaxKeyID}" Grid.Row="0" Grid.Column="1" Width="111" 

   3:                   HorizontalAlignment="Right" VerticalAlignment="Top" Margin="10,10,120,10" SelectionChanged="CboKeys_SelectionChanged" 

   4:                   SelectedItem="{Binding Path=Data, Mode=OneWay, ElementName=AuthorKeyDomainDataSource}" 

   5:                   SelectedValuePath="TaxKeyID" />

   6:         

The XAML for the linked Domain Data Source

   1: <riaControls:DomainDataSource AutoLoad="True"  Height="0" 

   2:                                       LoadedData="AuthorkeyDomainDataSource_LoadedData" 

   3:                                       Name="AuthorKeyDomainDataSource" 

   4:                                       QueryName="GetTaxKeysByAuthorQuery" Width="0" Margin="69,26,69,24">

   5:             <riaControls:DomainDataSource.DomainContext>

   6:                 <my:TaxFullDbDom />

   7:             </riaControls:DomainDataSource.DomainContext>

   8:             <riaControls:DomainDataSource.QueryParameters>

   9:                 <riaControls:Parameter ParameterName="AuthorID" Value="{Binding Path=Data}" />

  10:             </riaControls:DomainDataSource.QueryParameters>

  11:         </riaControls:DomainDataSource>

Now it’s a good idea to set up a resource for a common LinqToEntitiesDomainService  my:TaxFullDbDom above <my:TaxFullDbDom x:Key="TaxContext"/>

Now to the lbxMasterNodes (or question set as it will appear to the user) List Box

   1: <ListBox Grid.Column="1" ItemsSource="{Binding ElementName=view_NodesByTaxKeyDomainDataSource, Path=Data}" 

   2:                  Height="100" HorizontalAlignment="Left" Margin="5,32,0,0" Name="lbxMasterNodes" VerticalAlignment="Top" 

   3:                  Width="328" Background="AntiqueWhite" Style="{StaticResource SimpleListBox}" 

   4:                  MinWidth="300" Grid.Row="1" BorderBrush="{StaticResource LightCream}">

   5:             <ListBox.ItemTemplate>

   6:                 <DataTemplate>

   7:                     <StackPanel Orientation="Horizontal">

   8:                         <TextBlock  Text="{Binding NodeID, Mode=TwoWay}" FontSize="12" Margin="5,0"></TextBlock>

   9:                         <TextBlock Text="{Binding MasterDescription, Mode=TwoWay}" ToolTipService.Placement="Mouse" 

  10:                                    ToolTipService.ToolTip="{Binding MasterDescription}" 

  11:                                    Margin="5,0" FontStyle="Italic" MinWidth="200"></TextBlock>

  12:                     </StackPanel>

  13:                 </DataTemplate>

  14:             </ListBox.ItemTemplate>

  15:         </ListBox>

  16:         

and the DDS and parameter query for this list box in XAML is

   1: <riaControls:DomainDataSource AutoLoad="False"  

   2:                                       Height="0" LoadedData="view_NodesByTaxKeyDomainDataSource_LoadedData" 

   3:                                       Name="view_NodesByTaxKeyDomainDataSource" 

   4:                                       QueryName="GetNodeMasterByKeyQuery" Width="0">

   5:             <riaControls:DomainDataSource.DomainContext>

   6:                 <my:TaxFullDbDom />

   7:             </riaControls:DomainDataSource.DomainContext>

   8:             <riaControls:DomainDataSource.QueryParameters>

   9:                 <riaControls:Parameter ParameterName="Tkey" Value="{Binding ElementName=CboKeys, Path=CboKeys.SelectedValue}" />

  10:             </riaControls:DomainDataSource.QueryParameters>

  11:         </riaControls:DomainDataSource>

Now to the code patterns….

1.  By (re)setting the Query parameter(s) and reloading the Domain Data Source using the Load function you can cause the Domain Data Source .

   1: protected override void OnNavigatedTo(NavigationEventArgs e)

   2:         {

   3:             if ((Author)((App)Application.Current).CurrentAuthor != null)

   4:             {

   5:                 Author a = (Author)((App)Application.Current).CurrentAuthor;

   6:                 this.AuthorKeyDomainDataSource.QueryParameters[0].Value = a.AuthorID;

   7:                 this.AuthorKeyDomainDataSource.Load();

   8:                 

   9:  

  10:             }

  11:         }      

 

if you want to retrieve the active Domain Context Object associated with the Page in code behind forms then you can get it from any of the DomainDataSources using the following lines as they all share the same Domain Context. But for good code design use the DDS that you are working with for the control you are handling.

   1: TaxFullDbDom dbCon = (TaxFullDbDom)nodeDomainDataSource.DomainContext;

then you can execute functions in the Domain Service that this Domain Context Object represents

   1: private void CmdnewQS_Click(object sender, RoutedEventArgs e)

   2:         {

   3:             TaxFullDbDom dbDom = (TaxFullDbDom)this.view_NodesByTaxKeyDomainDataSource.DomainContext;

   4:             TaxKey T = (TaxKey)CboKeys.SelectedItem;

   5:             dbDom.AddNewNodeMaster(T.TaxKeyID, ret =>

   6:             {

   7:                 view_NodesByTaxKeyDomainDataSource.QueryParameters[0].Value = T.TaxKeyID;

   8:                 view_NodesByTaxKeyDomainDataSource.Load();

   9:             }, null);

NOTE: the Asynchronous call to the Domain Service Function and we wait for it to finish before we reload the DDS).

2. The second Pattern that will update the SL client controls after an Asynchronous call is to change the selected index which causes a refresh of the underlying DDS and parameter based query . This would allow you to reposition in the ListBox/ComboBox if desired as well.

   1: private void CmdDelete_Click(object sender, RoutedEventArgs e)

   2:         {

   3:             if (lbxNodes.Items.Count > 0)

   4:             {

   5:                 Node CurrentNode = (Node)lbxNodes.SelectedItem;

   6:                 TaxFullDbDom dbTax = (TaxFullDbDom)nodeDomainDataSource.DomainContext;

   7:                 dbTax.DeleteNode(CurrentNode, OnReturn =>      // Commits Domain Context changes in function

   8:                 {

   9:                     int lastMaster = lbxMasterNodes.SelectedIndex;

  10:                     lbxMasterNodes.SelectedIndex = -1;

  11:                     lbxMasterNodes.SelectedIndex = lastMaster;

  12:                     lbxNodes.SelectedIndex = lbxNodes.Items.Count - 1;

  13:                 }, null);

  14:             }

  15:             else MessageBox.Show("No Question to Delete" );

  16:         }

 

Just to tie off here are the Domain Service Functions

   1: namespace tax_sil4_net35.Web

   2: {

   3:     public partial class TaxFullDbDom : LinqToEntitiesDomainService<db1084688_TaxonomyEntities>

   4:     { …

   5:  

   6:  

   7:  

   8: public IQueryable<TaxKey> GetTaxKeysByAuthor(int AuthorID)

   9:        {

  10:            return this.ObjectContext.TaxKeys.Where(k => k.TaxKeyAuthor == AuthorID).OrderBy(k => k.TaxKeyName);

  11:        }

  12:  

  13: [Invoke]

  14: public  void AddNewNodeMaster(int K)

  15: {

  16:     int CurrentMax = GetMaxMasterNodeID(K);

  17:     this.ObjectContext.NodeMasters.AddObject(new NodeMaster() { NodeID = CurrentMax + 1, TaxKeyID = K, MasterDescription = "Question Set Description to be filled" });

  18:     // When you add a new MasterNode you need to add a default node and Arc

  19:     this.ObjectContext.Arcs.AddObject(new Arc() { ArcDescription = "Not Set", ArcLabel = "Not Set" });

  20:     int NextArcId = this.GetArcMax(); // have to check for nullable otherwise it will throw an error is no arcs at all in DB

  21:     this.ObjectContext.Nodes.AddObject(new Node() { NodeID = CurrentMax + 1, ArcID = NextArcId, TaxKeyID = K });

  22:     this.ObjectContext.SaveChanges();

  23: } 

  24:  

  25:  

  26:  

  27: [Invoke]

  28:         public void InsertNodeArc(NodeMaster nMaster)

  29:         {

  30:             //Have to deal with the case were user has deleted all the questions in a question set

  31:             // and the case were there are Qusestions 

  32:                 this.ObjectContext.Arcs.AddObject(new Arc() { ArcDescription = "Not Set", ArcLabel = "Not Set" });

  33:                 this.ObjectContext.SaveChanges();

  34:                 int NextArcId = this.ObjectContext.Arcs.Max(a => a.ArcID);

  35:                 this.ObjectContext.Nodes.AddObject(new Node() { NodeID = nMaster.NodeID, ArcID = NextArcId, TaxKeyID = nMaster.TaxKeyID });

  36:                 this.ObjectContext.SaveChanges();

  37:         }

 

In this app the amount of data being shipped up and down and in focus is kept to a minimum. This is especially important if you are dealing with collections of images as we are in this project.

I think DDSs in XAML are good for getting a solution up and going quickly, especially as touted in all the business app examples I’ve seen on the NET to date. But when complexity arises then, as with its predecessor the Data Control, you have to turn to code behind forms. RIA and EF thrown into the works obviously complicates matters, but its getting there I think. I remember first seeing data controls and the idea of drag and drop onto a form in VB6, and thinking now that’s nice and then seeing it all dissolve when you took the idea out of the norm. Lets hope DDSs fair better in SL5?

Hope this post helps some folk struggling with the complexities of RIA and Entity framework as well as the Asynchronous nature RIA from Silverlight or provides some food for thought at least.

Any questions or comments you can post them here.

Assigning users to a SQL database

Thursday, July 22nd, 2010

Filing this under ‘embarrassing errors’ heading, I’m hopeful that others have stumbled upon the same problem and so view this as a public safety broadcast-like post. In the current project, we’re (too regularly) updating our data model and this means dropping and re-scripting the database. The last step is to add a user with privileges to work on the database. And it’s here that 30 minutes of my life were needlessly wasted so listen up!

image thumb3 Assigning users to a SQL databaseHaving dropped the old database (called, say, dbOne), the user login previously associated (as dbo) still naturally exists – and maintains its dbo status on that (now gone) database. After running the script to re-instate the new version of the database, the user (we call him tax_user) re-asserts its dbo rights on the database. However, when you try to add that user as a ‘New User’ to dbOne I got an error:

The login already has an account under a different user name.

 

The rationale behind the error message is that that user (tax_user) is already associated with the database. It isn’t obvious that he is since he doesn’t appear in the list of users in the Security folder for that database. When you take a look at tax_user you may see that the default database for him is dbOne and you may be forgiven for thinking that all you need to do is change this. However, it isn’t enough – you have to delve into the User Mapping section and remove the user as dbo on the dbOne. Of course, it might be just easier to recreate tax_user. In any case, once you’re removed any reference to dbOne from tax_user, you’re good to add tax_user as a ‘New User’ for dbOne.

Now go forth and sin no more!

RIA Services – Nullable results and aggregates and a side order of RIA services structure and methodology

Monday, July 19th, 2010

if you are using a aggregate function in SQL for Entity Framework in the middle layer of RIA services Domain Service then the possibility that the projection query or an Object Context collection that you are using can return a nullable causes an issue for the aggregate queries that need to use Int32 return types only. Casting to int? will not help. A way around this is to check the count property of the of the generic collection returned and then take appropriate action. Here is the relevant part of the Data model behind all this

image thumb RIA Services   Nullable results and aggregates and a side order of RIA services structure and methodology

 

Case 1 based on executed EF Query

   1: [Invoke]

   2:         public int GetMaxMasterNodeID(int K)

   3:         { 

   4:             if(this.ObjectContext.NodeMasters.Where(m => m.TaxKeyID == K).Count<NodeMaster>() == 0 )

   5:                 return 0;

   6:             else return this.ObjectContext.NodeMasters.Where(m => m.TaxKeyID == K).Max(m => m.NodeID);

   7:         }

 

Case 2 based on possible empty collection in the Context Object of the Linq To Entities Domain Service (very similar)

   1: [Invoke]

   2: public int GetArcMax()

   3: {

   4:     if (this.ObjectContext.Arcs.Count<Arc>() == 0)

   5:         return 0;

   6:          else return this.ObjectContext.Arcs.Max(a => a.ArcID);

   7: }

 

The reason for doing this in our case here at Appsolo would be to programmatically create entities that are represented as a collection of tables for normalisation reasons while linking entities in the database as is done in the  following piece of code…

 

   1: [Invoke]

   2: public  void AddNewNodeMaster(int K)

   3: {            int CurrentMax = GetMaxMasterNodeID(K);

   4:              this.ObjectContext.NodeMasters.AddObject(new NodeMaster() { NodeID = CurrentMax + 1, TaxKeyID = K, MasterDescription = "Question Set Description to be filled" });

   5:             // When you add a new MasterNode you need to add a default node and Arc

   6:     this.ObjectContext.Arcs.AddObject(new Arc() { ArcDescription = "Not Set", ArcLabel = "Not Set" });

   7:     int NextArcId = this.GetArcMax(); // have to check for nullable otherwise it will throw an error is no arcs at all in DB

   8:     this.ObjectContext.Nodes.AddObject(new Node() { NodeID = CurrentMax + 1, ArcID = NextArcId, TaxKeyID = K });

   9:     this.ObjectContext.SaveChanges();

  10: }

 

SIDE TRACK

Of course you could do all or parts of this as a stored procedure(s) in the Database but “that my dear Doogle  is an ecumenical matter” as Father Ted would say.

If you do it as sprocs then you would have to use the Model Browser in the Entity Domain Model which I would call the backend of the Middle Tier/Layer of RIA services

 image thumb1 RIA Services   Nullable results and aggregates and a side order of RIA services structure and methodology

 

to include the sproc in the EDM and then flush forward the changes through the middle layer. This is an approach that I have taken previously. But I think this approach is more cumbersome (trackback 1). It also probably depends on whether you are more comfortable as a programmer or a DBA? Bottom line as always is that it all has to hang together some way with some level consistency of method and design pattern, otherwise you will GO MAAAAD! But sometimes, due to inadequacies in the RIA/Silverlight programming model behaviour( hey nothing’s perfect), you can be forced to work in the database side and work forward from there.

I may be inventing my own terminology here or re-inventing for RIA, (feel free to comment – maybe MS have their own terminology for what’s going on? – Possibly trackback 2), but as I see it RIA has a backend (Entity Domain Model and associated cs code) to communicate between and handle the database traffic and a front end to communicate between the client application using Domain Services and the Entity Framework with linkage between the EF and EDM handle . The business logic mentioned in trackback 2 resides mostly in the form of EF management code as good chunks of the the EDM code are hidden and probably should remain so.

image thumb2 RIA Services   Nullable results and aggregates and a side order of RIA services structure and methodology

One the front layer of RIA is also the hidden client side code (if you select a Domain Service function in the client side code in VS2010 and hit F12 you go to the client side (hidden and generated) code) to call the Domain Service EF based code that you have writen (what another layer of abstraction!! yes.)

   1: /// <summary>

   2:         /// Asynchronously invokes the 'AddNewNodeMaster' method of the domain service.

   3:         /// </summary>

   4:         /// <param name="K">The value for the 'K' parameter of this action.</param>

   5:         /// <param name="callback">Callback to invoke when the operation completes.</param>

   6:         /// <param name="userState">Value to pass to the callback.  It can be <c>null</c>.</param>

   7:         /// <returns>An operation instance that can be used to manage the asynchronous request.</returns>

   8:         public InvokeOperation AddNewNodeMaster(int K, Action<InvokeOperation> callback, object userState)

   9:         {

  10:             Dictionary<string, object> parameters = new Dictionary<string, object>();

  11:             parameters.Add("K", K);

  12:             this.ValidateMethod("AddNewNodeMaster", parameters);

  13:             return this.InvokeOperation("AddNewNodeMaster", typeof(void), parameters, true, callback, userState);

  14:         }

  15:         

  16:         /// <summary>

  17:         /// Asynchronously invokes the 'AddNewNodeMaster' method of the domain service.

  18:         /// </summary>

  19:         /// <param name="K">The value for the 'K' parameter of this action.</param>

  20:         /// <returns>An operation instance that can be used to manage the asynchronous request.</returns>

  21:         public InvokeOperation AddNewNodeMaster(int K)

  22:         {

  23:             Dictionary<string, object> parameters = new Dictionary<string, object>();

  24:             parameters.Add("K", K);

  25:             this.ValidateMethod("AddNewNodeMaster", parameters);

  26:             return this.InvokeOperation("AddNewNodeMaster", typeof(void), parameters, true, null, null);

  27:         }

 

P.

1 pages

latest news

Running a web site hosting SL4 OOB on local host using II express

Posted on Saturday, 4th February, 2012

Free Tools available:
IIS Express free download
Visual Studio 2010 express free download (there might be a licensing issue here)
or Visual Web developer express 2010
SQL Server 2008  Express R2 free download (2010 version is available in RC0 version)
Making sure that IIS Express is installed you can develop against it
So you create your web site and client [...]

Testimonials

Excellent design skills

Posted on Sunday, 2nd May, 2010

We at Taxonomy.ie are happy to be associated with Appsolo and look forward to further work together.

follow me

twitter facebook delicious

AppsoloLtd. VAT No. IE97548691 - Copyright © 2010.