Dependency Injection with Entity Framework 5 Database. Starting?

I am considering creating a VB.NET 11 WPF MVVM application using Entity Framework 5 and Database First (connecting to SQL Server 2008 R2).

I chose Database First because I am migrating an existing solution to WPF MVVM, where the database already exists, of course.

I would like to start using Dependency Injection so that I can Unit Test make the most of my code.

It seems that I can not find a clear and concise idea of ​​how to use dependency injection with EF DB-First and, in particular, with vb.net. Although even a C # example would be fine, I'm sure.

I would really like this simple step-by-step guide explaining how to set up a solution, how to set up each part ready for Injection Dependency, etc., but it seems hard to find.

So far I have created a solution and its projects as follows:

  • DBAccess There is nothing but my .edmx file, and a small mod to be able to connect ConnectionString to the constructor.
  • DBControl . The various classes that I use to provide the layer between my EDMX and my ViewModels are stored here. In particular, I fill in the complex types (which I created using the constructor) here to display the "Friendly" data through the user interface, as well as converting these "friendly" complex types into displayed objects for saving / updating. I have one class for a table in my database. Each of them has two methods "FetchFriendlyRecords" (one accepts filters) and the method "AddUpdateFriendlyRecord". I created an interface for each class. Each class accepts a DbContext constructor in the constructor, and I just pass my DBContext from the DBAccess project.
  • MainUI . This is where MVVM layers and links to each class are stored in the DBControl project to provide DataBinding, etc.

I saw that instead of spending time creating a complex solution to be able to Unit Test with EF, it’s easier to create a proprietary mock database with filled test data and just specify the code in the mock database, rather than live. However, I would rather be able to create an in-memory solution that will work without any problems with SQL Server.

Any help would be great, including telling me if I'm going to get it all wrong!

Update:

I made the decision provided by Paul Kirby below and created a “Grade” repository template, which I suppose.

I created an interface,

Public Interface IFriendlyRepository(Of T) ReadOnly Property FriendlyRecords As ObservableCollection(Of T) Function GetFilteredFriendlyRecords(predicates As List(of Func(Of T, Boolean))) As ObservableCollection(Of T) Function AddEditFriendlyRecord(ByVal RecordToSave As T) As EntityException Sub SaveData() End Interface 

Then I implemented this interface in a class by class;

 Namespace Repositories Public Class clsCurrenciesRepository Implements Interfaces.IFriendlyRepository(Of CriticalPathDB.FriendlyCurrencies) Private _DBContext As CriticalPathEntities 'The Data Context Public Sub New(ByVal Context As DbContext) _DBContext = Context End Sub Public ReadOnly Property FriendlyRecords As ObservableCollection(Of FriendlyCurrencies) Implements Interfaces.IFriendlyRepository(Of CriticalPathDB.FriendlyCurrencies).FriendlyRecords Get ' We need to convert the results of a Linq to SQL stored procedure to a list, ' otherwise we get an error stating that the query cannot be enumerated twice! Dim Query = (From Currencies In _DBContext.Currencies.ToList Group Join CreationUsers In _DBContext.Users.ToList On Currencies.CreationUserCode Equals CreationUsers.User_Code Into JoinedCreationUsers = Group From CreationUsers In JoinedCreationUsers.DefaultIfEmpty Group Join UpdateUsers In _DBContext.Users.ToList On Currencies.LastUpdateUserCode Equals UpdateUsers.User_Code Into JoinedUpdateUsers = Group From UpdateUsers In JoinedUpdateUsers.DefaultIfEmpty Where (Currencies.Deleted = False Or Currencies.Deleted Is Nothing) Order By Currencies.NAME Select New FriendlyCurrencies With {.Currency_Code = Currencies.Currency_Code, .NAME = Currencies.NAME, .Rate = Currencies.Rate, .CreatedBy = If(Currencies.CreationUserCode Is Nothing, "", CreationUsers.First_Name & " " & CreationUsers.Last_Name), .CreationDate = Currencies.CreationDate, .CreationUserCode = Currencies.CreationUserCode, .Deleted = Currencies.Deleted, .LastUpdateDate = Currencies.LastUpdateDate, .LastUpdatedBy = If(Currencies.LastUpdateUserCode Is Nothing, "", UpdateUsers.First_Name & " " & UpdateUsers.Last_Name), .LastUpdateUserCode = Currencies.LastUpdateUserCode}).ToList Return New ObservableCollection(Of FriendlyCurrencies)(Query) End Get End Property Public Function GetFilteredFriendlyRecords(predicates As List(of Func(Of FriendlyCurrencies, Boolean))) As ObservableCollection(Of FriendlyCurrencies) Implements Interfaces.IFriendlyRepository(Of CriticalPathDB.FriendlyCurrencies).GetFilteredFriendlyRecords Dim ReturnQuery = FriendlyRecords.ToList For Each Predicate As Func(Of FriendlyCurrencies, Boolean) In predicates If Predicate IsNot Nothing Then ReturnQuery = ReturnQuery.Where(Predicate).ToList End If Next Return New ObservableCollection(Of FriendlyCurrencies)(ReturnQuery) End Function Public Function AddEditFriendlyRecord(ByVal RecordToSave As FriendlyCurrencies) As EntityException Implements Interfaces.IFriendlyRepository(Of CriticalPathDB.FriendlyCurrencies).AddEditFriendlyRecord Dim dbCurrency As New Currency ' Check if this Staff Member Exists Dim query = From c In _DBContext.Currencies Where c.Currency_Code = RecordToSave.Currency_Code Select c ' If Asset exists, then edit. If query.Count > 0 Then dbCurrency = query.FirstOrDefault Else 'Do Nothing End If dbCurrency.Currency_Code = RecordToSave.Currency_Code dbCurrency.NAME = RecordToSave.NAME dbCurrency.CreationDate = RecordToSave.CreationDate dbCurrency.CreationUserCode = RecordToSave.CreationUserCode dbCurrency.LastUpdateDate = RecordToSave.LastUpdateDate dbCurrency.LastUpdateUserCode = RecordToSave.LastUpdateUserCode dbCurrency.Deleted = RecordToSave.Deleted ' Save Asset Object to Database If query.Count > 0 Then ' If Asset exists, then edit. Try '_dbContext.SaveChanges 'We could save here but it generally bad practice Catch ex As EntityException Return ex End Try Else Try _DBContext.Currencies.Add(dbCurrency) '_dbContext.SaveChanges 'We could save here but it generally bad practice Catch ex As EntityException Return ex End Try End If Return Nothing End Function Public Sub SaveData() Implements Interfaces.IFriendlyRepository(Of CriticalPathDB.FriendlyCurrencies).SaveData _DBContext.SaveChanges() End Sub End Class End Namespace 

I used constructor injection to insert dbContext into the class.

I was hoping that I could fake a fake dbContext using my existing context and the “Amplification” Unit Testing Tool .

However, it seems like I can't get this to work.

At the same time, in my Unit Test project, I drop (if it already exists) and create an empty test database using the SQLCMD command, using the same schema as my current database.

Then I create a dbContext that references the test database, populate it with test data and check for it.

As a note, I will reorganize my Add / Edit method to work with the actual base Entity, and not with my "friendly" complex version, it was the easiest method at that time.

+4
source share
1 answer

If you are working with DB-first, here is what I would suggest.

  • Open your .edmx file, right-click on any space and select "Add Code Generation Item"
  • In the "Online Templates" area, find "EF 5.x DbContext Generator for VB".
  • Give the .tt file a name, click add. This will change the way you create your .edmx support code file so that your objects are POCO, which simplifies testing in general without disconnecting the core logic from EF.

After you do this, you probably want to learn something like the Unit of Work pattern. Here is a quick code example, I will explain it later.

 public interface IUnitOfWork { IDbSet<Location> Locations { get; } void Commit(); } public class EFUnitOfWork : IUnitOfWork { private readonly YourGeneratedDbContext _context; public EFUnitOfWork(string connectionString) { _context = new YourGeneratedDbContext(); } public IDbSet<Location> Locations { get { return _context.Locations; } } public void Commit() { _context.SaveChanges(); } } 

This is the basic unit of work that provides an example of a list of locations (sorry that it is in C #, but I don't know VB very well).

Note that it exposes IDbSet objects - this is where magic comes from. If you use this unit of work or the repository template in your DBAccess project to hide EF, and also because it implements the interface and returns IDbSet objects, anywhere your data is needed, this IUnitOfWork constructor can be entered with DI and replaced to the mocked version that returns IDbSet mock objects (after all, it's just IQueryables) when you need a unit test.

You may find that with the POCO generation in this new template, you can even abandon the big work that you do in your DBControl project.

In any case, these are just some of the basic things in terms of positioning your project for optimal unit testing and DI.

+6
source

Source: https://habr.com/ru/post/1439274/


All Articles