4

Update 2 : @Enigmativity has a brilliant answer. I've implemented this into a IObservableRepository<T>. Details in my answer below.


Question: So I've changed most of the question (See edit history) I would just like it if someone commented/validated/puked on my design. =)

So typically my Repos look like this:

public interface IRepository<T> where T : class
{
    T GetById(int id);
    IQueryable<T> GetAll();
    void InsertOnSubmit(T entity);
    void DeleteOnSubmit(T entity);
    int SubmitChanges();
}

But when it comes to Silverlight and WCF Data Services, it gets seriously annoying query related data with all the asynchrony. I have to load the parent entity async first and then query its child entities async.

So I came up with an IAsyncRepository, I'd like to know if the design is ok, whether it could be improved, (and whether it makes any sense using Rx here?)

To Solve the child entities problem I plan to load all required child entities before calling the callback.

My Repo looks like:

public interface IAsyncRepository<T> where T : class
{
    void GetById(int id, Action<T> callback);
    void GetAllFromQuery(Func<MyEntities, IQueryable<Product>> funcquery,
                                             Action<IList<Calculator>> callback)
}

You could use the repo like this:

productRepo.GetAllFromQuery(
    x => x.Products.Where(p => p.ID > 5),
    y => Assert.IsTrue(y.Count > 0)); //y is a IList<Product>

What do you guys think?

Regards, Gideon

gideon
  • 19,329
  • 11
  • 72
  • 113
  • Just curious, but why not just use WCF RIA Services and take advantage of Async loading and associated events , validation bubbling, out of band invokes, changeset management, etc. that it provides out of the box even for POCO? – Keith Adler Mar 18 '11 at 07:29
  • Really wish I could do that, but, I have an app already built on WCF-Data Services, and the SL app is not the only consumer.. One of the main consumers is a desktop/WPF app. – gideon Mar 18 '11 at 07:31
  • @nissan Just _to think about it_ how difficult would it be to have a RIA service over Entity Framework 4 models? Would it be beneficial for a WPF desktop app too? – gideon Mar 18 '11 at 07:32
  • Creating a Domain Service that exposes out Entity Framework 4 is a snap. I have no tried to access it from WPF, but these people have: http://stackoverflow.com/questions/2551258/how-to-use-wcf-ria-services-with-wpf-application You can easily publish out your domain service to OData, SOAP, and JSON through simple config changes. You might want to explore it at least. – Keith Adler Mar 18 '11 at 07:37
  • @Nissan thanks, I will check it out, maybe use it for the next SL app, but it would be impossible to use in the current app, because Its a lot of code already written and it need to be updated and refactored. – gideon Mar 18 '11 at 07:50

2 Answers2

4

Just a quick, off the cuff answer.

How about using the Reactive Extensions for .NET (Rx)?

You could then define your repository as:

public interface IObservableRepository<T> where T : class
{
    IObservable<T> GetById(int id);
    IObservable<T> GetAll(Func<IQueryable<T>, IQueryable<T>> query);
    IObservable<Unit> InsertOnSubmit(T entity);
    IObservable<Unit> DeleteOnSubmit(T entity);
    IObservable<int> SubmitChanges();
}

All of the returned observables would contain single values, except for GetAll which would have zero or more.

The Unit type is void in the Rx world. It's just a way of not needing to define a non-generic IObservable interface.

You would then query like so:

IObservableRepository<Foo> repo = ...;

var foos = repo.GetAll(ts => ts.Where(t => t.Bar == "Hello"));

foos.Subscribe(foo =>
{
    // Do something asynchronously with each `Foo`.
});

And submit could be done like this:

var submit =
    foos
        .Select(foo => repo.InsertOnSubmit(foo)).ToArray()
        .Select(s => repo.SubmitChanges());

submit.Subscribe(result =>
{
    // handle the asynchronous result of submit.
});

This is all based on trying to keep the repository methods as close as possible to the original, but it may be worth refactoring on the Silverlight side to something like this:

public interface IObservableRepository<T> where T : class
{
    IObservable<T> GetById(int id);
    IObservable<T[]> GetAll(Func<IQueryable<T>, IQueryable<T>> query);
    IObservable<int> Submit(T[] insertsOrUpdates);
    IObservable<int> Submit(T[] insertsOrUpdates, T[] deletes);
}

Submit would be a bit nicer now:

repo.Submit(foos).Subscribe(result =>
{
    // Handle asynchronous result of submit;
});

Like I said, off the cuff. :-)

Enigmativity
  • 113,464
  • 11
  • 89
  • 172
3

Too long to update the question, so I posted as an answer.

So I implemented it like this:

public interface IObservableRepository<T, TContext>
{
    IObservable<T> GetById(int id);
    IObservable<IList<T>> GetAll(Func<TContext, IQueryable<T>> funcquery);
    IObservable<int[]> SubmitInserts(IList<T> inserts);
    IObservable<int[]> SubmitDeletes(IList<T> deletes);
    IObservable<int[]> SubmitUpdates(IList<T> updates);
    //helpers
    IObservable<int> SubmitInsert(T entity);
    IObservable<int> SubmitDelete(T entity);
    IObservable<int> SubmitUpdate(T entity);
}

Some notes :

  • TContext is needed for GetAll(), the implementation will have the Entity Framework DataServiceContext which will allow you to do the following:

    var foos = repo.GetAll(ts => ts.Where(t => t.Bar == "Hello"));
    //ts is a DataContext passed here by GetAll();
    
  • The methods marked //helpers just call the other methods that take arrays.
  • The actual return type for CRUD functions for WCF Data Services+Entity Framework is a DataServiceResponse. What I do is loop through them and return the Http Status Codes. So the ints returned for the CRUD methods are Http Status Codes.
  • When loading child entities I just eager loaded them by doing this:

    context.Products.Expand("Child").Expand("Child2");
    

I can basically use it like this:

productRepo.GetById(3).Subscribe(x => /* Do something with product x*/ );
productRepo.SubmitUpdate(product)
         .Subscribe(r => /*return code should be 204 (http) 201 for insert */);
//same for insert and delete

Do tell me if I should put up the actual implementation here.

Any comments on this would be well taken =)

Thanks

gideon
  • 19,329
  • 11
  • 72
  • 113