A few weeks ago, we looked at the first part, i.e., Disconnected Repository. In this article, let’s complete that with another piece of the puzzle, the connected Generic Repository. In this new Repository type, a very important new actor appears, that is none other than ObservableCollection<T>. This is an inseparable friend for Connected Generic Repository.
Index
- Entity Framework Generic Repositories Connected
- Set<TEntity> DbContext method
- Example Classes
- Building Entity Framework Generic Repositories Connected
- All / AllAsync
- Find / FindAsync
- GetData / GetDataAsync
- SaveChanges / SaveChangesAsync
- HasChanges / HasChangesAsync
- Extracting the Interface
- WPF Example
- Extending ConGeneriRepository<TEntitiy>
- Test Project
Entity Framework Generic Repositories Connected
Entity Framework generic repository connected is used in state process as WPF, Silverlight, Windows Forms, console app, etc.
This repository works with group changes, and it has fixed to ItemsControls. This generic repository type works with direct DataGrid modifications.
Its main characteristics are,
- Should receive the DbContext from dependency injection.
- Should implement an IDisposable interface for releasing unmanaged resources.
- Should have a DbContext protected property. This property will be alive during generic repository life and will only die in the Dispose method.
- The DbContext property will hear all repository changes.
- It doesn’t have methods (add, remove and update) because these actions are performed through the Local DbSet property. Local is ObservableCollection<TEntity> (INotifyCollectionChanged) and it usually will be linked directly to (ListBox, ListView, DataGrid, etc).
- The repository connected has a SaveChanged method to send all changes to DataBase.
- It has a SaveChanged method.
We must consider Connected Repository use because it has much impact on connection database consumption. This process consumes a connection for each user and screen loaded.
The ObservableCollection<T> is the key for the Repository, it as the intermediary between user/machine interactions and DbSet/DbContext. The ObservableCollection<T> receive the data from the database through queries and listen to the insert/delete changes through your event CollectionChanged and the modified by the event INotifiedPropertyChanged of the model.
Set<TEntity> DbContext method
Set<TEntity> the same as in the Disconnected Repository is very important, but in this case, our Connected Repository save its reference in a protected field because it has to be available in all repository live.
For more info, you read the DbSet<T> section y Disconnected Repository.
Example Classes
This is the example class.
- public partial class MyDBEntities : DbContext
- {
- public MyDBEntities()
- : base("name=MyDBEntities")
- {
- }
-
- public virtual DbSet<City> Cities { get; set; }
- public virtual DbSet<FootballClub> FootballClubs { get; set; }
-
- protected override void OnModelCreating(DbModelBuilder modelBuilder)
- {
- modelBuilder.Entity<City>()
- .Property(e => e.Name)
- .IsUnicode(false);
-
- modelBuilder.Entity<FootballClub>()
- .Property(e => e.Name)
- .IsUnicode(false);
-
- modelBuilder.Entity<FootballClub>()
- .Property(e => e.Members)
- .HasPrecision(18, 0);
- }
- }
-
- public partial class City
- {
- public int Id { get; set; }
-
- [Required]
- [StringLength(50)]
- public string Name { get; set; }
-
- [Column(TypeName = "numeric")]
- public decimal? People { get; set; }
-
- [Column(TypeName = "numeric")]
- public decimal? Surface { get; set; }
-
- public ICollection<FootballClub> FootballClubs { get; set; }
- }
-
- public partial class FootballClub
- {
- public int Id { get; set; }
-
- public int CityId { get; set; }
-
- [Required]
- [StringLength(50)]
- public string Name { get; set; }
-
- [Column(TypeName = "numeric")]
- public decimal Members { get; set; }
-
- [Required]
- [StringLength(50)]
- public string Stadium { get; set; }
-
- [Column(TypeName = "date")]
- public DateTime? FundationDate { get; set; }
-
- public string Logo { get; set; }
- }
Building Entity Framework Generic Repositories Disconnected
In the first step, we will create the generic ConGenericRepository class.
- public class ConGenericRepository < TEntity > : IDisposable where TEntity: class {
- protected internal readonly DbContext _dbContext;
- protected internal readonly DbSet < TEntity > _dbSet;
- public ConGenericRepository(DbContext dbContext) {
- if (dbContext == null) throw new ArgumentNullException(nameof(dbContext), $ "The parameter dbContext can not be null");
- _dbContext = dbContext;
- _dbSet = _dbContext.Set < TEntity > ();
- }
- public void Dispose() {
- if (_dbContext != null) _dbContext.Dispose();
- }
- }
To start, we will inject the DbContext object, will consult the DbSet and will save in a dbset field.
The class should implement the IDisposible interface for releasing the unmanaged resources. The class has a generic constraint from reference types.
Some methods are very similar that Disconnected in the description but are different in implementation.
Let’s go build all methods.
All / AllAsync
The All/AllAsync methods returns the all table data.
- public ObservableCollection < TEntity > All() {
- _dbSet.Load();
- var result = _dbSet.Local;
- return result;
- }
- public Task < ObservableCollection < TEntity >> AllAsync() {
- return Task.Run(() => {
- return All();
- });
- }
In use,
- [TestMethod]
- public void All_OK()
- {
- ObservableCollection<FootballClub> result = instance.All();
-
- Assert.IsNotNull(result);
- Assert.IsTrue(result.Count > 0);
- }
The method All/Async load the complete table in the Local (ObservableCollection) property and return the collection. The local property continuous listen the changes.
Find / FindAsync
The Find/FindAsync methods, is very similar to All/AllAsync methods, but Find/FindAsync search a simple row for PK. The PK can be simple or complex. It returns one row always.
- public TEntity Find(params object[] pks) {
- if (pks == null) throw new ArgumentNullException(nameof(pks), $ "The parameter pks can not be null");
- var result = _dbSet.Find(pks);
- return result;
- }
- public Task < TEntity > FindAsync(object[] pks) {
- return Task.Run(() => {
- return Find(pks);
- });
- }
The param pks behavior is identical to Disconnected, view this section of Disconnected article for more info.
In use,
- [TestMethod]
- public void Find_OK() {
- object[] pks = new object[] {
- 1
- };
- FootballClub result = instance.Find(pks);
- Assert.AreEqual(result.Id, 1);
- }
GetData / GetDataAsync
Like Find/FindAsync, the methods GetData/GetDataAsync are very similar than All/AllAsync unlike, GetData has an Expression<Func<TEntity,bool>> parameter for filter the query and the Find/FindAsync return only one item and GetData/GetDataAsync returns a collection although the collection has one item.
- public ObservableCollection < TEntity > GetData(Expression < Func < TEntity, bool >> filter) {
- if (filter == null) throw new ArgumentNullException(nameof(filter), $ "The parameter filter can not be null");
- _dbSet.Where(filter).Load();
- var filterFunc = filter.Compile();
- var result = new ObservableCollection < TEntity > (_dbSet.Local.Where(filterFunc));
- RelinkObservableCollection(result);
- return result;
- }
- public Task < ObservableCollection < TEntity >> GetDataAsync(Expression < Func < TEntity, bool >> filter) {
- return Task.Run(() => {
- return GetData(filter);
- });
- }
Note we have used a private method RelinkObservableCollection,
- private void RelinkObservableCollection(ObservableCollection<TEntity> result)
- {
- result.CollectionChanged += (sender, e) =>
- {
- switch (e.Action)
- {
- case System.Collections.Specialized.NotifyCollectionChangedAction.Add:
- _dbSet.Add((TEntity)e.NewItems[0]);
- break;
- case System.Collections.Specialized.NotifyCollectionChangedAction.Remove:
- _dbSet.Remove((TEntity)e.OldItems[0]);
- break;
- default:
- break;
- }
- };
- }
This method is necessary because we should return a part of DbSet property Local information only, for it we create a new ObservableCollection with the filter data and in this moment the Local property unlink it. The RelinkObservableCollection relinked the ObservableCollection changes with the DbSet.
In use,
- [TestMethod]
- public void GetData_OK()
- {
- Expression < Func < FootballClub, bool >> filter = a => a.Name == "Real Madrid C. F.";
- ObservableCollection < FootballClub > result = instance.GetData(filter);
- Assert.IsNotNull(result);
- Assert.IsTrue(result.Count == 1);
- }
SaveChanges / SaveChangesAsync
The method SaveChanges/SaveChangesAsync preserves the ObservableCollection Local property in Database.
- public int SaveChanges() {
- var result = _dbContext.SaveChanges();
- return result;
- }
- public Task < int > SaveChangesAsync() {
- return _dbContext.SaveChangesAsync();
- }
In action,
- [TestMethod]
- public void SaveChanges_OK() {
- ObservableCollection < FootballClub > data = instance.All();
- data.Add(new FootballClub {
- CityId = 1,
- Name = "New Team",
- Members = 0,
- Stadium = "New Stadium",
- FundationDate = DateTime.Today
- });
- int result = instance.SaveChanges();
- int expected = 1;
- RemovedInsertRecords();
- Assert.AreEqual(expected, result);
- }
HasChanges / HasChangesAsync
The method HasChanges/HasChangesAsync verifies if the DbSet property has been modified. In WPF applications this is very practical in conjunction of Commands for enabled or disabled save buttons.
- public bool HasChanges()
- {
- var result = _dbContext.ChangeTracker.Entries<TEntity>()
- .Any(a => a.State == EntityState.Added
- || a.State == EntityState.Deleted
- || a.State == EntityState.Modified);
-
- return result;
- }
-
- public Task<bool> HasChangesAsync()
- {
- return Task.Run(() =>
- {
- return HasChanges();
- });
- }
The method verified the ChangeTracker property search rows in state: Added, Deleted or Modified.
In action,
- [TestMethod]
- public void HasChanges_OK() {
- ObservableCollection < FootballClub > data = instance.All();
- data.Add(new FootballClub {
- CityId = 1,
- Name = "New Team",
- Members = 0,
- Stadium = "New Stadium",
- FundationDate = DateTime.Today
- });
- bool result = instance.HasChanges();
- Assert.IsTrue(result);
- }
Extracting the Interface
Once this has been done, we will extract the Interface.
Result
- public interface IConGenericRepository<TEntity> : IDisposable where TEntity : class
- {
- ObservableCollection<TEntity> All();
- Task<ObservableCollection<TEntity>> AllAsync();
- ObservableCollection<TEntity> GetData(Expression<Func<TEntity, bool>> filter);
- Task<ObservableCollection<TEntity>> GetDataAsync(Expression<Func<TEntity, bool>> filter);
- TEntity Find(params object[] pks);
- Task<TEntity> FindAsync(object[] pks);
- int SaveChanges();
- Task<int> SaveChangesAsync();
- bool HasChanges();
- Task<bool> HasChangesAsync();
- }
We have translated the IDisposable implements to this interface.
WPF Example
The example making is the same as for Disconnected Repository, so that you can see it in the Disconnected article.
Thinking in our Connected Generic Repository, MainViewModel is a most important class. In our example, we interact directly with the data grid for three actions:
- Insert .- For insert new row, we will fill the last empty data-grid row.
- Update .- For update rows, we will click on y the datagrid cell for entry in edit mode, and we will modify data.
- Delete .- For delete rows, we will select the datagrid row and press the ‘supr’ key.
In the following, we will show the classes (ViewModels) where we use the Connected Generic Repository in the WPF project.
- public class MainViewModel: ViewModelBase, IDisposable {
- private readonly IConGenericRepository < FootballClub > _repository;
- public ObservableCollection < FootballClub > Data {
- get;
- set;
- }
- private FootballClub _selectedItem;
- public FootballClub SelectedItem {
- get {
- return _selectedItem;
- }
- set {
- Set(nameof(SelectedItem), ref _selectedItem, value);
- }
- }
- public MainViewModel(IConGenericRepository < FootballClub > repository) {
- _repository = repository;
- Data = _repository.All();
- }
- public void Dispose() {
- _repository.Dispose();
- }
- public RelayCommand SaveCommand => new RelayCommand(SaveExecute, SaveCanExecute);
- private bool SaveCanExecute() {
- var result = _repository.HasChanges();
- return result;
- }
- private void SaveExecute() {
- Action callback = () => {
- var changes = _repository.SaveChanges();
- Messenger.Default.Send(new PopupMessage($ "It has been realized {changes} change(s) in Database."));
- CollectionViewSource.GetDefaultView(Data).Refresh();
- };
- Messenger.Default.Send(new PopupMessage("Do you want to make changes in DataBase ?", callback));
- }
- }
The class MainViewModel receives injected a IConGenericRepository<FootballClub> interface. In its constructor feed your namesake injects field and fills the ObservableCollection and uses All generic repository method. This class has a RelayCommand with name SaveCommand, this command uses two methods, Execute and CanExecute. For CanExecute method we will use the HasChanges repository method, this will provide enabled or disabled the save button and we will us to assure save without changes. The SaveExecuted method sends message to view for show messagebox, and it wait the messagebox answer to save data in database for SaveChanges repository method.
Extending ConGenericRepository
The connected world can be confusing. In the previous example, we could make serveral changes in the datagrid and as we were going to save the changes, we knew nothing of which rows are inserted or which rows are updated or deleted. For this reason, we will create a new datagrid column with this information. This column will be the row state.
In the Entity Framework model class, we will create a new NotMapped property.
- private string _state;
- [NotMapped]
- public string State {
- get {
- return _state;
- }
- set {
- if (_state != value) {
- _state = value;
- OnPropertyChanged();
- }
- }
- }
This property will contain the all property general state. In Entity Framework initial version, all generates entities class had this property.
We will add the new specific generic repository connected, FutballClubConRepository
- public class FootballClubConRepository: ConGenericRepository < FootballClub > , IFootballClubConRepository {
- public FootballClubConRepository(DbContext dbContext): base(dbContext) {}
- public string GetState(FootballClub entity) {
- var stateEntity = _dbContext.Entry(entity).State;
- return stateEntity.ToString();
- }
- }
FutballClubConRepository should be inherits ConGenericRepository<TEntity> and implements a constructor base. Add the GetState method for advice the Entity Framework internal state.
We will Update MainViewModel
- public class MainViewModel : ViewModelBase, IDisposable
- {
- private readonly IFootballClubConRepository _repository;
-
-
- public ObservableCollection<FootballClub> Data { get; set; }
-
-
-
-
- public MainViewModel(IFootballClubConRepository repository)
- {
- _repository = repository;
-
- Data = _repository.All();
-
- ListenerChangeState(Data, _repository);
- }
-
- private void ListenerChangeState(ObservableCollection<FootballClub> data, IFootballClubConRepository repository)
- {
- data.ToList().ForEach(a => ChangeStateRegister(a, repository));
-
- data.CollectionChanged += (sender, e) =>
- {
- if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
- {
- var entity = e.NewItems[0] as FootballClub;
-
- entity.State = "Added";
- }
- };
- }
-
- private void ChangeStateRegister(FootballClub entity, IFootballClubConRepository repository)
- {
- entity.PropertyChanged += (sender, e) =>
- {
- if (e.PropertyName != "State")
- {
- entity.State = repository.GetState(entity);
- }
- };
- }
-
- public void Dispose()
- {
- _repository.Dispose();
- }
-
-
- public RelayCommand SaveCommand => new RelayCommand(SaveExecute, SaveCanExecute);
-
- private bool SaveCanExecute()
- {
- var result = _repository.HasChanges();
-
- return result;
- }
-
- private void SaveExecute()
- {
- Action callback = () =>
- {
- var changes = _repository.SaveChanges();
-
- Messenger.Default.Send(new PopupMessage($"It has been realized {changes} change(s) in Database." ));
-
- CollectionViewSource.GetDefaultView(Data).Refresh();
-
- ResetDataStates(Data);
- };
-
- Messenger.Default.Send(new PopupMessage("Has you make the changes in DataBase ?", callback));
- }
-
- private void ResetDataStates(ObservableCollection<FootballClub> data)
- {
- data.ToList().ForEach(a => a.State = null);
- }
- }
We have added two private methods for registering changes ListenerChangedState, that registers the insert changes and ChangeStateRegister that register the modified changes.
Ultimately, we will review the class converter
- public class StateConverter : IMultiValueConverter
- {
-
- public ImageBrush _imgInsert;
- public ImageBrush _imgUpdate;
-
-
-
- public StateConverter()
- {
- _imgInsert = Application.Current.FindResource("Inserted") as ImageBrush;
- _imgUpdate = Application.Current.FindResource("Edited") as ImageBrush;
- }
-
-
- public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
- {
- if (values[0] == null) return null;
-
- var valueStr = values[0].ToString();
-
- switch (valueStr)
- {
- case "Added" : return _imgInsert;
- case "Modified": return _imgUpdate;
- }
-
- return null;
- }
-
- public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
- {
- throw new NotImplementedException();
- }
- }
This class transform the state description for the image description.
In action,
Test Project
The test project still has the same structure as the one in the previous article, Generic Repository Disconnected. We add a new WPF project BuildingEFGRepository.WPF_Con with the new example.