Я рассматриваю создание приложения VB.NET 11 WPF MVVM с использованием Entity Framework 5 и Database First (подключение к SQL Server 2008 R2).
Я выбрал базу данных в первую очередь, так как я переношу существующее решение на WPF MVVM, где база данных, конечно, уже существует.
Я хотел бы начать использовать внедрение зависимостей, чтобы я мог проводить модульное тестирование как можно большей части своего кода.
Кажется, я не могу найти четкое и краткое описание того, как использовать внедрение зависимостей с EF DB-First и, в частности, с vb.net. Хотя я уверен, что даже пример C # будет в порядке.
Что бы я действительно хотел, так это простое пошаговое руководство, объясняющее, как настроить решение, как настроить каждую часть, готовую для внедрения зависимостей и т. д., но это кажется трудным.
До сих пор я создал решение и его проекты следующим образом;
- DBAccess. В нем нет ничего, кроме моего файла .edmx и небольшого мода, позволяющего передавать ConnectionString конструктору.
- DBControl. Здесь находятся различные классы, которые я использую для обеспечения слоя между моим EDMX и моими моделями представления. В частности, я заполняю сложные типы (которые я создал с помощью дизайнера) здесь для отображения «более дружественных» данных через пользовательский интерфейс, а также для преобразования этих «дружественных» сложных типов в сопоставленные объекты для сохранения/обновления. У меня есть один класс на таблицу в моей базе данных. Каждый с двумя методами "FetchFriendlyRecords" (один принимает фильтры) и методом "AddUpdateFriendlyRecord". Я создал интерфейс для каждого класса. Каждый класс принимает DbContext в своем конструкторе, и я просто передаю свой DBContext из проекта DBAccess.
- MainUI — в нем размещены мои слои MVVM и ссылки на каждый класс в проекте DBControl, чтобы обеспечить привязку данных и т. д.
Я видел предположения, что вместо того, чтобы тратить время на написание сложного решения для модульного тестирования с помощью EF, проще создать твердую фиктивную базу данных с заполненными тестовыми данными и просто указать код на фиктивную базу данных, а не на живи один. Однако я бы предпочел иметь возможность создать решение в памяти, которое будет работать без какой-либо необходимости запускать SQL Server.
Любая помощь будет отличной, в том числе сказать мне, что я все делаю неправильно!!
Обновление:
Я принял решение, предоставленное Полом Кирби ниже, и, как я полагаю, создал «своего рода» шаблон репозитория.
Я создал интерфейс;
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
Затем я реализовал этот интерфейс для каждого класса;
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's 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's 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
Я использовал внедрение конструктора, чтобы вставить dbContext в класс.
Я надеялся смоделировать поддельный dbContext, используя существующий контекст и инструмент модульного тестирования "Effort". .
Однако, похоже, я не могу заставить это работать.
Тем временем в моем проекте модульного тестирования я удаляю (если он уже существует) и создаю пустую тестовую базу данных с помощью команды SQLCMD, используя ту же схему, что и моя рабочая база данных.
Затем я создаю dbContext, ссылающийся на тестовую базу данных, заполняю ее тестовыми данными и тестирую их.
В качестве примечания, я буду реорганизовывать свой метод «Добавить/редактировать» для работы с реальной базовой сущностью, а не с моей «дружественной» сложной версией, это был самый простой метод в то время.