我正在考虑使用Entity Framework 5和Database First(连接到SQL Server 2008 R2)创建一个VB.NET 11 WPF MVVM应用程序。
我选择了数据库优先,因为我正在将现有的解决方案迁移到WPF MVVM,当然数据库已经存在。
我想开始使用依赖注入,这样我就可以对尽可能多的代码进行单元测试。
我似乎找不到一个清晰简洁的教程来介绍如何在EF DB First中使用依赖注入,尤其是在vb.net中。虽然即使是C#示例也可以,但我相信。
我真正想要的是一个简单的分步指南,解释如何设置解决方案,如何设置每个部件以备依赖注入等,但这些似乎很难实现
到目前为止,我已经创建了解决方案及其项目,如下所示;
- DBAccess-它只包含我的.edmx文件和一个小mod,以便能够向构造函数提供ConnectionString
- DBControl-它包含各种类,我使用这些类在EDMX和ViewModels之间提供一个层。具体来说,我在这里填充复杂类型(我使用设计器创建的),用于通过UI显示"友好"数据,以及将这些"友好"复杂类型转换为映射的实体以进行保存/更新。在我的数据库中,每个表有一个类。每个都有两个"FetchFriendlyRecords">方法(其中一个接受过滤器)和一个">AddUpdateFriendlyRecord"方法。我为每个类创建了一个接口。每个类在其构造函数中都接受一个DbContext,我只是从DBAccess项目传递我的DbContext
- MainUI-它包含我的MVVM层,并引用DBControl项目中的每个类以提供DataBinding等
我看到有人建议,与其花时间编写一个复杂的解决方案来使用EF进行单元测试,不如创建一个填充了测试数据的公司模拟数据库,并简单地将代码指向模拟数据库,而不是实际数据库。但是,我更希望能够创建一个在内存中运行的解决方案,而不需要访问SQL Server。
任何帮助都会很好,包括告诉我这一切是否都错了!!
更新:
我采用了下面Paul Kirby提供的解决方案,并创建了一个我认为"有点"的存储库模式。
我创建了一个界面;
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插入到类中。
我曾希望能够使用我现有的上下文和"Effort"单元测试工具来模拟一个假的dbContext。
然而,我似乎没能让它发挥作用。
在此期间,在我的单元测试项目中,我将删除(如果它已经存在)并使用SQLCMD命令创建一个空的测试数据库,使用与我的实时数据库相同的模式。
然后,我创建一个引用测试数据库的dbContext,用测试数据填充它,并对此进行测试。
需要注意的是,我将重构我的"添加/编辑"方法,以使用实际的基本实体,而不是我的"友好"复杂版本,这是当时最简单的方法。
如果您首先使用DB,以下是我的建议。
- 打开.edmx文件,右键单击任何空白处,然后选择"添加代码生成项">
- 在"在线模板"区域,搜索"EF 5.x DbContext Generator for VB">
- 给.tt文件一个名称,点击add。这将改变.edmx文件生成支持代码的方式,使您的实体都是POCO,这通过保持主逻辑与EF断开连接来简化总体测试
完成这项工作后,您可能需要研究类似于工作单元模式的东西。下面是一个快速的代码示例,我稍后将对此进行解释。
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();
}
}
这是一个基本的工作单元,它公开了一些位置列表作为示例(很抱歉它在C#中,但我不太了解VB)。
请注意,它正在公开IDbSet对象-这就是神奇之处。如果在您的DBAccess项目中,您使用此工作单元或存储库模式来隐藏EF,并且由于它实现了一个接口并返回IDbSet的对象,任何需要您的数据的地方都可以将此IUnitOfWork构造函数注入DI,并替换为在需要单元测试时返回mock IDbSet对象的mock版本(它们最终只是IQueryables)。
您可能会发现,在新模板中生成POCO后,您甚至可以省去DBControl项目中的许多工作。
无论如何,这只是一些基本的东西,在定位您的项目,以优化单元测试和DI。