Samstag, 24. April 2010

Foreign keys for RIA Services with Entity Framework 4.0

For Visual Studio 2010

If you want to use the ORM-Mapper Entity Framework 4.0 in an Silverlight Business Application you have to use additional foreign key for 0..1 to many associations in your model.

It should look like this:

image

To get the foreign key automatically in your entity you have to add the association by the context menu in the designer:

image

Another intressting feature in Visual Studio / Entity Framework 4.0 is the existence of a good support for complex types. So you can define often clustered fields as complex type, which make your model more readable.

image

These complex type will be transfered in the corresponding database table by the DB model generator of Visual Studio 2010 in that way:

image

DataAnnotations for Methods in WCF RIA Services and POCO model

When using the WCF RIA Services, especially in the scenario POCO Objects, you must notice a lot. In particular, the generation of the domain context code, could be very  frustrating. If you use for example a POCO data model with complex entities - in my case public enum as member of a class – the doman context access layer on the client (for example Silverlight ) maybe not longer generated. An error message does not appear.

Another mistake I made in the POCO scenario:

It is also important that you directly use the given entities from the client side to be manipulated. I mean don’t cast or transfer the parameter of the Update-, Insert-, Delete-method in an other class or reference instead of the given object the instance of the underlying data collection of the domain service. When you do this, all changes of your entity will not be propergated back to the Silverlight client.

A POCO-Objects example:

My business object:

   1: public enum RatingCategories
   2:     {
   3:         /// <summary>
   4:         /// Not set yet
   5:         /// </summary>
   6:         NotSet = -1,
   7:         /// <summary>
   8:         /// A positive average Rating
   9:         /// </summary>
  10:         Positive,
  11:         /// <summary>
  12:         /// An average Rating
  13:         /// </summary>
  14:         Average,
  15:         /// <summary>
  16:         /// A negative average Rating
  17:         /// </summary>
  18:         Negative,
  19:         /// <summary>
  20:         /// A random Rating
  21:         /// </summary>
  22:         Random
  23:     }
  24:     
  25:  
  26:     public enum IconVisibility 
  27:     {
  28:         Visible,
  29:         Collapsed
  30:     }
  31:     #endregion
  32:  
  33:     /// <summary>
  34:     /// A contract of a scenario for using as parameter in methods of the WCF facade. It implements the IScenario interface.
  35:     /// </summary>
  36:     public partial class ScenarioEntity : BaseEntity
  37:     {
  38:         #region Constructor
  39:         /// <summary>
  40:         /// Constructor. Initializes the contract from an object that implements IScenario.
  41:         /// </summary>
  42:         /// <param name="scenario">Object that implements IScenario.</param>
  43:         public ScenarioEntity()
  44:         {
  45:             ScenarioId = Guid.NewGuid().ToString();
  46:         }
  47:         #endregion
  48:         
  49:         #region Properties
  50:         /// <summary>
  51:         /// Contains the id of the scenario. Will be also used as row key!
  52:         /// </summary>
  53:         public string ScenarioId { get; set; }
  54:         /// <summary>
  55:         /// Contains the display name of this scenario entity.
  56:         /// </summary>
  57:         public string DisplayName { get; set; }
  58:         /// <summary>
  59:         /// Contains the datetime when the scenario was provided.
  60:         /// </summary>
  61:         public DateTime? Provided { get; set; }
  62:         /// <summary>
  63:         /// Contains the datetime when the calculation of this scenario was started.
  64:         /// </summary>
  65:         public DateTime? CalculationStart { get; set; }
  66:         /// <summary>
  67:         /// Contains the datetime when the calculation of this scenario was finished.
  68:         /// </summary>
  69:         public DateTime? CalculationEnd { get; set; }
  70:         /// <summary>
  71:         /// Contains the datetime when the calculation of this scenario was finished.
  72:         /// </summary>

My base class, which implements INotifyPropertyChanged:

   1: // Base Enity for POCO (plain-ole' CLR objects) domain models
   2:  public abstract class BaseEntity : INotifyPropertyChanged
   3:  {
   4:  
   5:      protected void OnPropertyChanged(string property)
   6:      {
   7:          OnPropertyChanged(property, true);
   8:      }
   9:  
  10:  
  11:      private void OnPropertyChanged(string property, bool setDirtyFlag)
  12:      {
  13:          PropertyChangedEventHandler handler = PropertyChanged;
  14:          if (handler != null)
  15:          {
  16:              handler(this, new PropertyChangedEventArgs(property));
  17:          }
  18:  
  19:      }
  20:  
  21:      #region INotifyPropertyChanged Members
  22:  
  23:      public event PropertyChangedEventHandler PropertyChanged;
  24:  
  25:      #endregion
  26:  
  27:      protected abstract bool _Equals(object obj);
  28:  
  29:      public override bool Equals(object obj)
  30:      {
  31:          return _Equals(obj);
  32:      }
  33:  
  34:     
  35:  }

The domain service which using a data persistence class for the business objects:

   1: [EnableClientAccess()]
   2:     public class RatingStressSimulatorDomainService : DomainService
   3:     {
   4:         private string _userId;
   5:  
   6:        
   7:         private RepositoryXMLStore simulatorRepository;
   8:  
   9:         public RatingStressSimulatorDomainService()
  10:         {
  11:             this.simulatorRepository = new RepositoryXMLStore();
  12:         }
  13:  
  14:         /// <summary>
  15:         /// Not used at this time
  16:         /// </summary>
  17:         /// <param name="context"></param>
  18:         public override void Initialize(DomainServiceContext context)
  19:         {
  20:             string x = "";
  21:             //this.ObjectContext.Connection.ConnectionString = "";
  22:             base.Initialize(context);
  23:         }
  24:  
  25:  
  26:         public void InsertScenario(ScenarioEntity scenarioEntity)
  27:         {
  28:             simulatorRepository.ProvideScenario(scenarioEntity,null);
  29:  
  30:         }
  31:  
  32:         public void UpdateScenario(ScenarioEntity scenarioEntity)
  33:         {
  34:             
  35:             simulatorRepository.Update(scenarioEntity);
  36:         }
  37:  
  38:  
  39:  
  40:         public void DeleteScenario(ScenarioEntity scenarioEntity)
  41:         {
  42:             simulatorRepository.DeleteScenario(scenarioEntity.ScenarioId);
  43:  
  44:         }
  45:  
  46:         [Invoke]
  47:         public string ProvideScenario(ScenarioEntity scenario)
  48:         {
  49:             if (scenario == null)
  50:             {
  51:                 scenario = new ScenarioEntity
  52:                 {
  53:                     Provided = DateTime.Now,
  54:                     ScenarioId = null,
  55:                     CreditCount = 0,
  56:                     CreditSum = 0,
  57:                     RatingCategorie = RatingCategories.NotSet
  58:                 };
  59:             }
  60:  
  61:             return simulatorRepository.ProvideScenario(scenario,null);
  62:         }
  63:  
  64:         [Invoke]
  65:         public void ProvideAndCalculateScenario(ScenarioEntity scenario)
  66:         {
  67:             ProvideScenario(scenario);
  68:             //simulatorRepository.StartCalculation(scenario.ScenarioId);
  69:         }
  70:  
  71:         public void StartCalculation(ScenarioEntity scenarioEntity)
  72:         {
  73:             simulatorRepository.StartCalculation(scenarioEntity);
  74:             
  75:         }

In the Silverlight client you can call the Insert-Method in that way:

   1:  
   2:             ProvideScenarioPopup scenarioPopup = (ProvideScenarioPopup)sender;
   3:             if (scenarioPopup.NewScenario != null)
   4:             {
   5:                 if (scenarioPopup.DialogResult == true)
   6:                 {
   7:                     simulator.ScenarioEntities.Add(scenarioPopup.NewScenario);
   8:                     simulator.SubmitChanges();
   9:                     // Grid refreshen
  10:                     Scenarios.ItemsSource = simulator.ScenarioEntities;
  11:                 }
  12:             }

After the SubmitChanges() the changes will be propergated back from the RIA Domain Service to the Domain Context an the client side and the entity collection of the context contains the new object.

In a similiar way you can call a update method:

   1: private void StartCalc_Click(object sender, RoutedEventArgs e)
   2:       {
   3:           Button button = sender as Button;
   4:           if (button != null)
   5:           {
   6:               button.IsEnabled = false;
   7:                ScenarioEntity entity = ((Button)sender).DataContext as ScenarioEntity;
   8:               if (entity != null)
   9:               {
  10:                   simulator.StartCalculation(entity);
  11:                   simulator.SubmitChanges();
  12:               }
  13:  
  14:             
  15:           }
  16:       }

 

DataAnnotations for DomainService-Methods

[Invoke] For methods which don’t have the standard signature
[Delete] Prefix: Delete orRemove
[Insert] Prefix: Insert, Add or Create
[Update] Prefix: Change, Update, Modify
[Query] IQueryable mit Return value INumerable, IQueryable, oder Entity
[Ignore] Not available in Domain context
[RequiresRole(“Role1”)] User have to be in role “Role1”
[Query(IsComposable=false)] Returns only one entity instance