WCF 示例 – 第十六章 – EF 5 和 SQL CE – AzureWebSites 部署






4.33/5 (6投票s)
EF Code First 实现通用存储库和工作单元。使用 SQL Compact 部署到 Azure 网站。
![]() |
||
第 XV 章 |
系列文章
WCF 示例是一个系列文章,介绍了如何设计和开发连接到 WCF 服务器组件的 WPF 客户端。对于持久化,在服务器端,我们已经介绍了如何实现 NHibernate、内存数据库和 RavenDB。该系列介绍描述了文章的范围,并从高层次讨论了架构解决方案。该系列的源代码可在CodePlex上找到。
章节概述
本章讨论了在 Azure WebServices 上实现 EF5 Code First 和 SQL Compact,然后在本地计算机上运行 WPF 客户端与 Azure 上部署的服务进行交互所需的操作。
关于 Azure 部署,我使用了 VS2010;如果您使用 VS2012,可能会发现部署更容易,因为 IDE 已与 Azure 完全集成。本文不涵盖安装 Azure SDK 后的部署步骤,而是描述如何使用 FTP 客户端手动部署服务器组件。
对于刚接触本系列文章的读者,eDirectory 解决方案的持久化组件围绕工作单元和存储库模式进行设计。本章的前几部分介绍了使用 EF5 Code First 实现这些模式。
以下是服务器组件中使用的代码示例
public CustomerDto UpdateCustomer(CustomerDto dto)
{
01 return ExecuteCommand(locator => UpdateCustomerCommand(locator, dto));
}
private CustomerDto UpdateCustomerCommand(IRepositoryLocator locator, CustomerDto dto)
{
02 var instance = locator.GetById<Customer>(dto.Id);
03 instance.Update(locator, dto);
return Customer_to_Dto(instance);
}
第 01 行:客户服务更新现有实例的入口点
第 02 行:使用 Dto.Id 属性,我们使用定位器解析客户实例
第 03 行:然后我们将操作委托给域类自身,传递定位器和 Dto 实例进行更新
有关解决方案中使用模式的更详细讨论,您可以参考以下章节
Entity Framework 实现 - 概述
在 eDirectory 解决方案中实现 EF 时,我想达成以下目标
- 对域实体的更改最少
- 如果可行,不对服务实现进行代码更改
- 最终解决方案不应破坏其他现有实现
以下是使 EF 工作所做的最相关的更改
- 替换专门的 Iesi 集合,改用 .NET 集合
- 添加了继承自 EntityTypeConfiguration 的内部映射类到实体类中
- 在 `RepositoryLocator` 中引入了一个新的 `FlushModifications` 方法
第一处更改,关于集合的更改,很容易解决。关于内部映射类的更改是一种替代 NHibernate 中映射配置的方法。我不喜欢在实体中包含持久化声明,但这是管理非公共成员映射声明的一种简单方法,例如 eDirectory 解决方案中的集合。最后一处很重要,因为 EF 处理标识列的方式与 NHibernate 在创建新实体时的方式不同。当 NHibernate 在调用 `Save` 方法后始终提供 Id 值时,EF 在调用 `Add` 方法时不会这样做。`FlushModifications` 提供了一种机制,可以持久化所有修改,以便新实体能够填充 Id 属性,此过程在不提交当前事务的情况下进行。
EF 存储库
这是我们本系列中第三次实现此模式;我们已经讨论过NHibernate和RavenDB的实现。无论如何,快速回顾一下存储库在 eDirectory 解决方案中的含义;首先,它是一个泛型类,为域实体公开基本的 CRUD 操作,但也公开了一个 `FindAll` 方法,该方法返回一个 `IQueryable` 实例。我使用了 EF linq 实现:`AsQueryable` 来提供此功能。以下代码是最终的 EF 实现
public class RepositoryEF<TEntity>
: IRepository<TEntity> where TEntity : class
{
private readonly IDbSet<TEntity> _dbSet;
public RepositoryEF(IDbSet<TEntity> dbSet)
{
_dbSet = dbSet;
}
#region Implementation of IRepository<TEntity>
public TEntity Save(TEntity instance)
{
return _dbSet.Add(instance);
}
public void Update(TEntity instance)
{
}
public void Remove(TEntity instance)
{
_dbSet.Remove(instance);
}
public TEntity GetById(long id)
{
return _dbSet.Find(id);
}
public IQueryable<TEntity> FindAll()
{
return _dbSet.AsQueryable();
}
#endregion
}
IDbSet
提供了所需的所有功能,使用 EF 实现 `IRepository` 模式非常直接。
EF 存储库定位器
`IRepositoryLocator` 的主要职责是在事务中创建实体存储库,第一次使用存储库时,定位器会创建它。对于 EF 实现,它还公开了上面提到的 `FlushModifications`。代码同样非常直接
public class RepositoryLocatorEF
: RepositoryLocatorBase
{
private readonly DbContext _dbContext;
public RepositoryLocatorEF(DbContext dbContext)
{
_dbContext = dbContext;
}
#region Overrides of RepositoryLocatorBase
public override void FlushModifications()
{
base.FlushModifications();
_dbContext.GetObjectContext().SaveChanges(SaveOptions.DetectChangesBeforeSave);
}
protected override IRepository<TEntity> CreateRepository<TEntity>()
{
return new RepositoryEF<TEntity>(_dbContext.Set<TEntity>());
}
#endregion
}
因此,定位器维护一个 `DbContext` 的引用,该引用有助于创建存储库实例,但也帮助获取允许我们在事务中间保存更改而不提交的 `ObjectContext`。顺便说一句,`GetObjectContext` 只是 `TransManager` 文件夹下的 `DbContextExtensions` 中的一个扩展方法。
EF 事务管理器
此类需要的代码最多,尽管如此,也还好,用 70 行代码我们就拥有了一个完整的事务性 EF 组件。顾名思义,此类负责管理事务中的所有实体更改;我们在 `using` 语句中使用此类,然后调用 `ExecuteCommand`,它提供了对 `RepositoryLocator` 的访问;使用 lambda 委托给帮助方法在使用此模式时是必需的。当 using 语句完成时,事务管理器负责提交所有更改。如果发生异常,将执行回滚,而无需在业务逻辑中执行任何额外的代码语句。以下是代码
public class TransManagerEF
: TransManagerBase
{
private readonly DbContext _dbContext;
private DbTransaction _transaction;
public TransManagerEF(DbContext dbContext)
{
_dbContext = dbContext;
var locator = new RepositoryLocatorEF(_dbContext);
Locator = locator;
}
public override void BeginTransaction()
{
base.BeginTransaction();
if (_dbContext.GetObjectContext().Connection.State != ConnectionState.Open)
{
_dbContext.GetObjectContext().Connection.Open();
}
_transaction = _dbContext.GetObjectContext().Connection.BeginTransaction();
}
public override void CommitTransaction()
{
base.CommitTransaction();
_dbContext.GetObjectContext().SaveChanges(SaveOptions.DetectChangesBeforeSave);
_dbContext.GetObjectContext().AcceptAllChanges();
_transaction.Commit();
}
public override void Rollback()
{
base.Rollback();
_transaction.Rollback();
}
...
}
再次,`DbContext` 实例是这里使用的关键 EF 组件;事实上,同一个实例被传递给与此 `TransactionManager` 一起创建的 `RepositoryLocator`。您可以看到如何使用 `ObjectContext` 完全控制连接,`DbContext` 实例对于我们所需的功能并不理想。`ObjectContext` 提供了管理事务所需的所有高级功能。值得一提的是 `DbTransation` 实例,它允许在需要时回滚更改。
EF 事务管理器工厂
此类唯一的角色是创建 `TransactionManager` 实例。代码如下
public class TransManagerFactoryEF
: ITransFactory
{
public IModelCreator ModelCreator { get; private set; }
public TransManagerFactoryEF(IModelCreator modelCreator)
{
ModelCreator = modelCreator;
}
#region Implementation of ITransFactory
public ITransManager CreateManager()
{
return new TransManagerEF(new eDirectoryDbContext(ModelCreator));
}
#endregion
}
与其他实现的主要区别在于 `IModelCreator`,这是 EF Code First 的要求;它有助于 EF 的初始化以及域实体和数据库表之间的映射。首先,我们有一个新类 `eDirectoryDbContext`
public class eDirectoryDbContext : DbContext
{
public IModelCreator ModelCreator { get; private set; }
public eDirectoryDbContext(IModelCreator modelCreator): base("eDirectory")
{
ModelCreator = modelCreator;
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
ModelCreator.OnModelCreating(modelBuilder);
}
}
这是 EF 中的常见模式,它允许初始化持久化层。与其他常见实现的一个区别是,它不为每个实体/聚合声明 DbSet 属性;相反,我们将此委托给 `IModelBuilder` 来完成。这样,此类就非常通用,并且与我们的域/实体没有直接依赖关系。
值得注意的是,构造函数如何通过将连接名称传递给 App/Web 配置文件中的基类来委托。您可能希望在自己的实现中构建一个更灵活的机制,以免此硬编码到您的解决方案中。
`IModelBuilder` 实现非常直接
public class ModelCreator:IModelCreator
{
#region Implementation of IModelCreator
public void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Configurations.Add(new Customer.Mapping());
modelBuilder.Entity<Address>();
}
#endregion
}
这基本上就是一个流畅的映射声明,虽然很简短;此类位于 Domain 项目中,并在运行时注入到 `TransManagerFactoryEF` 中。然后,当第一次使用 `DbContext` 时,将调用此类,并配置 EF 以便其能与我们的数据库协同工作。EF 在将 Poco 类链接到数据库方面非常智能,在我们的例子中,它只需要一点帮助。
首先,您可以使用 DataAnnotation 属性,EntityBase 使用 `[Key]` 属性来指示所有实体中的主键。然后,您可以使用 `ModelBuilder` 中的泛型 `Entity` 方法(如 Address 类所示),或者您也可以委托给 `EntityTypeConfiguration` 类。对于 `Customer` 实体也是如此
[JsonObject(IsReference = true)]
public class Customer
:EntityBase
{
protected Customer()
{
AddressSet = new HashSet<Address>();
}
...
protected virtual ICollection<Address> AddressSet { get; set; }
...
public class Mapping : EntityTypeConfiguration<Customer>
{
public Mapping()
{
HasMany(c => c.AddressSet).WithRequired(a => a.Customer);
}
}
}
`Mapping` 类是内部的,以便它可以访问未公开的 `AddressSet` 集合。自定义映射使用“隐藏”集合及其与 Address 实体的导航映射来声明集合。如前所述,您可能不希望在实体中包含此类依赖项,另一种方法是使用部分类来实现相同的结果。
值得注意的是,我们实际上不需要在 `ModelBuilder` 中声明 `Address` 实体,EF 可以从聚合根实体推断配置映射,在本例中,声明 `Customer` 足以让 EF 了解 `Address`。
WPF 客户端配置 EF 和 SQL Compact
要让 WPF 客户端使用 EF 连接到 SQL Compact 数据库,您必须在配置文件中添加以下部分
<connectionStrings>
<add name="eDirectoryDbContext" providerName="System.Data.SqlServerCe.4.0" connectionString="Data Source=|DataDirectory|\eDirectory.sdf;default lock timeout=60000" />
</connectionStrings>
注意连接名称,它与我们在声明 `eDirectoryDbContext` 时使用的名称相同,但没有“Context”后缀。如您所见,我们指示数据库位于 `DataDirectory`,即客户端应用程序的输出文件夹或 Web 应用程序的 App_Data 文件夹。然后使用 NuGet 安装 `EntityFramework.SqlServerCompact` 包,它还将添加以下依赖项

然后检查以下行是否已添加到您的配置文件中
<entityFramework>
<defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlCeConnectionFactory, EntityFramework">
<parameters>
<parameter value="System.Data.SqlServerCe.4.0" />
</parameters>
</defaultConnectionFactory>
</entityFramework>
在此示例中,SQL Compact 数据库已创建。如果需要,让 EF Code First 生成数据库应该不难。您也可以安装 `SQL Server Compact Tools` VS 扩展。它提供了一些创建和管理紧凑型数据库的实用功能。
此时,您应该尝试在本地测试应用程序,当您确定一切正常后,就可以准备部署到 Web 了。
部署到 Azure 网站
您可能知道,目前 Azure 网站为小型网站部署提供免费托管,您可能想查看定价和功能,如果您的计算机上安装了Azure SDK,使用 VS2010 或 VS2012 进行部署非常直接。您可以查看 Scott Hanselman 的视频或关于 Azure 网站部署的完整文章。
但您也可以使用 FTP 客户端部署应用程序,如果您这样做,则无需安装 SDK。本文包含两组二进制文件,一组是为连接到 Azure 上的 WcfByExamples 服务器配置的 WPF 客户端。另一组是服务器端工件,因此您可以尝试自己部署。
WPF 客户端
让我们看看如何让 WPF 客户端与 Azure 网站一起工作,首先,下载 WPF 客户端二进制文件并运行应用程序,如果您看到以下窗口并且可以看到数据,那么您已连接到 Azure 网站

如果您查看客户端配置文件,可以看到 WCF 端点已配置为连接到 Azure 网站
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<appSettings>
<add key="SpringConfigFile" value="file://WcfConfiguration.xml" />
</appSettings>
<system.serviceModel>
<client>
<endpoint address="http://wcfByExample.azurewebsites.net/CustomerWcfService.svc/CustomerServices"
binding="basicHttpBinding"
contract="eDirectory.Common.ServiceContract.ICustomerService"
name="BasicHttpBinding_ICustomerService">
</endpoint>
<endpoint address="http://wcfByExample.azurewebsites.net/AddressWcfService.svc/AddressServices"
binding="basicHttpBinding"
contract="eDirectory.Common.ServiceContract.IAddressService"
name="BasicHttpBinding_IAddressService">
</endpoint>
</client>
</system.serviceModel>
</configuration>
客户端配置非常简单,并且因为它使用 basic Http 绑定,所以您应该能够轻松地在大多数网络中使用它。
服务器部署
如前所述,使用 Azure SDK,从 VS 部署很容易;在这里,我将解释如何手动部署服务器解决方案,您需要执行以下操作
- 下载本文中包含的 eDirectory 服务器二进制文件
- 安装一个 FTP 客户端,我使用的是FileZilla
- 在 Azure 上创建一个帐户,以便您可以创建一个网站
- 部署服务器二进制文件
- 将 WPF 客户端指向您的网站
在 Azure 中创建网站
一旦您拥有 Azure 帐户,就需要使用“快速创建”选项创建一个网站

然后,给它起个名字

创建后,我们需要设置 FTP 用户名和密码。选择右侧的“部署凭据”选项,然后输入用户名和密码

此时,您应该可以在右侧仪表板上获取以下 FTP 详细信息

有了这些,使用 FTP 客户端连接应该很容易

将所有服务器文件(包括 App_Data 和 bin 文件夹)复制到 Azure 网站的 wwwroot 文件夹中。完成后,您可以配置客户端以指向您的网站。只需更改 `eDirectory.WPF.exe.config` 以便将端点重命名为您的网站名称

启用 NHibernate 代替
您可能想将服务器端更改为使用 NHibernate 代替,这只需要一行代码。打开 web.config 文件并将 appSetting 'SpringConfigFile' 更改为 ServerNhConfiguration.xml

结论
我终于有时间在 eDirectory 系列中实现 EF 了。令我惊讶的是,一旦我 put in place 了主要的 EF 组件,所需的映射量就很少。另一方面,在尝试实现事务管理器时,我与 EF API 进行了斗争;似乎 DbContext 是推荐使用的对象,但所有高级功能都在 ObjectContext 中提供。
我希望您喜欢 SQL Compact 和 ORM 解决方案的组合,我认为这是中小型应用程序的赢家。它非常容易部署到 Web,而且性能非常好。
最后但并非最不重要的,我希望这篇文章能够证明如今部署您的应用程序到 Azure 是多么容易……而且您还可以免费获得 10 个网站,虽然是小型的,但对很多人来说可能是一个很好的起点。