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] clip_image002[4]](http://blogs.developpeur.org/blogs/matthieu/clip_image0024_thumb_60B96170.gif)
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_image004[4]](http://blogs.developpeur.org/blogs/matthieu/clip_image0044_thumb_1A987E90.gif)
![clip_image006[4] clip_image006[4]](http://blogs.developpeur.org/blogs/matthieu/clip_image0064_thumb_180F4CD2.gif)
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_image008[4]](http://blogs.developpeur.org/blogs/matthieu/clip_image0084_thumb_76437440.gif)
![clip_image010[4] clip_image010[4]](http://blogs.developpeur.org/blogs/matthieu/clip_image0104_thumb_7AD97EFA.gif)
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_image012[4]](http://blogs.developpeur.org/blogs/matthieu/clip_image0124_thumb_263D9FF5.gif)
![clip_image014[4] clip_image014[4]](http://blogs.developpeur.org/blogs/matthieu/clip_image0144_thumb_23483B42.gif)
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] clip_image016[4]](http://blogs.developpeur.org/blogs/matthieu/clip_image0164_thumb_6880926B.gif)
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 :