‘ .NET开发 ’ category archive

Unity 学习笔记(1) — Unity简介及简单使用

261 views 四月 23, 09 by Timothy

Unity是微软Patterns & Practices团队所开发的一个轻量级的,并且可扩展的依赖注入(Dependency Injection)容器,它支持常用的三种依赖注入方式:构造器注入(Constructor Injection)、属性注入(Property Injection),以及方法调用注入(Method Call Injection).现在Unity最新的版本的1.2版,可以在微软的开源站点http://unity.codeplex.com下载最新的发布版本和文档。通过使用Unity,我们能轻松构建松耦合结构的程序,从而让整个程序框架变得清晰和易于维护。

在平常的软件编码过程中,程序的逻辑往往很复杂,尤其是大型项目的开发中,一个模块常常会去引用其他的模块,假设我们有一个监控器类,用来监控CPU的温度,当温度达到预警的范围时,监控器有一个报警的方法,方法里面通过短信提醒器,以发送短信的方式通知维护人员。于是就出现了下面这样一段最为常见的代码:

   1:  public class Monitor
   2:  {
   3:         public void Alarm()
   4:         {
   5:                 SMSNotify notify = new SMSNotify();
   6:                 notify.Send();
   7:          }
   8:  }

在Monitor类中,直接引用到了一个短信提醒器的类,这是最为不灵活和最不易于扩展的一种方式。或许我们想到了面向接口编程,利用多态的好处,可以提供灵活的不同子类的实现,增加代码扩展性等等。但是说到底,接口一定是需要实现的,也就是如下语句迟早要执行:

   1:  public void Alarm()
   2:  {
   3:         INotify notify = new SMSNotify();
   4:         notify.Send();
   5:  }

这样看来,在实现INotify这个接口的时候,仍然需要具体的类来实现,而这样的代码在程序编译的时候就已经固定下来,如果以后需要使用新的提醒器,仍旧需要修改源代码并重新编译。并且在我们的Monitor类中,明显依赖SMSNotify类,二者之间的耦合度非常紧密。因此Ioc(控制反转)模式被提出用来解决这种问题,也即把接口的具体实现延缓到运行时,接口的实现类是在运行时被装载的。这样,就算有了新的实现类,也不需要更改调用者的代码(可以在Unity中使用配置文件的方式实现)。这种Ioc模式可以被形象的比喻为:接口就像一个空壳,而在具体实现时,向这个空壳注入内容,而让它成为一个真正的实体。这种模式也被形象的称为:依赖注入。通过使用Unity,我们能构建松耦合的软件,并且对象之间相互关联的细节,我们也不必关心,可以交由依赖注入容器全权负责。

前面也提到了依赖注入常用的三种形式:构造器注入、属性注入和方法调用注入,我们可以通过例子来实现这三种形式的注入。还是以上面的场景为例,我们的几个类和接口如下图:

1

1.Constructor Injection

IMonitor接口定义:

   1:  public interface IMonitor
   2:  {
   3:      void Alarm();
   4:  }

Monitor类:

   1:  public class Monitor : IMonitor
   2:  {
   3:      private INotify notify;
   4:  
   5:      public Monitor(INotify n)
   6:      {
   7:          notify = n;
   8:      }
   9:  
  10:      public void Alarm()
  11:      {
  12:          notify.Send();
  13:      }
  14:  }

INotify接口定义:

   1:  public interface INotify
   2:  {
   3:      void Send();
   4:  }

EmailNotify类:

   1:  public class EmailNotify : INotify
   2:  {
   3:      public void Send()
   4:      {
   5:          Console.WriteLine("Send Email Notify...");
   6:      }
   7:  }

SMSNotify类:

   1:  public class SMSNotify : INotify
   2:  {
   3:      public void Send()
   4:      {
   5:          Console.WriteLine("Send SMS Notify...");
   6:      }
   7:  }

可以看到,在Monitor类的构造函数里面,传入的参数是一个INotify接口类型,Alarm方法,调用了实现类的Send方法,但具体调用哪一个实现类的Send方法,只有在注入实体后才知道。Unity容器中,通常使用RegisterType和Resolve方法来分别注册和获取实例,并且这两个方法有很多泛型和非泛型的重载,具体的类型和参数,可以参考Unity的官方帮助文档。

现在我们向Monitor的构造函数注入实现INotify接口的实例:

   1:  static void Main(string[] args)
   2:  {
   3:      IUnityContainer container = new UnityContainer();
   4:      container.RegisterType<IMonitor, Monitor>().RegisterType<INotify, SMSNotify>();
   5:  
   6:      IMonitor monitor = container.Resolve<IMonitor>();
   7:      monitor.Alarm();
   8:  
   9:      Console.ReadLine();
  10:  }

代码中我们注入的INotify实例是SMSNotify类的实例,然后调用monitor.Alrarm(),里面会调用notify.Send().
运行查看结果:
2
上面是针对单个构造函数的情况,如果有多个构造函数,需要指明哪个构造函数是需要被注入的,也即需要在指定被注入的构造函数加上attribute:InjectionConstructor

   1:  public Monitor(INotify n, string name)
   2:  {
   3:      notify = n;
   4:  }
   5:  
   6:  [InjectionConstructor]
   7:  public Monitor(INotify n)
   8:  {
   9:      notify = n;
  10:  }

运行后可得到一样的结果.

2.Property Injection

通过属性注入,我们需要加上attribute: Dependency,使得Unity容器在获取类对象实例时,自动实例化该属性所依赖的对象,并注入到属性中。

修改Monitor类,实现下面的代码:

   1:  public class Monitor : IMonitor
   2:  {
   3:      [Dependency]
   4:      public INotify Notify { get; set; }
   5:  
   6:      public void Alarm()
   7:      {
   8:          Notify.Send();
   9:      }
  10:  }

再在Main函数里面,修改原有的代码,这次我们让容器注入EmailNotify实例:

   1:  container.RegisterType<INotify, EmailNotify>();

运行查看结果:
3 还有一个比较方便的地方,可以为Dependency特性指定名称,这样,在注入时,会将RegisterType所指定的对应名称的实体进行注入,例如:

   1:      public class Monitor : IMonitor
   2:      {
   3:          [Dependency("SMS")]
   4:          public INotify Notify { get; set; }
   5:  
   6:          public void Alarm()
   7:          {
   8:              Notify.Send();
   9:          }
  10:      }

修改Main函数,在RegisterType函数中指定注入名称:

   1:              container.RegisterType<INotify, EmailNotify>("Email");
   2:              container.RegisterType<INotify, SMSNotify>("SMS");

运行查看结果:
2

3.Method Call Injection

Method Call Injection注入的时机和Constructor Injection有一定的区别,构造函数注入,是在容器创建实例的时候,而方法调用注入,是在方法被调用的时候。实现方法调用注入,需要在指定注入的方法前加上attribute: InjectionMethod

修改Monitor类的代码如下:

   1:      public class Monitor : IMonitor
   2:      {
   3:          private INotify notify;
   4:  
   5:          [InjectionMethod]
   6:          public void GetNotify(INotify n)
   7:          {
   8:              notify = n;
   9:          }
  10:  
  11:          public void Alarm()
  12:          {
  13:              notify.Send();
  14:          }
  15:      }

在程序运行时,容器会自动实例化GetNotify方法所依赖的对象,并自动调用该方法,将其注入到方法中。

Main函数如下:

   1:          static void Main(string[] args)
   2:          {
   3:              IUnityContainer container = new UnityContainer();
   4:              container.RegisterType<IMonitor, Monitor>();
   5:              container.RegisterType<INotify, EmailNotify>();
   6:  
   7:              IMonitor monitor = container.Resolve<IMonitor>();
   8:              monitor.Alarm();
   9:  
  10:              Console.ReadLine();
  11:          }

运行查看结果:

3

SQL中的日期计算

120 views 二月 22, 09 by Timothy

这两天写一个和统计数据有关的存储过程,里面要利用日期进行一些计算和判断,也自然要利用SQL的一些日期相关的函数。这里略记一下,当是复习一下SQL。

利用SQL脚本内置的几个函数,我们能灵活的对日期进行计算和比较。常用的几个函数:GETDATE(),DATEDIFF(),DATEADD()

GETDATE() 当然顾名思义,得到当前的日期,返回类型是DateTime类型。

DATEDIFF ( datepart , startdate , enddate ) 用于判断在两个日期之间存在的指定时间间隔的数目。

第一个参数是指定时间间隔的类型,例如mm(月),dd(天),yy(年),ms(毫秒),ss(秒),不同的间隔类型,返回的结果也不一样。

DATEADD (datepart , number, date ) 用于日期运算的函数,将传入的日期,加上指定时间间隔数目的日期。

 

例如,计算得到本年的第一天:

Select DATEADD(yy, DATEDIFF(yy,0,getdate()), 0)

我们来分析下这个SQL语句就可以知道,首先从最里面的getdate()开始,getdate()得到当前日期和时间,外层的datediff,计算当前日期和1900-01-01 00:00:00之间的时间间隔,返回单位是以年来统计的,如果我们分开看DATEDIFF(yy,0,getdate()),0)的结果,返回就是:109。返回的109,传递给最外层的DATEADD函数,将1900-01-01 00:00:00加上109年,得到的结果,自然就是2009-01-01 00:00:00了,也即本年的第一天。

 

同样,灵活的利用这几个函数的组合,我们可以得到不同的结果:

得到当月的第一天:Select DATEADD(mm, DATEDIFF(mm,0,getdate()), 0)

得到当前季度的第一天:Select DATEADD(qq, DATEDIFF(qq,0,getdate()), 0)

得到当天的起始时间: Select DATEADD(dd, DATEDIFF(dd,0,getdate()), 0)

得到上个月最后一天: Select DATEADD(ms,-3,DATEADD(mm, DATEDIFF(mm,0,getdate()), 0))

得到上个月的第一天: Select DATEADD(m,-1,DATEADD(mm, DATEDIFF(mm,0,getdate()), 0))

其原理就是得到当月第一天,再减去三毫秒(SQL的时间以3毫秒为一个单位),这样以当前为2月,得到的结果就是:2009-01-31 23:59:59.997

得到去年的最后一天: Select DATEADD(ms,-3,DATEADD(yy, DATEDIFF(yy,0,getdate()), 0))

得到本月的最后一天: Select DATEADD(ms,-3,DATEADD(mm, DATEDIFF(m,0,getdate())+1, 0))

得到本年的最后一天: Select DATEADD(ms,-3,DATEADD(yy, DATEDIFF(yy,0,getdate())+1, 0))

得到本月的第一个星期一: Select DATEADD(wk, DATEDIFF(wk,0,DATEADD(dd,6-datepart(day,getdate()),getdate())), 0)

Host WCF Service in WAS

189 views 十一月 24, 08 by Timothy

WAS 是 Windows (Process) Activation Service 的缩写,是Windows Vista中所新增的一种进程宿主模型。WAS作为 IIS7.0 特有的新增功能,和以前IIS 6.0的功能相比更加强大,因为它提供并支持除HTTP之外的更多协议,比如TCP方式和Pipe(管道)方式。以下的文中,都把Windows (Process) Activation Service简称为WAS。利用WAS作为WCF(Windows Communication Foundation)的宿主,我们能充分利用WAS的很多优点,因为我们再也不用为非HTTP方式的WCF Service单独编写宿主程序了。而WAS本身的特点,也让我们的服务端程序能享受到只有以往的HTTP方式的WCF Service才能拥有的很多特性。本文也简单向大家分享一下怎样使用WAS来作为WCF Service的宿主。

以往的部署WCF Service的方式,大家一般会想到以下几种:
1. 使用WinForm或者控制台程序作为宿主
2. 使用Windows Service作为宿主
不管我们用怎样的方式来作为WCF Service的宿主,少不了的麻烦,都是需要单独编写Host程序,除此之外,就该轮到WAS了。WAS在Vista中,其实是由一个单独的Windows Service来实现的,如果我们仔细找找,就能找到,Windows Service的名称,就叫做"Windows Process Activation Service"。由此看来,WAS的进程和IIS进程在物理上是隔离开的,能为我们提供一个灵活、稳定的WCF Service宿主环境。WAS内部的工作机制,大致和ASMX WebService类似。

简单了解了WAS的特性,下面我们用一个netTcpBinding的WCF示例来演示怎样利用WAS来Host WCF Service:

首先,我们需要查看Vista的组件中,是否打开了WAS的功能,打开控制面板,打开“程序和功能”对话框,在"打开/关闭 Windows功能"对话框中,确保下面图中的功能项被打开:

其实在这一部操作之后,windows会自动帮我们在IIS中做好配置,不过为了放心,我们还是打开IIS确认一下设置是否正确。

首先是检查IIS站点中的绑定:

确保net.tcp方式,绑定到808端口

打开站点的高级设置,确认"已启用的协议"中,填有"net.tcp",没有的话,可以补上。

然后是建立应用程序目录,这个目录等下会用来部署WCF的Service端程序。建立好目录后,也在"高级设置–已启用的协议"中,填上net.tcp

到这里,基本的host环境我们已经设置好了。下面来实现我们的服务端和客户端。

建立Contract
[code]
[ServiceContract]
public interface IService1
{
[OperationContract]
string SayHello(string value);
}
[/code]

实现简单的Service类
[code]
public class Service1 : IService1
{
public string SayHello(string value)
{
return string.Format("Hello,{0}", value);
}
}
[/code]

建立svc文件
[code]
<%@ ServiceHost Language="C#" Debug="true" Service="WCFLibrary.Service1" CodeBehind="./App_Data/Service1.cs" %>
[/code]

服务端的WCF配置
[code]










bindingConfiguration="NetTcpBinding" contract="WCFLibrary.IService1">






















[/code]

其实endpoint只需要一个即可,为了方便通过svcutil.exe生成客户端代理,需要通过另外一个endpoint的mexTcpBinding来暴露元数据。这样当服务在发布好的时候,我们可以通过
Svcutil.exe net.tcp://Timothy-T61/WCFService/HelloService.svc/mex 来生成客户端代理了。

完成服务端的编写,直接将服务端程序,部署到刚才在IIS中添加的应用程序目录中。

客户端的wcf配置如下:
[code]









bindingConfiguration="ClientBinding" contract="WCFLibrary.IService1" />

[/code]

客户端拖放一个按钮,添加如下代码:
[code]
private void button1_Click(object sender, EventArgs e)
{
ClientProxy client = new ClientProxy();
MessageBox.Show(client.SayHello("Timothy!"));
}
[/code]

一切就绪,运行程序,点击按钮:

没有单独编写host程序,通过WAS,我们的服务端正常运行了。

Linq to SQL 的更新冲突与管理

121 views 七月 03, 08 by Timothy

前段时间工作中的一个新需求,有机会用到了Linq to SQL。使用后的第一感觉,就是方便很多,也为整个项目节约了一大把的开发时间,甚至代码量也少了很多。不过在程序的实际运行中,始终会遇到一些莫名其妙的异常,最令人不解的,就是“System.Data.Linq.ChangeConflictException: Row not found or changed.” 。当初凭自己和同事的判断,可能是数据库的数据异常所导致,后来发觉这个异常出现得越来越频繁,于是上MSDN查了查,原来是Linq中一个常见的问题:更新冲突。
这个词说起来比较玄乎,其实再平常不过了。下面可以通过一个简单的例子,来重现这个异常。
建立一个普通的测试表:LinqTest(如图)

在测试表中,插入一条测试数据(如图)

测试代码如下:

[code]
namespace LinqTest
{
class Program
{
static void Main(string[] args)
{

TestDataContext db = new TestDataContext();

db.Log = Console.Out;
var result = from p in db.LinqTests
where p.ID == 1
select p;

var info = result.FirstOrDefault();

if(info != null) //插入断点
{
info.Age = 25;
db.SubmitChanges();
}

Console.ReadLine();

}

}

}
[/code]

在测试代码中,将DataContext的日志定向到Console的输出部分,这样方便我们观察Linq实际执行的SQL语句是什么。重现的时候,我们需要在注释的地方,插入断点进行测试。对于示例中的代码,在正常情况下,是不会有错误的。执行过后,我们可以在Console的输出中,看到实际执行的SQL语句(如图)

再进行第二次调试,首先,恢复Age的数据到以前的样子。下面我们运行到断点处,然后偷偷去SQL Server Management Studio中,手动修改数据,将原始数据中的Age,由24,改为22。然后回到VS2008的IDE,按F5继续运行程序,这个时候,你会发现异常出现了(如图)

再回到Console的输出,查看,执行的SQL语句和刚才的一样。这就是问题的所在,在正常运行状态下,Linq在运行时,会把数据库的数据缓存到实体对象中,这是一种理想化的情况,并且在更新时,Linq会默认把除更新字段外的所有字段,作为Update语句中的Where条件。但是,如果此时有另外的程序,在访问数据库,并修改数据库数据的时候,比如刚才把Age改为22。此时Linq缓存起来的数据和实际数据库中的数据产生了不一致的情况。Linq此时仍然把被修改过的字段,作为Update的Where条件,但是数据库中Age早就被我们改过了,不再是25,Where条件始终匹配不到原有的数据。这时,就会抛出所谓的:“System.Data.Linq.ChangeConflictException: Row not found or changed.”异常。

产生此异常,主要是Linq缓存数据和实际数据库数据不一致的情况造成。解决次问题的情况,主要有几种:

1.比较简单的方法,不使用Linq提供的SubmitChanges()方式提交更改,而直接执行SQL语句,例如:
db.ExecuteCommand("Update [dbo].[LinqTest] SET Age=25 Where ID = @p0", 1);
这样虽然比较方便,但是感觉又回到了直接写SQL的时代,毕竟Linq to SQL的目的,就是为了让我们看不见SQL,避免写复杂的SQL语句,而直接操作实体对象,这样也可以避免程序可读性差、不便于维护。所以除非万不得已,还是不太推荐使用此方法。

2.参考MSDN的资料,采用Linq提供的解决更新冲突的方法,在异常中捕获冲突,然后手动解决冲突:
[code]
try
{
db.SubmitChanges(System.Data.Linq.ConflictMode.ContinueOnConflict);
}
catch (System.Data.Linq.ChangeConflictException ex)
{
foreach (System.Data.Linq.ObjectChangeConflict occ in db.ChangeConflicts)
{
//以下是解决冲突的三种方法,选一种即可

// 使用当前数据库中的值,覆盖Linq缓存中实体对象的值
occ.Resolve(System.Data.Linq.RefreshMode.OverwriteCurrentValues);

// 使用Linq缓存中实体对象的值,覆盖当前数据库中的值
occ.Resolve(System.Data.Linq.RefreshMode.KeepCurrentValues);

// 只更新实体对象中改变的字段的值,其他的保留不变
occ.Resolve(System.Data.Linq.RefreshMode.KeepChanges);
}

// 这个地方要注意,Catch方法中,我们前面只是指明了怎样来解决冲突,这个地方还需要再次提交更新,这样的话,值 //才会提交到数据库。
db.SubmitChanges();
}

[/code]

3. 这个方法也比较简单,也即MSDN中所说的Pessimistic Concurrency Control 。 我们可以来设定哪些字段需要放入Where条件,哪些字段不需要,这样就可以控制更新时候的条件匹配尺度。具体做法,就是在Linq to SQL Designer中,把一些字段的UpdateCheck属性设置为Never,这样,这些字段在更新的时候,就不会再出现在Where条件中了。其实比较推荐的做法,就是在表中设立主键,因为更新的时候,只要把主键作为Where条件,就可以单独的确立一行数据了。把除主键外的字段属性中UpdateCheck设置为Never即可。

关于Linq to SQL中如何管理更改冲突的更多资料,可以在MSDN找到

http://msdn.microsoft.com/zh-cn/library/bb399389.aspx

ASP.net 2.0中水晶报表迁移部署问题

211 views 十月 14, 07 by Timothy

asp.net 2.0的水晶报表,在迁移机器的时候,如果目标机器没有相应的程序集,在IIS中会报错。
错误描述:

Parser Error
Description: An error occurred during the parsing of a resource required to service this
request. Please review the following specific parse error details and modify your source file
appropriately.

Parser Error Message: Could not load file or assembly 'Microsoft.ReportViewer.WebForms,
Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' or one of its
dependencies. The system cannot find the file specified.

【解决方法】

进入你的开发使用机器的c:\windows\assembly目录查看,是否有以下程序集安装在GAC中(如图):

由于资源管理器是使用了shell扩展来查看GAC中安装的程序集,所以我们无法看到这些程序集当前部署的真实目录在什么地方。我们可以直接在命令行模式中进行查看,进入X:\windows\assembly目录,然后查找和水晶报表相关的程序目录(如图):

我们可以大致看到GAC的部署结构了。我们在浏览器中所看到的一个一个的assembly名称,其实对应于assembly目录下面的每一个目录命名。进入具体的目录,还有一级子目录,这个目录的命名,是根据发布到GAC的程序集的版本号加上后面的公钥标记组成。(见上图的下面的红色标记部分)

再进入子目录,我们就可以看到公有部署的程序集,所存在的真正位置了。(如图):

接下来的工作就好办了,拷贝程序集到目标机器上,然后运行gacutil /i filename,将程序集安装到GAC即可。需要拷贝的程序集如下:

1) Microsoft.ReportViewer.Common.dll

2) Microsoft.ReportViewer.ProcessingObjectModel.dll

3) Microsoft.ReportViewer.WebForms.dll

Page 3 of 512345