‘ .NET开发 ’ category archive

扩展方法收集–实体验证

190 views 一月 17, 10 by Timothy

关于实体验证,是早期在博客园看到一个同学的文章,里面利用扩展方法对实体进行验证的思路比较有创意。大家可以先跳过去先看一下:http://www.cnblogs.com/tristanguo/archive/2009/05/15/1457197.html
这个实体验证的扩展方法,设计比较巧妙,充分利用了链式编程的特点,让代码更加简洁和美观,唯一不足的地方是后来作者在改进的时候感觉不是很到位。于是偶的同事Rex同学,对这实体验证的扩展方法进行了改进,在我看来,应该算是比较完美的解决方法了,因此在我们的实际项目中,也比较大量的应用到了此扩展方法。在这里贴出来,给大家分享一下。
贴代码:

?View Code CSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace CommonLib
{
    public static class EntityValidator
    {
        public static ValidateResult Validate(this T target,
                                              Predicate predicate,
                                              string errorMessage)
        {
            var result = new ValidateResult(target);
 
            if (!predicate(target))
            {
                result.Errors.Add(errorMessage);
            }
 
            return result;
        }
 
        public static ValidateResult Validate(this ValidateResult target,
                                              Predicate predicate,
                                              string errorMessage)
        {
 
            if (!predicate(target.Entity))
            {
                target.Errors.Add(errorMessage);
            }
 
            return target;
        }
    }
 
    public class ValidateResult
    {
        internal List Errors { get; set; }
        internal T Entity { get; private set; }
        public bool HasErrors
        {
            get { return Errors.Count > 0; }
        }
 
        internal ValidateResult(T entity)
        {
            Errors = new List();
 
            Entity = entity;
        }
 
        public string[] ErrorMessages { get { return Errors.ToArray(); } }
    }
}

有了这个实体验证的方法,代码可以简洁多了,并且再也不用在业务逻辑判断中,写入复杂的if…else语句块,取而代之的代码,示例如下:

?View Code CSHARP
1
2
3
4
5
6
7
8
9
10
11
12
ValidateResult validateResult =
                        content.Validate(c => !string.IsNullOrEmpty(c.Name), "Name为空")
                               .Validate(c => c.MobilePhone.Length == 11 , "MobilePhone长度不正确")
                               .Validate(c => c.Age >= 10), "年龄不在规定范围")
                               .Validate(c => c.Height <= 200 && c.Height >= 180, "身高不符合标准");
 
                    if (validateResult.HasErrors)
                    {
                        log.Info("实体验证失败:");
                        validateResult.ErrorMessages.ForEach(p => log.Info(p));
                        return null;
                    }

怎么样?这样的链式编程的代码,比起一大串的if…else组合语句,简洁、明了多了,并且还可以提高代码阅读的效率,强烈推荐各位同学使用。

Linq to Sql 之延迟加载与立即加载

163 views 十二月 20, 09 by Timothy

Linq的延迟加载

Linq to Sql中默认采用的模式就是延迟执行,所谓延迟执行,其实就是在获取对象本身时,并不会获取和其关联的其他对象,只有在访问其关联对象的时候,程序才会去加载关联对象的数据到内存中。这样的好处是程序不会在初次访问的时候,就加载大批量的数据,而是以一种延迟加载的方式进行处理,相对而言,对于系统和网络的性能开支会减小很多。对于一个默认的Linq to Sql查询,延迟加载就是其默认的设置,不过,在某些情况下,延迟加载并非完全“智能”,不但没有实现其本意,反而增大了网络流量和性能开支。下面我们以SQL Server中的演示数据库NorthWind来试验一下:

linq_context

?View Code CSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
LinqTestDataContext ctx = new LinqTestDataContext();
ctx.Log = Console.Out;
 
var result = ctx.Orders.Where(p => p.OrderID == 10251);
 
foreach (var t in result)
{
	Console.WriteLine("OrderID:" 
		+ t.OrderID 
		+ "-" 
		+ "OrderDate:" 
		+ t.OrderDate.Value.ToString("yyyy-MM-dd"));
}

通过Linq to sql查询所有OrderID为10251的订单信息,并输出订单编号和订单日期。通过显示Linq的日志输出,我们可以看到后台生成的SQL语句如下:

1
2
3
4
5
6
SELECT [t0].[OrderID], [t0].[CustomerID], [t0].[EmployeeID], [t0].[OrderDate], [
t0].[RequiredDate], [t0].[ShippedDate], [t0].[ShipVia], [t0].[Freight], [t0].[Sh
ipName], [t0].[ShipAddress], [t0].[ShipCity], [t0].[ShipRegion], [t0].[ShipPosta
lCode], [t0].[ShipCountry]
FROM [dbo].[Orders] AS [t0]
WHERE [t0].[OrderID] = @p0

输出的SQL看来还比较正常。下面我们再来改一下我们的程序:

?View Code CSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 foreach (var t in result)
            {
                Console.WriteLine("OrderID:" + t.OrderID 
		+ "-OrderDate:" 
		+ t.OrderDate.Value.ToString("yyyy-MM-dd") 
		+"-CustomerName:"
		+ t.Customer.ContactName);
                foreach(var m in t.Order_Details)
                Console.WriteLine("ProductID:" 
		+ m.ProductID 
		+ "-Price:" 
		+ m.UnitPrice 
		+ "-Amount:" 
		+ m.Quantity);
            }

出了输出订单相关信息外,还输出其关联对象:客户姓名、订单中的产品编号、单价、数量

再来看看输出的SQL语句:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
SELECT [t0].[OrderID], [t0].[CustomerID], [t0].[EmployeeID], [t0].[OrderDate], [
t0].[RequiredDate], [t0].[ShippedDate], [t0].[ShipVia], [t0].[Freight], [t0].[Sh
ipName], [t0].[ShipAddress], [t0].[ShipCity], [t0].[ShipRegion], [t0].[ShipPosta
lCode], [t0].[ShipCountry]
FROM [dbo].[Orders] AS [t0]
WHERE [t0].[OrderID] = @p0
 
 
SELECT [t0].[CustomerID], [t0].[CompanyName], [t0].[ContactName], [t0].[ContactT
itle], [t0].[Address], [t0].[City], [t0].[Region], [t0].[PostalCode], [t0].[Coun
try], [t0].[Phone], [t0].[Fax]
FROM [dbo].[Customers] AS [t0]
WHERE [t0].[CustomerID] = @p0
 
 
SELECT [t0].[OrderID], [t0].[ProductID], [t0].[UnitPrice], [t0].[Quantity], [t0]
.[Discount]
FROM [dbo].[Order Details] AS [t0]
WHERE [t0].[OrderID] = @p0

我们可以看到,对于我们修改后的代码,程序向数据库请求了三条SQL语句,当然,这还不是最坏的情况,但是我们在这里的确看到延迟加载似乎“变了味道”,不但没有节省开支,反而增大了网络浏览。怎样才能改善这样的情况呢?

关于立即加载

其实我们知道,有很多扩展方法会导致延迟加载失效,而开始立即执行。当我们在调用诸如:ToList、ToDictionary、ToLookup或者ToArray之类的扩展方法之后,程序会将最终的结果存放到某个临时的变量集合中,并让所有的数据一次性的加载完成。

另外,还有一种方式,通过设置DataContext的DeferredLoadingEnabled属性为false,显示的关闭默认的延迟加载方式。

?View Code CSHARP
1
2
LinqTestDataContext ctx = new LinqTestDataContext();
ctx.DeferredLoadingEnabled = false;

 

这些方式虽然比较方便,但是还是有一定的局限性。例如,简单的使用ToList只能解决一些简单的查询问题,而对于复杂的查询需求,ToList还是不能解决延迟取得子对象所引发的多次查询问题。并且,在大量数据被加载到内存中的时候,对内存的需求也是很大的。不过,幸好Linq to sql给我们提供了另外一套不错的方法。

使用DataLoadOptions实现对加载对象的优化

Linq to Sql提供DataLoadOptions,用以立即加载关联的对象数据,其中包含两种方法:

LoadWith方法,用于立即加载与主对象相关联的数据

AssociateWith方法,用于对关联对象的数据进行筛选,并加载

有了DataLoadOptions,我们就可以用如下的方式优化我们的查询中需要加载的对象:

?View Code CSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
            LinqTestDataContext ctx = new LinqTestDataContext();
            ctx.Log = Console.Out;
 
            DataLoadOptions dl = new DataLoadOptions();
            dl.LoadWith<order>(p => p.Customer);
            dl.LoadWith<order>(p => p.Order_Details);
            ctx.LoadOptions = dl;
 
            var result = ctx.Orders.Where(p => p.OrderID == 10251).ToList();
 
            foreach (var t in result)
            {
                Console.WriteLine("OrderID:" + 
		t.OrderID 
		+ "-OrderDate:" 
		+ t.OrderDate.Value.ToString("yyyy-MM-dd") 
		+"-CustomerName:"
		+ t.Customer.ContactName);
                foreach(var m in t.Order_Details)
                Console.WriteLine("ProductID:" 
		+ m.ProductID 
		+ "-Price:" 
		+ m.UnitPrice 
		+ "-Amount:" 
		+ m.Quantity);
            }

对于我们开发者而言,需要注意的是,对于同一个DataContext实例,DataLoadOptions只能设定一次,并且一旦设定,就无法更改。

接下来,运行程序,看看优化后,程序向数据库服务器请求的的SQL:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
SELECT [t0].[OrderID], [t0].[CustomerID], [t0].[EmployeeID], [t0].[OrderDate], [
t0].[RequiredDate], [t0].[ShippedDate], [t0].[ShipVia], [t0].[Freight], [t0].[Sh
ipName], [t0].[ShipAddress], [t0].[ShipCity], [t0].[ShipRegion], [t0].[ShipPosta
lCode], [t0].[ShipCountry], [t3].[OrderID] AS [OrderID2], [t3].[ProductID], [t3]
.[UnitPrice], [t3].[Quantity], [t3].[Discount], (
    SELECT COUNT(*)
    FROM [dbo].[Order Details] AS [t4]
    WHERE [t4].[OrderID] = [t0].[OrderID]
    ) AS [value], [t2].[test], [t2].[CustomerID] AS [CustomerID2], [t2].[Company
Name], [t2].[ContactName], [t2].[ContactTitle], [t2].[Address], [t2].[City], [t2
].[Region], [t2].[PostalCode], [t2].[Country], [t2].[Phone], [t2].[Fax]
FROM [dbo].[Orders] AS [t0]
LEFT OUTER JOIN (
    SELECT 1 AS [test], [t1].[CustomerID], [t1].[CompanyName], [t1].[ContactName
], [t1].[ContactTitle], [t1].[Address], [t1].[City], [t1].[Region], [t1].[Postal
Code], [t1].[Country], [t1].[Phone], [t1].[Fax]
    FROM [dbo].[Customers] AS [t1]
    ) AS [t2] ON [t2].[CustomerID] = [t0].[CustomerID]
LEFT OUTER JOIN [dbo].[Order Details] AS [t3] ON [t3].[OrderID] = [t0].[OrderID]
 
WHERE [t0].[OrderID] = @p0
ORDER BY [t0].[OrderID], [t2].[CustomerID], [t3].[ProductID]

可以看出,之前的分三次向数据库提交sql的情况,现在被程序优化为一条带LEFT JOIN的关联查询,而获取关联数据。三次SQL请求,被优化为一次,从而减少了数据库和网络流量开支,由此看来DataLoadOptions的好处不言而喻。

一点小结

延迟加载与立即加载,并无孰优孰劣之区别,在某些情况下,需要我们根据自己的需求和实际情况来选择来进行选择。

开源的作业调度框架:Quartz.NET

278 views 十二月 05, 09 by Timothy

Quartz.NET是一套开源的作业调度框架,是最初由Java平台的企业级开源作业调度框架 Quartz 移植到.NET平台的。在sourceforge上,Quartz.NET的主页有这样的介绍: Quartz.NET is a full-featured, open source job scheduling system that can be used from smallest apps to large scale enterprise systems. (Quartz.NET是能应用在小到轻量级的应用程序,大到重量级的企业级系统中的全功能的开源任务调度系统)并且,值得令人信服的是,Quartz.NET已经在生产环境中得以用,并得到了良好的反馈。

回想一下,在小到普通的应用程序,大到企业级项目中,我们常常会遇到需要定时轮询和调度的场景,简单点的需求是每隔固定的秒数或者分钟、小时进行轮询,复杂点的是每周的某个时刻,或者每月,或者每年才执行一次,又或者更复杂的情景,某些天需要执行,某些天不执行。用Windows自带的计划任务,能够实现简单的逻辑,如果要用程序实现这样的逻辑,想必也很复杂了。其实,用Quartz能很好的应付这些情况。

Quartz.NET的核心模块分为几个部分,Scheduler、Job、JobDetail、Trigger

Scheduler:实现调度的主体,负责调度任务的开始、结束,Scheduler中包含已注册的JobDetail和Trigger,并且,当Trigger中的条件得以触发时,Scheduler负责执行和Trigger相关联的Job,实现任务的调度。
Job:需要被调度的任务,Job必须实现IJob接口的Execute方法,而Execute方法的内容,其实也就是我们需要根据不同的需求自己实现的业务逻辑。
JobDetail:包含对Job的具体描述,包括Job的名称、所属的Group名称、以及Job的Type描述,用以Quartz.NET通过反射实现对Job的调用。
Trigger:触发器,描述调度的触发条件和时机。

Quartz.NET之所以没把Job和Trigger设计在一起,而是采用一种松耦合的方式,把Job和Trigger独立为两个不同的对象,原因是因为在Quartz.NET中,Job和Trigger可以建立一种一对多的关系,也就是可以对一个调度任务绑定多个Trigger,另外的好处是当某个Job的Trigger过期的时候,可以直接为Job绑定新的Trigger而不用重新定义和这个Trigger相关联的Job。

最初见到Quartz.NET,还是0.6的版本,一直躺在偶的硬盘里面,直到最近项目中遇到很多需要定时轮询处理的情况,突然想到了Quartz.NET,打开它在sourceforge的大本营,才发现Quartz.NET已经更新到1.0.1版本了。

Quartz.NET为我们提供了简洁的API,通过简单的例子,我们就可以很快上手和使用

在Quartz.NET中,所有的Job都必须实现IJob接口:

?View Code CSHARP
1
2
3
4
5
6
7
namespace Quartz
{
    public interface IJob
    {
        void Execute(JobExecutionContext context);
    }
}

JobExecutionContext 包含运行时的一些上下文信息,还可以用来传递参数

编写我们自己的Job,实现IJob接口:

?View Code CSHARP
1
2
3
4
5
6
7
8
9
10
11
12
using Quartz;
 
namespace ExampleJob
{
    public class TestJob : IJob
    {
        public void Execute(JobExecutionContext context)
        {
            Console.WriteLine("Job executed!");
        }
    }
}

然后就可以通过Scheduler来调度任务了

?View Code CSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 创建SchedulerFactory
ISchedulerFactory schedFact = new StdSchedulerFactory();
 
// 获取Scheduler实例
IScheduler sched = schedFact.GetScheduler();
sched.Start();
 
// 创建JobDetail
JobDetail jobDetail = new JobDetail("ExampleJob", "MyJobGroup", typeof(ExampleJob));
// 创建Trigger,每分钟触发一次
Trigger trigger = TriggerUtils.MakeMinutelyTrigger();
// 设定trigger的开始时间
trigger.StartTimeUtc = TriggerUtils.GetEvenHourDate(DateTime.UtcNow);
trigger.Name = "myTrigger";
sched.ScheduleJob(jobDetail, trigger);

这样,任务调度框架便会每分钟调度一次我们的Job,在控制台每分钟都能看到一句 ”Job executed!”,是不是很简单呢?

其实Quartz.NET还有很多方便开发人员的特性和高级功能,比如通过配置文件配置Job和Trigger,可以让你的调度程序更加灵活,以应对后续需求的变化,而不需要重新编译程序。另外,还有调度线程池、JobStore、Clustering等很多高级功能,等着我们去发现。

相关资源:

Quartz.NET 主页:http://quartznet.sourceforge.net

在sourceforge的Quartz.NET主页上,有入门教程供初学者查看:http://quartznet.sourceforge.net/tutorial/index.html

另外,博客园的张善友也把教程翻译成了中文的,供E文不好的同学学习和参考,地址:http://www.cnblogs.com/shanyou/archive/2007/08/25/QuartzNETtutorial.html

常用扩展方法收集&整理(置顶-不断更新)

613 views 十一月 14, 09 by Timothy

扩展方法,是.NET 3.5中引入的新特性,在《扩展方法使用小结中》,我有具体的介绍。合理的使用扩展方法,能节约不少的代码量,甚至能在开发中给我们带来意想不到的效果,让代码更加的简洁、易懂。其实,网上早就有了不少的大牛写的各种出色的扩展方法,以至于我有了整理一个扩展方法库的想法,把一些实用、优秀的扩展方法收集起来,一来为资源共享,二来也是为了应用在以后的项目代码中,提高开发效率。 Read the rest of this entry »

扩展方法使用小结

168 views 九月 28, 09 by Timothy

随着.Net Framework一路走来,已经让广大开发人员体验到快速开发的甜头,这得益于.Net Framework为我们提供了更高层次的封装,开发人员不必关心底层的Win32 API及其繁琐的调用参数,而可以把大部分的经历放在对业务的分析和实现。随着.Net的不断革新,也引入了更多的特性,例如C# 2.0,就增加了匿名方法和迭代器,这些特性让我们的编码效率更高。随着C# 3.0的推出,引入了更多的新特性,包括:隐式类型局部变量、对象初始化器、Lambda表达式、扩展方法、匿名类型。

而这些特性,都为LINQ的推出,构建好了基础。其中一个比较不错的特性,就是扩展方法。扩展方法,顾名思义,就是在类型定义完成之后,再继续为其添加新的方法。这是相当方便的,比如对于一个已经封装好的Assembly来说,我们不用改动Assembly的代码,而通过扩展方法就能实现对其功能的扩展,而且在调用的时候,仅仅通过代码,你几乎判断不出这是扩展方法,还是Assembly本身的方法。

扩展方法在.Net 3.5中的应用也是非常普遍的,如果你仔细观察,就会发现我们常用的Linq to Object中的Where,Select,Average,Sum等方法,以及Linq to SQL中的Where,Select,Average,Sum等方法,其实都是扩展方法,他们分别定义于System.Linq.Enumerable类和System.Linq.Queryable类之中。

就扩展方法本身而言,也存在一些限制之处。对于编译器来说,如果扩展方法和被扩展类型的方法发生冲突的时候,在调用此方法的时候,究竟是调用被扩展类型的方法,还是扩展方法呢?通过一个例子,我们就能发现其中的区别。

   1:    class Program
   2:      {
   3:          static void Main(string[] args)
   4:          {
   5:              new TestClassA().Display("Test");
   6:              new TestClassB().Display("Test");
   7:              Console.ReadLine();
   8:          }
   9:      }
  10:  
  11:      class TestClassA
  12:      {
  13:          public void Display(int b)
  14:          {
  15:              Console.WriteLine("This is TestClassA.Display() ...");
  16:          }
  17:      }
  18:  
  19:      class TestClassB
  20:      {
  21:          public void Display(string s)
  22:          {
  23:              Console.WriteLine("This is TestClassB.Display() ...");
  24:          }
  25:      }
  26:  
  27:      static class TestClassExtention
  28:      {
  29:          static public void Display(this object o, string s)
  30:          {
  31:              Console.WriteLine("This is TestClassExtention.Display() ...");
  32:          }
  33:      }

程序输出:

ExtentionMethod

可以看出,TestClassA的方法和扩展方法并没有冲突,因为他们的方法签名是不一样的,而TestClassB的方法和扩展方法有冲突,因为他们的都是接受一个string类型的输入参数。从结果可以看到,程序对Display的方法调用,TestClassB本身的Display方法,要“优先”于扩展方法Display被调用。因此,类本身的方法如果满足调用条件,那么这个方法会被优先执行,只有在类当中无法找到同样参数的方法时,扩展方法才有机会被执行。所以,我们可以得出这样的结论:扩展方法的优先级较低,也即扩展方法不会覆盖同名的类本身的方法。

另外,还有一个比较明显的区别,就是扩展方法要远远弱于类本身的方法,比如,在一个类中,类的方法可以访问自己的非公有成员,而扩展方法做不到这一点。

Page 1 of 512345