views:

189

answers:

1

I'm looking at using oData endpoints in my Silverlight client. Naturally, I'm doing MVVM and I want the project to be nice and "Blendable" (i.e. I must be able to cleanly use static data instead of the oData endpoints when in design mode.)

Now to the problem. I'd like to use the DataServiceCollection in my ViewModels, since it allows for nice bindable collections without having to worry too much with BeginExecute/EndExecute etc.

Now, let's look at some code. My Model interface looks like this:

public interface ITasksModel
{
    IQueryable<Task> Tasks { get; }
}

The oData endpoint implementation of that interface:

public class TasksModel : ITasksModel
{
    Uri svcUri = new Uri("http://localhost:2404/Services/TasksDataService.svc");

    TaskModelContainer _container;

    public TasksModel()
    {
        _container = new TaskModelContainer(svcUri);
    }

    public IQueryable<Task> Tasks
    {
        get
        {
            return _container.TaskSet;
        }
    }
}

And the "Blendable" design-time implementation:

public class DesignModeTasksModel : ITasksModel
{
    private List<Task> _taskCollection = new List<Task>();

    public DesignModeTasksModel()
    {
        _taskCollection.Add(new Task() { Id = 1, Title = "Task 1" });
        _taskCollection.Add(new Task() { Id = 2, Title = "Task 2" });
        _taskCollection.Add(new Task() { Id = 3, Title = "Task 3" });
    }

    public IQueryable<Task> Tasks
    {
        get {
            return _taskCollection.AsQueryable();
        }
    }
}

However, when I try to use this last one in my ViewModel constructor:

    public TaskListViewModel(ITasksModel tasksModel)
    {
        _tasksModel = tasksModel;

        _tasks = new DataServiceCollection<Task>();
        _tasks.LoadAsync(_tasksModel.Tasks);
    }

I get an exception:

Only a typed DataServiceQuery object can be supplied when calling the LoadAsync method on DataServiceCollection.

First of all, if this is the case, why not make the input parameter of LoadAsync be typed as DataServiceQuery?

Second, what is the "proper" way of doing what I'm trying to accomplish?

+1  A: 

The reason LoadAsync requires DataServiceQuery is that just plain IQueryable doesn't define asynchronous way of executing the query. The reason the method takes IQueryable type as its parameter is so that users don't have to cast the query object to DataServiceQuery explicitely (makes the code shorter) and since we assume that users will try to run their code at least once, they would see the error immediately (as you did).

LoadAsync only supports asynchronous operations, so it needs the DataServiceQuery. If you already have the results (without a need to execute async request) you can call the Load method instead. Which is the answer to your second question. Instead of calling LoadAsync for both desing time and run time, you could use Load for design time and LoadAsync for run time. But due to tracking constrains you might need to create the DataServiceCollection in different way.

Something like this:

DataServiceCollection<Task> dsc;
DataServiceQuery<Task> dsq = _tasksModel as DataServiceQuery<Task>;
if (dsq != null)
{
    dsc = new DataServiceCollection<Task>();
    dsc.LoadAsync(dsq);
}
else
{
    dsc = new DataServiceCollection<Task>(myDataServiceContext);
    dsc.Load(_tasksModel);
    // Invoke the LoadAsyncCompleted handler here
}

If you pass the DataServiceContext to the constructor before caling Load the entities will be tracked (just like in the LoadAsync case). If you don't need that you can call the constructor which takes IEnumerable and TrackingMode and turn off tracking on it.

Vitek Karas MSFT