Click here to Skip to main content
14,028,444 members
Click here to Skip to main content
Add your own
alternative version

Stats

8.3K views
17 bookmarked
Posted 18 Dec 2017
Licenced CPOL

Building Entity Framework Generic Repository 2 Connected

Rate this:
Please Sign up or sign in to vote.
Continue with the Entity Framework Generic Repository, in this case Connected for statefull apps.

Introduction

A few weeks ago, we looked a first article about of Disconnected Repository, in this article let’s complete with other piece off the puzzle, the connected Generic Repository. In this new Repository type, a very important new actor appears, is none other than ObservableCollection<T>, this will be 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 Projet

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 working 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 implements IDisposable interface for releasing unmanaged resources.
  • Should has 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 for 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> recive the data from database through queries and listen 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 classes:

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 IDisposible interface for releasing the unmanage resources.

 The class has a generic constrain from reference types.

Some methods are very similar that Disconnected in description, but are different in implementation.

Let’s go to build all methods.

 

ALL / ALLASYNC

The All/AllAsync methods return 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. Return 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  _dbSet.FindAsync(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 return a collection ever 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 to 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 datagrid for three actions:

  • Insert .- For insert new row, we will fill the last empty datagridrow.
  • Update .- For update rows, we will click y the datagridcell for entry in edit mode, and we will modify data.
  • Delete .- For delete rows, we will select the datagridrow and press the ‘supr’ key.

In Action:

 

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 inject field and fill the ObservableCollection 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 confused. 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 Enity Framwork 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;
 
    //private readonly IConGenericRepository<FootballClub> _repository;
    public ObservableCollection<FootballClub> Data { get; set; }
 
 
 
    //public MainViewModel(IConGenericRepository<FootballClub> repository)
    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 register changes ListenerChangedState, that register 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 having the same structure that the previous article Generic Repository Disconnected. We add a new WPF project BuildingEFGRepository.WPF_Con with the new example.

You will the change connectionstring of project BuildingEFGRepository.WPF_Con too.

 

 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Juan Francisco Morales Larios
Software Developer (Senior) Cecabank
Spain Spain
MVP C# Corner 2017

MAP Microsoft Active Professional 2014

MCPD - Designing and Developing Windows Applications .NET Framework 4
MCTS - Windows Applications Development .NET Framework 4
MCTS - Accessing Data Development .NET Framework 4
MCTS - WCF Development .NET Framework 4

You may also be interested in...

Pro

Comments and Discussions

 
QuestionUse the framework's async methods instead of calling your sync methods asynchronously Pin
Tohid Azizi25-Dec-17 23:00
professionalTohid Azizi25-Dec-17 23:00 
AnswerRe: Use the framework's async methods instead of calling your sync methods asynchronously Pin
Juan Francisco Morales Larios26-Dec-17 23:20
memberJuan Francisco Morales Larios26-Dec-17 23:20 
Hi, Tohid,

Your are certainly right. This is the best solution for the Connected Repository than DbContext is alive for all class live. In DisConnected repository, isn't a good practice, because disposed DbContext without finally the FindAsync method.

I have updated the method.

Thanks !!!

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile
Web01 | 2.8.190419.4 | Last Updated 18 Dec 2017
Article Copyright 2017 by Juan Francisco Morales Larios
Everything else Copyright © CodeProject, 1999-2019
Layout: fixed | fluid