I've looked into this quite a bit and haven't found a "perfect" solution. The repository pattern works wonderfully for MVC applications where the context is short lived because it exists in a short lived controller, but the problem happens when you try to apply that same structure to a wpf app where the VM can persist for long periods of time.
I have used this solution in the past which is much more simple than many of the repo patterns I have seen that attempt to abstract things out to an extreme amount, resulting in near unreadable amounts of code that are difficult to debug. Here are the steps...
- Create a separate project for the EDMX to act as your Data access layer
- Create a "Repositories" folder under the same project
Create a base class "BaseRepository" to act as the "Unit of Work". IDisposable
will allow you to use this in a using(){}
and the partial
will allow you do implement other repositories
public partial class MyEntityRepository : IDisposable
{
MyEntities context = new MyEntities();
public void Dispose()
{
context.Dispose();
}
}
Create another file called "MyOtherRepository". create the same partial class but implement methods based on what you want that file to contain
public partial class MyEntityRepository
{
public void MyOtherMethodSave(EntityObject obj)
{
//work with context
...
context.SaveChanges();
}
}
Now in your VM you can do this...
using(MyEntityRepository repo = new MyEntityRepository())
{
repo.MyOtherMethodSave(objectToSave);
}
This groups all your repositories under one class so you don't have to deal with separate context. It allows you better manage different repos by grouping the methods into different files and helps prevent code duplication. On top of that, your contexts are as short lived as they were without using this pattern.
The disadvantage is that with larger systems, you may have a lot of methods that get bundled under your repo. One solution in that case would be to implement some basic common commands like "Find" or "Add", and implement specialized ones in their respective repository.
In fact, both of these solutions are bad.
Creating a services singleton (IServices) containing all the available services as interfaces. Example: Services.Current.XXXService.Retrieve(), Services.Current.YYYService.Retrieve(). That way, I don't have a huge constructor with a ton of services parameters in them.
This is essentially the Service Locator Pattern, which is an anti-pattern. If you do this, you will no longer be able to understand what the view model actually depends on without looking at its private implementation, which will make it very difficult to test or refactor.
Creating a facade for the services used by the viewModel and passing this object in the ctor of my viewmodel. But then, I'll have to create a facade for each of my complexe viewmodels, and it might be a bit much...
This isn't so much an anti-pattern but it is a code smell. Essentially you're creating a parameter object, but the point of the PO refactoring pattern is to deal with parameter sets that are used frequently and in a lot of different places, whereas this parameter would only ever be used once. As you mention, it would create a lot of code bloat for no real benefit, and wouldn't play nice with a lot of IoC containers.
In fact, both of the above strategies are overlooking the overall issue, which is that coupling is too high between view models and services. Simply hiding these dependencies in a service locator or parameter object does not actually change how many other objects the view model depends on.
Think of how you would unit-test one of these view models. How big is your setup code going to be? How many things need to be initialized in order for it to work?
A lot of people starting out with MVVM try to create view models for an entire screen, which is fundamentally the wrong approach. MVVM is all about composition, and a screen with many functions should be composed of several different view models, each of which depends on only one or a few internal models/services. If they need to communicate with each other, you do so via pub/sub (message broker, event bus, etc.)
What you actually need to do is refactor your view models so that they have fewer dependencies. Then, if you need to have an aggregate "screen", you create another view model to aggregate the smaller view models. This aggregate view model doesn't have to do very much by itself, so it in turn is also fairly easy to understand and test.
If you've done this properly, it should be obvious just from looking at the code, because you'll have short, succinct, specific, and testable view models.
Best Answer
Personally I see nothing wrong with binding directly to the
Model
instead of exposing properties through theViewModel
, however if you plan on binding to your EF POCO classes, you need to have them implementINotifyPropertyChanged
so the UI knows when they update.EF should have a setting that will make it generate the classes with
INotifyPropertyChanged
already implemented, however you will still have to do something custom to getIDataErrorInfo
to work the way you want (I recommend usingIDataErrorInfo
instead of theValidator
class when working with WPF because the XAML is already setup to use that interface for errors). Usually I'll create partial classes for each data model that needs validation that implementsIDataErrorInfo
, although I know other methods do exist that may work better for you.Another alternative I've used in the past is to create another layer entirely for the Models, and not use EF models at all other than in the database layer. I think last time I did this, I modified EF's T4 template to autogenerate my Models at the same time the EF POCO objects were generated, with the exact same data structure, and I used AutoMapper to map EF objects to my Model objects whenever a database call was made.
And of course, there's also the 3rd alternative you mentioned of simply exposing the properties from the
ViewModel
, and not binding to theModel
at all. This is the "MVVM-Purist" way of doing it, but its often not as practical since it requires more time to implement.