我在上一篇日记的最后已经预告了,我这次将给大家介绍一下如何将Fluent API的配置组织的更好,更利于维护,但是一位“特别”同志迫不及待地揭晓了谜底,那么就让我们来看一下Fluent API的另一种使用方式吧。
我们项目中的domain中一般都有很多的类,如果我们把所有类的代码都写在DbContext子类的OnModelCreating重载方法中,那么这个方法将会非常庞大,并且各个类的配置都混杂在一起,非常不利于项目的后期维护。所以我们就需要更好地组织Fluent API的数据映射配置。
从上一篇日记我们知道modelBuilder的Entity<>泛型方法的返回值是EntityTypeConfiguration<>泛型类。我们可以定义一个继承自EntityTypeConfiguration<>泛型类的类来定义domain中每个类的数据库配置,我们在这个自定义类的构造函数中使用我们上次提到的那些方法配置数据库的映射。
public class CustomerEntityConfiguration:EntityTypeConfiguration<Customer>
{
public CustomerEntityConfiguration()
{
HasKey(c => c.IDCardNumber).Property(c => c.IDCardNumber).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
this.Property(c => c.IDCardNumber).HasMaxLength(20);
this.Property(c => c.CustomerName).IsRequired().HasMaxLength(50);
this.Property(c => c.Gender).IsRequired().HasMaxLength(1);
this.Property(c => c.PhoneNumber).HasMaxLength(20);
}
}
昨天我们已经介绍了怎样去改变Code First默认的数据库映射。但是当我们通过Fluent API改变数据库映射时,Code First会如何处理与新的数据库映射不匹配的数据库呢?
通过Code First提供的Database类的SetInitializer方法设定Code First如何根据Fluent API数据库映射配置初始化数据库。
每次AppDomain加载的时候会执行SetInitializer指定的初始化方法。
SetInitializer方法的参数可以使以下三个泛型类的对象:
CreateDatabaseIfNotExists<>:只有在没有数据库的时候才会根据数据库连接配置创建新的数据库。这种配置主要用于production环境,因为你不可能把你现在使用的数据库删除掉,那样会损失重要的数据。你需要让你的实施人员拿着与Fluent API配置对应的数据库脚本去更新数据库。
DropCreateDatabaseIfModelChanges<>:只要Fluent API配置的数据库映射发生变化或者domain中的model发生变化了,就把以前的数据库删除掉,根据新的配置重新建立数据库。这种方式比较适合开发数据库,可以减少开发人员的工作量。
DropCreateDatabaseAlways<>:不管数据库映射或者model是否发生变化,每次都重新删除并根据配置重建数据库。这种方式可以适用于一些特殊情况的测试,比如说当每次测试结束之后把所有的测试数据都删除掉,并且在测试开始前插入一些基础数据。
一般Database.SetInitializer方法都是在应用程序的入口,比如Global.ascx.cs,Main方法等地方调用的。
但是通过代码调用设置数据库的初始化方式并不是很方便,因为每种初始化方式都应用于不同的场合,当我们从开发环境变化到production环境时,肯定会使用不同的初始化方式,比如说从DropCreateDatabaseIfModelChanges变为CreateDatabaseIfNotExists。如果每次变化都要重新改代码,重新编译的话,太不方便了。
我们可以通过配置指定数据库初始化的方式,这样就可以更灵活的改变我们的初始化方式:
<?xml version="1.0"?>
<configuration>
<appSettings>
<add key="DatabaseInitializerForType OrderSystemContext"
value="System.Data.Entity.DropCreateDatabaseIfModelChanges[[OrderSystemContext]], EntityFramework" />
</appSettings>
</configuration>
我们还以自定义数据库初始化类,通过自定的初始化类,还可以将一些基础数据在创建数据库之后插入到数据库中去。
假设我们在测试环境中测试对Product类的相关操作,我们需要一些ProductCatalog的基础数据,因为Product中有一个Productcatalog的引用。我们可以定义一个自己的数据库初始化类,继承DropCreateDatabaseAlways,让Code First每次在执行测试之前都删除掉原来的数据库并且插入一些ProductCatalog的测试数据。
public class DropCreateOrderDatabaseWithSeedValueAlways : DropCreateDatabaseAlways<OrderSystemContext>
{
protected override void Seed(OrderSystemContext context)
{
context.ProductCatalogs.Add(new ProductCatalog { CatalogName = "DELL E6400", Manufactory = "DELL", ListPrice = 5600, NetPrice = 4300 });
context.ProductCatalogs.Add(new ProductCatalog { CatalogName = "DELL E6410", Manufactory = "DELL", ListPrice = 6500, NetPrice = 5100 });
context.ProductCatalogs.Add(new ProductCatalog { CatalogName = "DELL E6420", Manufactory = "DELL", ListPrice = 7000, NetPrice = 5400 });
}
}
我们在测试类的测试初始化方法中就可以指定Code First使用我们自定义的初始化类进行数据库的初始化:
Database.SetInitializer(new DropCreateOrderDatabaseWithSeedValueAlways());
然后我们就可以使用初始化时插入的基础数据进行我们的测试了:
[TestMethod]
public void CanAddProduct()
{
OrderSystemContext unitOfWork = new OrderSystemContext();
ProductRepository repository = new ProductRepository(unitOfWork);
ProductCatalog catalog = repository.SearchProductCatalog(c => c.CatalogName == "DELL E6400", 1, 10)[0];
Product product = new Product { Catalog = catalog, CreateDate = DateTime.Parse("2010-2-10"), ExpireDate = DateTime.Parse("2012-2-10") };
repository.AddNewProduct(product);
unitOfWork.CommitChanges();
}
我们打开SQL Server就可以发现基础的测试数据了:
PS:接下来的日记将涉及DbContext的一些配置,有兴趣剧透的同学可以继续猜测一下,谜题明天揭晓。
本文链接
本最小架构在1.0版本的基础上进行了进一步改进,适用于B/S结构的企业级系统应用,本系统依然站在巨人的肩膀上(马丁福勒),是在.NET领域中的实现模式的一种探索,供大家研究学习并应用于实际项目中。架构整体风格为三层架构,做到了松散耦合、可扩展性、易维护性等诸多有点。其较之前一个版本1.0而言已有较大改观和进步,主要体现在以下几点:
1、Spring深度整合MVC和Hibernate,MVC当“导演”,Spring当“管家”,Hibernate当“采购总监”,(*^__^*)
2、Spring升级到1.3.2,Hibernate升级到3.2,MVC依然为3.0
3、架构模块职责进一步划清,进一步提高了松耦合。
4、引入SpringAOP编程,解决DAO异常日志记录。
5、重新改善了Log4net组件的编程模式。
6、将对象依赖配置文件集中管理,一改过去分散管理的状态。
7、完善了Spring事物配置的通用性。
8、绝大部分对象之间依赖都使用Spring“依赖注入”,提高系统松耦合性,以及便利的实现了功能类的“单例性。
9、改Facade层为Segregate层。Facade层为远程调用,而使用Segregate(隔离)层在这里更加适用,并增加接口。
10、丰富样例,添加了编程规范注释。并且可以使用Ctrl+F5运行出样例。
代码:
EnterpriseArchitecture-2
运行环境:
VS2010、MVC3、.NetFramework4.0、Window XP以上、SQLServer NorthWind数据库
架构图:
其余设计和1.0版本类似,请参考1.0版本资料
企业级分层架构视图
本文链接
来自蜡人张:RDLC报表(五)
随着Visual Studio 2005中文版的推出,Microsoft汉化了MSDN的大部分内容,开发者再也不用啃英文了,本来想介绍一下LocalReport的Render方法,现在您可以到http://msdn2.microsoft.com/zh-cn/library/ms252207(VS.80).aspx获 得关于这部分的详细信息。之所以以前想介绍这个方法,是因为我将想大家介绍一种在Crystal Report中无法实现的自定义票据打印纸张的方法。Anyway,现在我直接向大家介绍这种方法,可能这种方法并不是很好的,但是确实是我经过一段时间 的摸索总结出来的。萝卜(http://luobos.cnblogs.com)曾经提到过的变通的方法不知道是不是我要介绍的这一种,欢迎和我进行交流!
要想使用RDLC报表并进行页面设置,我们先来看一下LocalReport是否有类似PageSettings的类、属性、方法或事件等,我仔细找了一 下,发现Microsoft.Reporting.WinForms.ReportPageSettings类具有PaperSize属性和Margin 属性,但可惜的是它们都是只读的,对我们来说没有意义;另外,LocalReport具有GetDefaultPageSettings()方法,这也只 能是获取当前报表的页面设置。没办法,只能采用变通的方法了。在.NET中如果想使用自定义纸张,最好的方法莫过于使用 System.Drawing.Printing.PrintDocument类了,还记得我在前面提到的一个GotReportViewer的例子吗?
2 private IList<Stream> m_streams;
3
4 private Stream CreateStream(string name, string fileNameExtension, Encoding encoding, string mimeType, bool willSeek)
5 {
6 Stream stream = new FileStream(name + "." + fileNameExtension, FileMode.Create);
7 m_streams.Add(stream);
8 return stream;
9 }
10
11 private void Export(LocalReport report)
12 {
13 string deviceInfo =
14 "<DeviceInfo>" +
15 " <OutputFormat>EMF</OutputFormat>" +
16 " <PageWidth>8.5in</PageWidth>" +
17 " <PageHeight>11in</PageHeight>" +
18 " <MarginTop>0.25in</MarginTop>" +
19 " <MarginLeft>0.25in</MarginLeft>" +
20 " <MarginRight>0.25in</MarginRight>" +
21 " <MarginBottom>0.25in</MarginBottom>" +
22 "</DeviceInfo>";
23 Warning[] warnings;
24 m_streams = new List<Stream>();
25 report.Render("Image", deviceInfo, CreateStream, out warnings);
26
27 foreach (Stream stream in m_streams)
28 stream.Position = 0;
29 }
30
31 private void PrintPage(object sender, PrintPageEventArgs ev)
32 {
33 Metafile pageImage = new Metafile(m_streams[m_currentPageIndex]);
34 ev.Graphics.DrawImage(pageImage, ev.PageBounds);
35
36 m_currentPageIndex++;
37 ev.HasMorePages = (m_currentPageIndex < m_streams.Count);
38 }
39
40 private void Print()
41 {
42 const string printerName = "Microsoft Office Document Image Writer";
43
44 if (m_streams == null || m_streams.Count == 0)
45 return;
46
47 PrintDocument printDoc = new PrintDocument();
48 printDoc.PrinterSettings.PrinterName = printerName;
49 if (!printDoc.PrinterSettings.IsValid)
50 {