Tuesday, February 9, 2010

WCF RIA Services – Short Notes – Refreshing data from the server

By default if an Entity arriving from the server has the same Identity has one already in the DomainContexts at the client it is discarded. This behavior can be changed by calling the Load method of the DomainContext giving a MergeOption. It can be:

  1. MergeOption.OverwriteCurrentValues – What is comming from the server overrides the changes on the client.
  2. MergeOption.KeepCurrentValues – What is on the client remains the same what comes from the server is discarded.
  3. MergeOption.KeepChanges – Changes that where made on the client are maintained and everything else is replaced from what is on the server.

Have fun,

WCF RIA Services – Short Notes – Transactions

You can easily integrate transactions in a DomainService by overloading the Submit method on the service and creating the transaction scope there:

   1: using (var tx = new TransactionScope( TransactionScopeOption.Required, 
   2:     new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted })) 
   3: { 
   4:     base.Submit(changeSet); 
   5:     tx.Complete(); 
   6: }

The Insert*, Update* and Delete* methods will be called one by one inside the given scope.

Have fun,

WCF RIA Services – Short Notes – Custom logic on solving concurrency errors

DomainServices have solving concurrency errors as a first class scenario. If you want to override the framework default logic you should.

Have an update operation for the entity.

Have a method that handles the concurrency and that:

  1. Begins with the word Resolve plus the name of the entity.
  2. Follows the signature template:
   1: public bool ResolveEntityA(EntityA current, EntityA original, EntityA store, bool deleteOperation)

Have fun,

WCF RIA Services – Short Notes – Server Side sequence of operations

When the domain service is processing a changeset the sequence of operation is:

Submit – This is the entry point for a submit operation.
AuthorizeChangeSet – In this point the entries in the changeset should be checked against the authorization rules.
ValidateChangeSet – The entries in the changeset should be checked against business rules.
ExecuteChangeSet – The business methods in the domain service are called from this method one by one.
PersistChangeSet – The changeset is persisted.
ResolveChangeSet – If there are any concurrency conflicts they are resolved here.

Kind Regards,

Pedro

WCF RIA Services – Advanced Topics – Fully supporting inheritance

One of the limitations on WCF RIA Services is not support inheritance though making EF inheritance feature impossible to use with LinqToEntities. When you try to set a property of a base class you get this error:

Entity type 'Square' does not contain a public property named 'ShapeId'.

I found that this error is actually a problem with Silverlight reflection. Because of the new restrictions on reflection the change tracking code is unable to get the value of the property. But there is a workaround for it. Let’s illustrate the problem. I have this object model:

Overview

There are several things you have to do to make this model work. First you need to put the correct annotations on your model.

   1: public class RootEntity
   2: {
   3:     [Key]
   4:     public Guid Id
   5:     {
   6:         get;
   7:         set;
   8:     }
   9:  
  10:     public Guid ChildId
  11:     {
  12:         get;
  13:         set;
  14:     }
  15:  
  16:     public string Name
  17:     {
  18:         get;
  19:         set;
  20:     }
  21:  
  22:     [Composition]
  23:     [Association("RootEntity_ChildEntity", "ChildId", "Id")]
  24:     public ChildEntity Child
  25:     {
  26:         get;
  27:         set;
  28:     }       
  29: }
  30:  
  31: public class ChildEntity
  32: {
  33:     public ChildEntity()
  34:     {
  35:         this.SubChildren = new List<SubChildEntity>();
  36:     }
  37:  
  38:     [Key]
  39:     public Guid Id
  40:     {
  41:         get;
  42:         set;
  43:     }       
  44:  
  45:     [Composition]
  46:     [Association("ChildEntity_SubChildEntity", "Id", "ChildEntityId")]
  47:     public IList<SubChildEntity> SubChildren
  48:     {
  49:         get;
  50:         private set;
  51:     }
  52: }
  53:  
  54: public class SubChildEntity
  55: {
  56:     public SubChildEntity()
  57:     {            
  58:     }
  59:  
  60:     [Key]
  61:     public Guid Id
  62:     {
  63:         get;
  64:         set;
  65:     }
  66:  
  67:     public Guid ChildEntityId
  68:     {
  69:         get;
  70:         set;
  71:     }
  72:  
  73:     [Include]
  74:     [Association("SubChildEntity_Shape", "Id", "SubChildEntityId")]
  75:     public Shape Shape
  76:     {
  77:         get;
  78:         set;
  79:     }
  80: }
  81:  
  82: [KnownType(typeof(Square))]
  83: [KnownType(typeof(Circle))]
  84: public abstract class Shape
  85: {
  86:     [Key]
  87:     public Guid ShapeId
  88:     {
  89:         get;
  90:         set;
  91:     }
  92:  
  93:     public Guid SubChildEntityId
  94:     {
  95:         get;
  96:         set;
  97:     }
  98:  
  99:     public int CenterX
 100:     {
 101:         get;
 102:         set;
 103:     }
 104:  
 105:     public int CenterY
 106:     {
 107:         get;
 108:         set;
 109:     }
 110: }
 111:  
 112: public class Circle : Shape
 113: {
 114:     public int Radius
 115:     {
 116:         get;
 117:         set;
 118:     }
 119: }
 120:  
 121: public class Square : Shape
 122: {
 123:     public int RoudingRadius
 124:     {
 125:         get;
 126:         set;
 127:     }
 128:  
 129:     public bool RoundCorners
 130:     {
 131:         get;
 132:         set;
 133:     }
 134: }

You have to notice the Composition attribute and the KnownType attribute.

Second you have to put methods to the CUD the detail objects on the server. This will force the EntitySets on the DomainContext to allow all kinds of changes. Also notice the fixup I have to do on the Ids that are involved in the associations in the InsertRootEntityMethod.

   1: [EnableClientAccess]
   2: public class RootEntityService : DomainService
   3: {
   4:     private static List<RootEntity> dataSource = new List<RootEntity>();
   5:  
   6:     public IQueryable<RootEntity> GetRootEntities()
   7:     {
   8:         var q = from r in dataSource
   9:                 //from src in r.Child.SubChildren
  10:                 select r;
  11:  
  12:         return q.AsQueryable();
  13:     }
  14:  
  15:     public IQueryable<ChildEntity> GetChildEntities(Guid parentId)
  16:     {
  17:         var q = from r in dataSource
  18:                 where r.Id == parentId
  19:                 select r.Child;
  20:  
  21:         return q.AsQueryable();
  22:     }
  23:  
  24:     public IQueryable<SubChildEntity> GetSubChildren(Guid parentId)
  25:     {
  26:         var q = from r in dataSource
  27:                 from s in r.Child.SubChildren
  28:                 where r.Id == parentId
  29:                 select s;
  30:  
  31:         return q.AsQueryable();
  32:     }
  33:  
  34:     public IQueryable<Shape> GetShapes(Guid parentId)
  35:     {
  36:         var q = from r in dataSource
  37:                 from sc in r.Child.SubChildren                    
  38:                 where r.Id == parentId
  39:                 select sc.Shape;
  40:  
  41:         return q.AsQueryable();
  42:     }
  43:  
  44:     public void InsertRootEntity(RootEntity value)
  45:     {
  46:         if (dataSource.Find(entity => entity.Id == value.Id) != null)
  47:         {
  48:             throw new InvalidOperationException("Cannot add two entities with the same key");
  49:         }
  50:  
  51:         dataSource.Add(value);
  52:         value.ChildId = value.Child.Id;
  53:         foreach (var sub in value.Child.SubChildren)
  54:         {
  55:             sub.ChildEntityId = value.Child.Id;
  56:             sub.Shape.SubChildEntityId = sub.Id;
  57:         }            
  58:     }
  59:  
  60:     public void UpdateRootEntity(RootEntity value)
  61:     {
  62:         var desiredEntity = dataSource.Find(entity => entity.Id == value.Id);
  63:         if (desiredEntity == null)
  64:         {
  65:             throw new InvalidOperationException("The given entity is not part of the data source");
  66:         }
  67:  
  68:         dataSource.Remove(desiredEntity);
  69:         dataSource.Add(value);
  70:  
  71:         value.ChildId = value.Child.Id;
  72:     }
  73:  
  74:     public void DeleteEntity(RootEntity value)
  75:     {
  76:         var desiredEntity = dataSource.Find(entity => entity.Id == value.Id);
  77:         if (desiredEntity == null)
  78:         {
  79:             throw new InvalidOperationException("The given entity is not part of the data source");
  80:         }
  81:  
  82:         dataSource.Remove(desiredEntity);
  83:     }
  84:  
  85:     public void InsertChildEntity(ChildEntity value)
  86:     {
  87:     }
  88:  
  89:     public void UpdateChildEntity(ChildEntity value)
  90:     {
  91:     }
  92:  
  93:     public void DeleteChildEntity(ChildEntity value)
  94:     {
  95:     }
  96:  
  97:     public void InsertSubChildEntity(SubChildEntity value)
  98:     {
  99:     }
 100:  
 101:     public void DeleteSubChildEntity(SubChildEntity value)
 102:     {
 103:     }
 104:  
 105:     public void UpdateSubChildEntity(SubChildEntity value)
 106:     {
 107:     }
 108:  
 109:     public void InsertCircle(Circle circle)
 110:     {
 111:     }
 112:  
 113:     public void UpdateCircle(Circle circle)
 114:     {
 115:     }
 116:  
 117:     public void DeleteCircle(Circle circle)
 118:     {
 119:     }
 120:  
 121:     public void InsertShape(Shape Shape)
 122:     {
 123:     }
 124:  
 125:     public void UpdateShape(Shape Shape)
 126:     {
 127:     }
 128:  
 129:     public void DeleteShape(Shape Shape)
 130:     {
 131:     }
 132:  
 133:     public override System.Collections.IEnumerable Query(QueryDescription queryDescription, out IEnumerable<System.ComponentModel.DataAnnotations.ValidationResult> validationErrors, out int totalCount)
 134:     {
 135:         return base.Query(queryDescription, out validationErrors, out totalCount);
 136:     }
 137:  
 138:     public override bool Submit(ChangeSet changeSet)
 139:     {
 140:         return base.Submit(changeSet);
 141:     }
 142:  
 143:     public override void Initialize(DomainServiceContext context)
 144:     {
 145:         base.Initialize(context);
 146:     }
 147:  
 148:     public override object Invoke(DomainOperationEntry operation, object[] parameters, out IEnumerable<System.ComponentModel.DataAnnotations.ValidationResult> validationErrors)
 149:     {
 150:         return base.Invoke(operation, parameters, out validationErrors);
 151:     }
 152:     
 153:     protected override bool AuthorizeChangeSet(ChangeSet changeSet)
 154:     {
 155:         return base.AuthorizeChangeSet(changeSet);
 156:     }
 157:     
 158:     protected override bool ExecuteChangeSet(ChangeSet changeSet)
 159:     {
 160:         return base.ExecuteChangeSet(changeSet);
 161:     }
 162:  
 163:     protected override bool ValidateChangeSet(ChangeSet changeSet)
 164:     {
 165:         return base.ValidateChangeSet(changeSet);
 166:     }
 167:  
 168:     protected override bool PersistChangeSet(ChangeSet changeSet)
 169:     {
 170:         return base.PersistChangeSet(changeSet);
 171:     }
 172:  
 173:     protected override bool ResolveChangeSet(ChangeSet changeSet)
 174:     {
 175:         return base.ResolveChangeSet(changeSet);
 176:     }
 177:  
 178:     protected override bool Resolve(object current, object original, object store, ResolveOption resolveOption)
 179:     {
 180:         return base.Resolve(current, original, store, resolveOption);
 181:     }
 182:  
 183:     protected override int Count<T>(IQueryable<T> query)
 184:     {
 185:         return base.Count<T>(query);
 186:     }
 187:  
 188:     protected override void OnError(DomainServiceErrorInfo errorInfo)
 189:     {
 190:         base.OnError(errorInfo);
 191:     }        
 192: }

Finally and very important you need to apply this workaround on the client side:

   1: public partial class Square
   2: {
   3:     public new Guid ShapeId
   4:     {
   5:         get
   6:         {
   7:             return base.ShapeId;
   8:         }
   9:  
  10:         set
  11:         {
  12:             base.ShapeId = value;
  13:         }
  14:     }
  15:  
  16:     public new int CenterX
  17:     {
  18:         get
  19:         {
  20:             return base.CenterX;
  21:         }
  22:  
  23:         set
  24:         {
  25:             base.CenterX = value;
  26:         }
  27:     }
  28:  
  29:     public new int CenterY
  30:     {
  31:         get
  32:         {
  33:             return base.CenterY;
  34:         }
  35:  
  36:         set
  37:         {
  38:             base.CenterY = value;
  39:         }
  40:     }
  41: }

And you will be able to run this code on client side:

   1: RootEntity rootEntity = new RootEntity();
   2: rootEntity.Id = Guid.NewGuid();
   3: rootEntity.Name = "A";
   4:  
   5: ChildEntity child = new ChildEntity();
   6: child.Id = Guid.NewGuid();
   7: rootEntity.Child = child;
   8:  
   9: SubChildEntity subChild = new SubChildEntity() { Id = Guid.NewGuid() };
  10: child.SubChildren.Add(subChild);                
  11:  
  12: Square square = new Square { ShapeId = Guid.NewGuid(), CenterX = 10, CenterY = 10 };
  13: subChild.Shape = square;
  14:  
  15: this.domainContext.RootEntities.Add(rootEntity);
  16:  
  17: var submitOperation = this.domainContext.SubmitChanges();
  18: submitOperation.Completed += (o, ee) =>
  19:     {
  20:         var op = (SubmitOperation)o;
  21:         if (op.HasError)
  22:         {
  23:             this.errors.Add(op.Error);
  24:         }
  25:     };

I was able to do the update using this code:

   1: bool flag2 = false;
   2: bool flag3 = false;
   3: bool flag4 = false;
   4:  
   5: private void btRunTest2_Click(object sender, RoutedEventArgs e)
   6: {
   7:     try
   8:     {
   9:         var op = this.domainContext.Load(this.domainContext.GetRootEntitiesQuery());                
  10:  
  11:         op.Completed += (o, ee) =>
  12:             {                                                
  13:                 var entity = this.domainContext.RootEntities.First();
  14:                 var op2 = this.domainContext.Load(this.domainContext.GetChildEntitiesQuery(entity.Id));
  15:                 op2.Completed += (oo, eee) =>
  16:                     {
  17:                         flag2 = true;
  18:                         OnEditEntity();
  19:                     };
  20:  
  21:                 var op3 = this.domainContext.Load(this.domainContext.GetSubChildrenQuery(entity.Id));
  22:                 op3.Completed += (oo, eee) =>
  23:                     {
  24:                         flag3 = true;
  25:                         OnEditEntity();
  26:                     };
  27:  
  28:                 var op4 = this.domainContext.Load(this.domainContext.GetShapesQuery(entity.Id));
  29:                 op4.Completed += (oo, eee) =>
  30:                     {
  31:                         flag4 = true;
  32:                         OnEditEntity();
  33:                     };                       
  34:             };
  35:     }
  36:     catch (Exception ex)
  37:     {
  38:         this.errors.Add(ex);
  39:     }
  40: }
  41:  
  42: private void OnEditEntity()
  43: {
  44:     if (flag2 && flag3 && flag4)
  45:     {
  46:         var entity = this.domainContext.RootEntities.First();
  47:         entity.Name = "B";
  48:         this.domainContext.SubmitChanges();
  49:     }
  50: }

Notice that ALL related entitysets must have the entities loaded for the associations to be correctly initialized.

Have fun,