Bienvenue à Blogs CodeS-SourceS Identification | Inscription | Aide

Abonnements

Utiliser un repository depuis WF

Imaginons le repository suivant :

public class FooRepository
{
public void Add(FooEntity entity) { /*TODO*/ }
public void Update(FooEntity entity) { /*TODO*/ }
public void Delete(FooEntityentity) { /*TODO*/ }
public void SaveChanges() { /*TODO*/ }
public void Dispose() { /*TODO*/ }
}

L’idée est de l’utiliser dans un contexte Workflow.

Pour cela j’utilise un WCF workflow service.

clip_image002[4]

Le receive de la méthode GetFooEntity retourne une instance de FooEntity que j’affecte ensuite dans la variable fooEntity du type FooEntity.

Imaginons qu’entre le Receive et le SendReply, je veuille ajouter une instance de fooEntity en utilisant mon repository, puis persister mon repository et enfin le disposer.

On peut le faire en instanciant et initialisant une variable de type FooRepository et en utilisant des activités de type InvokeMethod :

clip_image004[4]

clip_image006[4]

Comme vous pouvez le voir, c’est un peu pénible…

Mon idée est donc de définir une activité RepositoryScope pour simplifier cela.

Tout d’abord, nous allons créer une interface IRepository que ma classe FooRepository va implémenter :

public interface IRepository : IDisposable
{
     void Add(object entity);
     void Update(object entity);
     void Delete(object entity);
     void SaveChanges(); } public class FooRepository : IRepository {
     public void Add(FooEntity entity) { /*TODO*/ }
     public void Update(FooEntity entity) { /*TODO*/ }
     public void Delete(FooEntity entity) { /*TODO*/ }
     public void SaveChanges() { /*TODO*/ }
     public void Dispose() { /*TODO*/ }


     void IRepository.Add(object entity)
     {
         Add((FooEntity)entity);
     }
     void IRepository.Update(object entity)
     {
         Update((FooEntity)entity);
     }
     void IRepository.Delete(object entity)
     {
         Delete((FooEntity)entity);
     } }

Maintenant, nous allons définir notre activité RepositoryScope qui hérite de NativeActivity.

Dans le RepositoryScope, il faudrait être capable d’ajouter plusieurs entités. Pour cela, on pourrait utiliser une propriété de type Activity et utiliser une séquence mais autant directement utiliser une collection d’activité.

public class RepositoryScope<RepositoryType> : NativeActivity
     where RepositoryType : IRepository, new() {
     private Collection<Activity> _activities;
     public Collection<Activity> Activities
     {
         get { return _activities ?? (_activities = new Collection<Activity>()); }
     }

     protected override void Execute(NativeActivityContext context)
     {
         // TODO
     } }

Maintenant la première question est : comment renseigner cette propriété dans le designer ? Pour cela, nous allons définir notre propre ActivityDesigner et ajouter l’attribut Designer dans notre classe RepositoryScope<RepositoryType>.

[Designer(typeof(RepositoryScopeDesigner))]
 
 

<sap:ActivityDesigner x:Class="MyNamespace.RepositoryScopeDesigner"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    xmlns:sap="clr-namespace:System.Activities.Presentation;assembly=System.Activities.Presentation"

    xmlns:sapv="clr-namespace:System.Activities.Presentation.View;assembly=System.Activities.Presentation">

 

    <sap:ActivityDesigner.Resources>

        <Style TargetType="sap:WorkflowItemsPresenter">

            <Setter Property="SpacerTemplate">

                <Setter.Value>

                    <DataTemplate>

                        <Rectangle Height="1"

                                   Stroke="Gray"

                                   Width="50"

                                   Margin="0,10,0,10" />

                    </DataTemplate>

                </Setter.Value>

            </Setter>

            <Setter Property="ItemsPanel">

                <Setter.Value>

                    <ItemsPanelTemplate>

                        <StackPanel Orientation="Vertical" />

                    </ItemsPanelTemplate>

                </Setter.Value>

            </Setter>

        </Style>

    </sap:ActivityDesigner.Resources>

 

    <Grid>

        <sap:WorkflowItemsPresenter Items="{Binding Path=ModelItem.Activities}"

                                    HintText="Insert Activities Here" />

    </Grid>

</
sap:ActivityDesigner
>

Ensuite, on peut maintenant l’utiliser comme ceci :

clip_image008[4]

clip_image010[4]

Maintenant notre activité ScopeRepository doit scheduler ses activités. La manière la plus aisée est d’utiliser une séquence, de la scheduler dans la méthode Execute, de les retirer les activités des metadatas, de les ajouter dans la séquence et d’ajouter la Sequence dans les enfants de l’activité :

[Designer(typeof(RepositoryScopeDesigner))]
public class RepositoryScope<RepositoryType> : NativeActivity
     where RepositoryType : IRepository, new() {
     private Collection<Activity> _activities;
     public Collection<Activity> Activities
     {
         get { return _activities ?? (_activities = new Collection<Activity>()); }
     }


     private Sequence _scopeSequence;
     private Sequence ScopeSequence
     {
         get { return _scopeSequence ?? (_scopeSequence = new Sequence()); }
     }

     protected override void Execute(NativeActivityContext context)
     {
         context.ScheduleActivity(ScopeSequence);     }

     protected override void CacheMetadata(NativeActivityMetadata metadata)
     {
         base.CacheMetadata(metadata);
         metadata.SetChildrenCollection(null);         foreach (Activity activity in Activities)              ScopeSequence.Activities.Add(activity);         metadata.AddChild(ScopeSequence);     } }

Notez que le metadata.SetChildrenCollection(null) est obligatoire pour les affecter à la séquence.

Maintenant l’idée est d’utiliser ce scope pour faire plus que ce que ne fait déjà la séquence. Le scope va instancier le repository et va le disposer à la fin. Nous allons également utiliser un bloc Try / Finally pour disposer le repository quoi qu’il arrive.

[Designer(typeof(RepositoryScopeDesigner))]
public class RepositoryScope<RepositoryType> : NativeActivity
     where RepositoryType : IRepository, new() {
     public OutArgument<RepositoryType> Repository { get; set; }

     private Collection<Activity> _activities;
     public Collection<Activity> Activities
     {
         get { return _activities ?? (_activities = new Collection<Activity>()); }
     }

     private Sequence _scopeSequence;
     private Sequence ScopeSequence
     {
         get { return _scopeSequence ?? (_scopeSequence = new Sequence()); }
     }

     private TryCatch _tryCatch;
     private TryCatch TryCatch
     {
         get { return _tryCatch ?? (_tryCatch = new TryCatch()); }
     }

     private Activity _finallyActivity;
     public Activity FinallyActivity
     {
         get { return _finallyActivity ?? (_finallyActivity = new DisposeActivity()); }
     }

     protected override void Execute(NativeActivityContext context)
     {
         var repo = new RepositoryType();
         Repository.Set(context, repo);         context.ScheduleActivity(TryCatch);
     }

     protected override void CacheMetadata(NativeActivityMetadata metadata)
     {
         metadata.AddChild(FinallyActivity);
         base.CacheMetadata(metadata);
         metadata.SetChildrenCollection(null);
         foreach (Activity activity in Activities)
             ScopeSequence.Activities.Add(activity);
         TryCatch.Try = ScopeSequence;
         TryCatch.Finally = FinallyActivity;
         metadata.AddChild(TryCatch);
     } }

C’est un bon début mais encore faudrait-il que l’activité DisposeActivity connaisse le repository.

On pourrait utiliser un InArgument<IRepository>, cependant, il y a un meilleur moyen avec les NativeActivity : utiliser la propriété Properties. En effet, les instances contenues dans la propriété Properties sont partagées entre l’activité et ses descendants.

Nous pouvons donc procéder comme ceci :

[Designer(typeof(RepositoryScopeDesigner))]
public class RepositoryScope<RepositoryType> : NativeActivity
     where RepositoryType : IRepository, new() {
     public OutArgument<RepositoryType> Repository { get; set; }

     private Collection<Activity> _activities;
     public Collection<Activity> Activities
     {
         get { return _activities ?? (_activities = new Collection<Activity>()); }
     }

     private Sequence _scopeSequence;
     private Sequence ScopeSequence
     {
         get { return _scopeSequence ?? (_scopeSequence = new Sequence()); }
     }

     private TryCatch _tryCatch;
     private TryCatch TryCatch
     {
         get { return _tryCatch ?? (_tryCatch = new TryCatch()); }
     }

     private DisposeActivity _disposeActivity;
     public Activity DisposeActivity
     {
         get { return _disposeActivity ?? (_disposeActivity = new DisposeActivity()); }
     }

     protected override void Execute(NativeActivityContext context)
     {
         var repo = new RepositoryType();
         Repository.Set(context, repo);
         context.Properties.Add("Repository", repo);         context.ScheduleActivity(TryCatch);
     }

     protected override void CacheMetadata(NativeActivityMetadata metadata)
     {
         base.CacheMetadata(metadata);
         metadata.SetChildrenCollection(null);
         foreach (Activity activity in Activities)
             ScopeSequence.Activities.Add(activity);
         TryCatch.Try = ScopeSequence;
         TryCatch.Finally = DisposeActivity;
         metadata.AddChild(TryCatch);
     } }




internal class DisposeActivity : NativeActivity {
     protected override void Execute(NativeActivityContext context)
     {
         ((IRepository)context.Properties.Find("Repository")).Dispose();     } }

Cool ! Maintenant, nous n’avons plus besoin d’initialiser la variable fooRepository que nous allons récupérer comme OutArgument de notre RepositoryScope. Nous n’avons plus besoin de l’InvokeMethod sur Dispose (puisque le RepositoryScope s’en charge désormais).

clip_image012[4]

clip_image014[4]

A présent nous allons améliorer l’utilisation du RepositoryScope.

Tout d’abord, je ne pense pas qu’il soit souhaitable de passer la méthode à appeler sous la forme d’une string.

Nous allons donc créer des activités AddEntity, UpdateEntity, DeleteEntity et SaveChanges que nous allons utiliser dans le workflow.

public class AddEntity : NativeActivity
{
     [RequiredArgument]
     public InArgument<IRepository> Repository { get; set; }

     [RequiredArgument]
     public InArgument<object> Entity { get; set; }

     protected override void Execute(NativeActivityContext context)
     {
         Repository.Get(context).Add(Entity.Get(context));
     } } public class SaveChanges : NativeActivity {
     [RequiredArgument]
     public InArgument<IRepository> Repository { get; set; }

     protected override void Execute(NativeActivityContext context)
     {
         Repository.Get(context).SaveChanges();
     } }

clip_image016[4]

Maintenant l’idée est de définir l’argument Repository optionnel et de le déduire, dans ce cas, du RepositoryScope parent en utilisant la propriété Properties, comme nous avons fait avec la DisposeActivity :

public abstract class RepositoryActivity : NativeActivity
{
     public InArgument<IRepository> Repository { get; set; }

     protected virtual IRepository GetRepository(NativeActivityContext context)
     {
         IRepository repo = Repository.Get(context);
         if (repo == null)
             repo = ((IRepository)context.Properties.Find("Repository"));
         return repo;
     } } public class AddEntity : RepositoryActivity {
     [RequiredArgument]
     public InArgument<object> Entity { get; set; }

     protected override void Execute(NativeActivityContext context)
     {
         GetRepository(context).Add(Entity.Get(context));
     } } public class SaveChanges : RepositoryActivity {
     protected override void Execute(NativeActivityContext context)
     {
         GetRepository(context).SaveChanges();
     } } internal class DisposeActivity : RepositoryActivity {
     protected override void Execute(NativeActivityContext context)
     {
         GetRepository(context).Dispose();
     } }

Maintenant il va y avoir un point à prendre en compte : être capable de gérer l’encapsulation d’un RepositoryScope par un autre RepositoryScope. Pour cela, notre propriété “Repository” n’est pas suffisante.

Il va donc falloir changer notre code et nous allons utiliser une Stack<IRepository> à la place.

Dans notre code, on peut regretter la manière d’utiliser la properties “Repository”. Même s’il serait mieux d’utiliser une constante à la place de "Repository", ça reste pas terrible selon moi, et, de plus, c’est dommage de devoir faire un cast. Selon moi, on devrait utiliser des variables typés à la place.

Pour cela nous allons utiliser une nouvelle classe :

public class RepositoryContext
{
     private Stack<IRepository> _repositories;
     public Stack<IRepository> Repositories
     {
         get { return _repositories ?? (_repositories = new Stack<IRepository>()); }
     }

     public static RepositoryContext GetContext(NativeActivityContext context)
     {
         return (RepositoryContext)context.Properties.FirstOrDefault(kv => kv.Key == typeof(RepositoryContext).Name).Value;
     }

     public static RepositoryContext CreateContext(NativeActivityContext context)
     {
         RepositoryContext c = new RepositoryContext();
         context.Properties.Add(typeof(RepositoryContext).Name, c);
         return c;
     }

     public static RepositoryContext GetOrCreateContext(NativeActivityContext context)
     {
         return GetContext(context) ?? CreateContext(context);
     } }

Maintenant dans la méthode Execute de notre RespositoryScope, nous allons l’initialiser comme ceci :

RepositoryContext.CreateContext(context).Repositories.Push(repo);

à la place de

context.Properties.Add("Repository", repo);

Dans les RepositoryActivity, nous allons l’utiliser comme ceci :

repo = RepositoryContext.GetContext(context).Repositories.Peek();

au lieu de

repo = ((IRepository)context.Properties.Find("Repository"));

Dans le Dispose, nous allons dépiler notre pile:

internal class DisposeActivity : RepositoryActivity
{
     protected override void Execute(NativeActivityContext context)
     {
         GetRepository(context).Dispose();
         RepositoryContext.GetContext(context).Repositories.Pop();
     } }

Je trouve cela beaucoup mieux et je pourrais vouloir reproduire ce fonctionnement dans d’autre cas, d’où l’idée de le factoriser au maximum.

Je vais donc utiliser une classe de base :

public class WFPropertiesContext<T> 
where T : WFPropertiesContext<T>, new() {
     public static T GetContext(NativeActivityContext context)
     {
         return (T)context.Properties.FirstOrDefault(kv => kv.Key == typeof(T).Name).Value;
     }

     public static T CreateContext(NativeActivityContext context)
     {
         T c = new T();
         c.Initialize(context);
         context.Properties.Add(typeof(T).Name, c);
         return c;
     }

     public static T GetOrCreateContext(NativeActivityContext context)
     {
         return GetContext(context) ?? CreateContext(context);
     }

     protected virtual void Initialize(NativeActivityContext context)
     {
     } } public class RepositoryContext : WFPropertiesContext<RepositoryContext> {
     private Stack<IRepository> _repositories;
     public Stack<IRepository> Repositories
     {
         get { return _repositories ?? (_repositories = new Stack<IRepository>()); }
     } }
Ce post vous a plu ? Ajoutez le dans vos favoris pour ne pas perdre de temps à le retrouver le jour où vous en aurez besoin :

Publié jeudi 1 septembre 2011 23:59 par Matthieu MEZIL

Classé sous : ,

Commentaires

# re: Utiliser un repository depuis WF @ lundi 5 septembre 2011 12:04

Bonjour Matthieu,

Du WF4!!!! le pied...

Plus sérieusement, j'ai deux petits ajouts à te proposer :

Dans la méthode CacheMetadata de ton RepositoryScope tu as effectivement besoin de casser le Binding avec activités enfants via la méthode SetCollection(null). Mais si tu ne laissais pas la méthode de base du CacheMetadata s'exécuter, tu n'en aurais pas besoin. C'elle si est plus couteuse en ressources du fait de la réflexion, que ton propre code qui lui est plus propre.

J'ai bien quelques remarques à faire sur les bonnes pratiques. Donc si tu veux, petit mail?

J'adore l'idée du Stack! (mon RepositoryContext mien est moins propre).

Par contre il y a un truc auquel il faut faire attention, c'est l'emploi d'une séquence "dans" une activité et non "parmi" la composition de son implémentation. C'est risqué, car à l'avenir (WF vNext), il n'est pas certain que Microsoft ne va pas l'interdire.

L'habitude pour être tranquille c'est de faire une propriété Body de type Activity et de laisser à l'utilisateur le choix de l'activité qui servira à orchestrer les activités du scope (aucun scope fourni par Microsoft n'est fait autrement). Côté argumentaire pour le client final, on dit que l'on "responsabilise l'utilisateur final". En plus on améliore les performances en supprimant les activités en trop quand le conteneur doit être différent.

PS: j'avais fait un petit article WF  + EF ici : http://blogs.codes-sources.com/jeremyjeanson/archive/2010/08/05/wf4-un-petit-peu-d-executionproperties-avec-entity-framework.aspx

Tu pourrais me donner ton avis sur l'EF? Il n'y a pas grand-chose, mais je me demande toujours s’il n'y a pas une ou deux méthodes que je ne connaitrais pas.

.

JeremyJeanson

# re: Utiliser un repository depuis WF @ lundi 5 septembre 2011 15:53

Et ouais t'as vu je sais tout faire ;)

Pour le CacheMetadata, c'est vrai que si je n'appelle pas le base, je n'ai pas à réinitialiser les collection à null MAIS je dois définir les arguments dans ce cas et du coup, c'est reloud aussi.

Sinon oui je veux bien un petit mail :)

Pour la séquence, tu as raison mais je trouvais cette solution plus simple car dans la plus part des cas, on va enchaîner au minimum une action de type Add ou Delete et un Save.

Matthieu MEZIL

Les commentaires anonymes sont désactivés

Les 10 derniers blogs postés

- Les actualités de la semaine sur c2i.fr (14 mai - 20 mai) par Richard Clark le il y a 5 heures et 17 minutes

- Reactive Extensions : Consommer des services avec Rx Partie 3, les pièges à éviter par Léonard Labat le il y a 14 heures et 22 minutes

- SharePoint Blog Site, problème d’archives par Le Blog (Vert) d'Arnaud JUND le 05-20-2012, 13:09

- Soirée ALT.NET Mai - 3 présentations par #Rui le 05-18-2012, 11:59

- [ #SharePoint 2010][ #SQLServer 2012] AlwaysOn pour SharePoint (2/4) : Configuration (2e partie)… par Le blog de Patrick [MVP SharePoint] le 05-18-2012, 11:31

- Team Foundation Server 11: tous les trésors cachés du site d’équipe par Philess le 05-16-2012, 19:01

- [PowerShell 3] Télécharger et installer la documentation en ligne par Blog de SPBrouillet (Pierrick BROUILLET) le 05-16-2012, 17:36

- [#SharePoint 2010][#SQLServer 2012] AlwaysOn pour SharePoint (1/4) : Configuration (1ère partie)… par Le blog de Patrick [MVP SharePoint] le 05-16-2012, 12:10

- Job Day @MIC Brussels - .Net Developers on Mobile applications par Le Blog (Vert) d'Arnaud JUND le 05-15-2012, 20:26

- [SharePoint 2010] – SharePoint 2010, Windows (Server) 8 et des erreurs IIS sont dans une VM… par Blog de SPBrouillet (Pierrick BROUILLET) le 05-14-2012, 12:10