千淘万漉虽辛苦,吹尽狂沙始到金

------记录,收集跟技术相关的文章,工具,软件

C# => Lambda表达式

"Lambda表达式"是一个匿名函数,是一种高效的类似于函数式编程的表达式,Lambda简化了开发中需要编写的代码量。它可以包含表达式和语句,并且可用于创建委托或表达式目录树类型,支持带有可绑定到委托或表达式树的输入参数的内联表达式。所有Lambda表达式都使用Lambda运算符=>,该运算符读作"goes to"。Lambda运算符的左边是输入参数(如果有),右边是表达式或语句块。Lambda表达式x => x * x读作"x goes to x times x"。可以将此表达式分配给委托类型,如下所示:

  1. delegate int del(int i);  
  2. del myDelegate = x => x * x;  
  3. int j = myDelegate(5); //j = 25 

s

namespace LambdaLearn
{
    public class Person
    {
        public string Name { get; set; }
        public int Age  {  get;set; }    
    }
    class Program
    {

        public static List<Person> PersonsList()//方法返回Person类的List集合
        {
            List<Person> persons = new List<Person>();
            for (int i = 0; i < 7; i++)
            {
                Person p = new Person() { Name = i + "人物年龄", Age = 8 - i, };
                persons.Add(p);                
            }
            return persons;
        }

        static void Main(string[] args)
        {
            List<Person> persons0 = PersonsList();
            List<Person> persons1 = persons.Where(p => p.Age > 6).ToList();   //所有Age>6的Person的集合
            Person per = persons.SingleOrDefault(p => p.Age == 1);  //Age=1的单个people类
            List<Person> persons2 = persons.Where(p => p.Name.Contains("年龄")).ToList();   //所有Name包含年龄的Person的集合
        }
    }
}
用lambda表达式简化委托
利用委托处理方法:
//委托  逛超市
        delegate int GuangChaoshi(int a);
        static void Main(string[] args)
        {
            GuangChaoshi gwl = JieZhang;
            Console.WriteLine(gwl(10) + "");   //打印20,委托的应用
            Console.ReadKey();
        }
        
        //结账
        public static int JieZhang(int a)
        {
            return a + 10;
        }

利用lambda表达式处理方法:

//委托  逛超市
        delegate int GuangChaoshi(int a);
        static void Main(string[] args)
        {          
           // GuangChaoshi gwl = JieZhang;
            GuangChaoshi gwl = p => p + 10;
            Console.WriteLine(gwl(10) + "");   //打印20,表达式的应用
            Console.ReadKey();
        }
委托跟表达式的两段代码,我们应该能明白了:其实表达式(p => p + 10;)中的 p 就代表委托方法中的参数,而表达式符号右边的 p+10,就是委托方法中的返回结果。
 
再看一个稍微复杂一点的例子:
//委托  逛超市
        delegate int GuangChaoshi(int a,int b);
        static void Main(string[] args)
        {            
            GuangChaoshi gwl = (p,z) => z-(p + 10);
            Console.WriteLine(gwl(10,100) + "");   //打印80,z对应参数b,p对应参数a
            Console.ReadKey();
        }
[code]csharpcode:
/// <summary>
        /// 委托  逛超市
        /// </summary>
        /// <param name="a">花费</param>
        /// <param name="b">付钱</param>
        /// <returns>找零</returns>
        delegate int GuangChaoshi(int a,int b);
        static void Main(string[] args)
        {
            GuangChaoshi gwl = (p, z) =>
            {
                int zuidixiaofei = 10;
                if (p < zuidixiaofei)
                {
                    return 100;
                }
                else
                {
                    return z - p - 10;
                }
           
            };
            Console.WriteLine(gwl(10,100) + "");   //打印80,z对应参数b,p对应参数a
            Console.ReadKey();
        }
 接下来看一下lambda的具体写法形式:隐式表达即没有指定参数类型(因为编译器能够根据上下文直接推断参数的类型)
(x, y) => x * y              //多参数,隐式类型=>表达式  
x => x * 5                   //单参数,隐式类型=>表达式  
x => { return x * 5; }       //单参数,隐式类型=>语句块  
(int x) => x * 5             //单参数,显式类型=>表达式  
(int x) => { return x * 5; } //单参数,显式类型=>语句块  
() => Console.WriteLine()    //无参数 
Lambda表达式的本质是“匿名方法”,即当编译我们的程序代码时,“编译器”会自动将“Lambda表达式”转换为“匿名方法”,如下例:
string[] names={"agen","balen","coure","apple"};
string[] findNameA=Array.FindAll<string>(names,delegate(string v){return v.StartsWith("a");});
string[] findNameB=Array.FindAll<string>(names,v=>v.StartsWith("a"));
上面中两个FindAll方法的反编译代码如下:
string[]findNameA=Array.FindAll<string>(names,delegate(stringv){returnv.StartsWith("a");});
string[]findNameB=Array.FindAll<string>(names,delegate(stringv){returnv.StartsWith("a");});
Lambda表达式的语法格式:

参数列表 => 语句或语句块

其中“参数列”中可包含任意个参数(与委托对应),如果参数列中有0个或1个以上参数,则必须使用括号括住参数列,如下:

() => Console.Write("0个参数")

I => Console.Write("1个参数时参数列中可省略括号,值为:{0}",i)

(x,y) => Console.Write("包含2个参数,值为:{0}*{1}",x,y)

而“语句或语句块”中如果只有一条语句,则可以不用大括号括住否则必须使用,如下:

I => Console.Write("只有一条语句")

I => { Console.Write("使用大括号的表达式"); }

//两条语句时必须要大括号

I => { i++;Console.Write("两条语句的情况"); }

如果“语句或语句块”有返回值时,如果只有一条语句则可以不输写“return”语句,编译器会自动处理,否则必须加上,如下示例:

“Lambda表达式”是委托的实现方法,所以必须遵循以下规则:

1)“Lambda表达式”的参数数量必须和“委托”的参数数量相同;

2)如果“委托”的参数中包括有ref或out修饰符,则“Lambda表达式”的参数列中也必须包括有修饰符
 

下面来看一下,如何自己定义和使用Lambda表达式,首先写下面一个函数:

    public void LambdaFun(string str,Func<string,string> func)
      {
         Console.WriteLine(func(str));
      }

这里用到了Func<T>委托,这个方法什么都没有做,只是调用了委托方法,并将参数传递过去,下面来看一下使用方法:

   LambdaFun("BeiJing 2013", s => 
         {
            if (s.Contains("2013"))
            {
               s = s.Replace("2013", "2014");
            }
            return s;
         });

这里将传入字符串中的2013替换成为2014,当然还可以是将其他字符串替换城任何内容,或者是截取,连接等等,完全由我们传入的Lambda表达式决定,到了这里感觉到Lambda表达式的强大了吧。

 

C#委托的学习

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CSharp_委托的学习
{
    class Program
    {
        //1、什么是委托及最简单的委托定义与实例化
        //如果我们需要把方法当做参数来传递的话,我们就需要用到委托。委托是一个类型,可以指向一个方法。委托需要定义和实例化。
        //最简单的委托:
        private delegate void Print();//定义了一个无返回类型、名称为Print的委托。

        static void Main(string[] args)
        {
           Print print1 = printStr;//用Print类型的委托实例化了一个委托print1,并指向函数printStr。
            print1();//调用委托即相当于调用函数printStr();
        }
        static void printStr()
        {
            Console.WriteLine("hello world");
        }


        //2、委托可以作为参数传递
        private delegate void Print();//定义了一个无返回类型、名称为Print的委托。

        static void Main(string[] args)
        {
           Print print1 = printStr;//用Print类型的委托实例化了一个委托print1,并指向函数printStr。
            printDelegateMethod(print1);//print1作为参数传递,最终调用函数printStr()
        }

        static void printStr()
        {
            Console.WriteLine("hello world");
        }
        static void printDelegateMethod(Print printTemp)
        {
            printTemp();
        }

        //3、内置委托:Action类型委托
        //Action类型委托的特点:无返回类型的委托即返回类型为void
        //定义格式:T表示方法的参数,最多不超过16个
        //Action
        //Action<in T>
        //Action<in T1, in T2>
        //Action<in T1, in T2....in T16>
        static void Main(string[] args)
        {
            Action a1 = printStr;//用Action类型委托实例化一个委托a1,并指向printStr
            a1();//调用a1即相当于调用函数printStr

            Action<string> a2 = printOneStr;//用Action类型委托实例化一个含有一个string类型参数的委托a2,并指向printOneStr
            a2("hello, I Invoke printOneStr");//调用a2即相当于调用函数printOneStr

            Action<string, int> a3 = printTwoStr;
            a3("剩余血量:", 50);
        }

        static void printStr()
        {
            Console.WriteLine("hello world");
        }
        static void printOneStr(string s1)
        {
            Console.WriteLine(s1);
        }
        static void printTwoStr(string s1, int i2)
        {
            Console.WriteLine(s1+i2);
        }

        //3、内置委托:Func类型委托
        //Func类型委托的特点:带有一个返回值的委托
        //定义格式:T表示方法的参数, TReturn表示返回值类型,T最多不超过16个
        //Func
        //Func<out TReturn>
        //Func<in T1, in T2>
        //Func<in T1, in T2....in T16, out TReturn>
        static void Main(string[] args)
        {
           Func<int> a1 = Test1;//用Func类型委托实例化一个委托a1,并指向Test1
            Console.WriteLine(a1());//调用a1即相当于调用函数Test1

            Func<float, string> a2 = Test2;//用Func类型委托实例化一个含有一个float类型参数,返回类型为string的委托a2,并指向Test2
            Console.WriteLine(a2(3.14f));//调用a2即相当于调用函数Test2

            Func<string, int, string> a3 = Test3;
            Console.WriteLine(a3("剩余血量:", 50));
        }

        static int Test1()
        {
            return 1;
        }
        static string Test2(float f1)
        {
            return f1.ToString();
        }
        static string Test3(string s1, int i2)
        {
            return (s1 + i2);
        }

        //多播委托:委托里包含多个方法(函数),调用多播委托可以同时调用这些方法
        private delegate void Print();//定义了一个无返回类型、名称为Print的委托。

        static void Main(string[] args)
        {
            Print print = print1;//用Print类型的委托实例化了一个委托print,并指向函数print1。
            print();//调用委托print即相当于调用函数print1();

            print += print2;//并没有直接指向print2,而是加上print2
            print();//调用委托print即相当于调用函数print1()和print2();
        }

        static void print1()
        {
            Console.WriteLine("hello ");
        }
        static void print2()
        {
            Console.WriteLine("world");
        }

    }
}

 

C# 之泛型详解

什么是泛型
       我们在编写程序时,经常遇到两个模块的功能非常相似,只是一个是处理int数据,另一个是处理string数据,或者其他自定义的数据类型,但我们没有办法,只能分别写多个方法处理每个数据类型,因为方法的参数类型不同。有没有一种办法,在方法中传入通用的数据类型,这样不就可以合并代码了吗?泛型的出现就是专门解决这个问题的。读完本篇文章,你会对泛型有更深的了解。

为什么要使用泛型
为了了解这个问题,我们先看下面的代码,代码省略了一些内容,但功能是实现一个栈,这个栈只能处理int数据类型:

public class Stack

    {

        private int[] m_item;

        public int Pop(){...}

        public void Push(int item){...}

        public Stack(int i)

        {

            this.m_item = new int[i];

        }

}

上面代码运行的很好,但是,当我们需要一个栈来保存string类型时,该怎么办呢?很多人都会想到把上面的代码复制一份,把int改成string不就行了。当然,这样做本身是没有任何问题的,但一个优秀的程序是不会这样做的,因为他想到若以后再需要long、Node类型的栈该怎样做呢?还要再复制吗?优秀的程序员会想到用一个通用的数据类型object来实现这个栈:

public class Stack

    {

        private object[] m_item;

        public object Pop(){...}

        public void Push(object item){...}

        public Stack(int i)

        {

            this.m_item = new[i];

        }

      

    }

这个栈写的不错,他非常灵活,可以接收任何数据类型,可以说是一劳永逸。但全面地讲,也不是没有缺陷的,主要表现在:

当Stack处理值类型时,会出现装箱、折箱操作,这将在托管堆上分配和回收大量的变量,若数据量大,则性能损失非常严重。 
在处理引用类型时,虽然没有装箱和折箱操作,但将用到数据类型的强制转换操作,增加处理器的负担。 
在数据类型的强制转换上还有更严重的问题(假设stack是Stack的一个实例):
Node1 x = new Node1();

            stack.Push(x);

         Node2 y = (Node2)stack.Pop();

上面的代码在编译时是完全没问题的,但由于Push了一个Node1类型的数据,但在Pop时却要求转换为Node2类型,这将出现程序运行时的类型转换异常,但却逃离了编译器的检查。

 

针对object类型栈的问题,我们引入泛型,他可以优雅地解决这些问题。泛型用用一个通过的数据类型T来代替object,在类实例化时指定T的类型,运行时(Runtime)自动编译为本地代码,运行效率和代码质量都有很大提高,并且保证数据类型安全。

 

使用泛型
下面是用泛型来重写上面的栈,用一个通用的数据类型T来作为一个占位符,等待在实例化时用一个实际的类型来代替。让我们来看看泛型的威力:

public class Stack<T>

    {

        private T[] m_item;

        public T Pop(){...}

        public void Push(T item){...}

        public Stack(int i)

        {

            this.m_item = new T[i];

        }

}

类的写法不变,只是引入了通用数据类型T就可以适用于任何数据类型,并且类型安全的。这个类的调用方法:

//实例化只能保存int类型的类

Stack<int> a = new Stack<int>(100);

      a.Push(10);

      a.Push("8888"); //这一行编译不通过,因为类a只接收int类型的数据

      int x = a.Pop();

 

//实例化只能保存string类型的类

Stack<string> b = new Stack<string>(100);

b.Push(10);    //这一行编译不通过,因为类b只接收string类型的数据

      b.Push("8888");

string y = b.Pop();

 

这个类和object实现的类有截然不同的区别:

1.       他是类型安全的。实例化了int类型的栈,就不能处理string类型的数据,其他数据类型也一样。

2.       无需装箱和折箱。这个类在实例化时,按照所传入的数据类型生成本地代码,本地代码数据类型已确定,所以无需装箱和折箱。

3.       无需类型转换。

 

泛型类实例化的理论
C#泛型类在编译时,先生成中间代码IL,通用类型T只是一个占位符。在实例化类时,根据用户指定的数据类型代替T并由即时编译器(JIT)生成本地代码,这个本地代码中已经使用了实际的数据类型,等同于用实际类型写的类,所以不同的封闭类的本地代码是不一样的。按照这个原理,我们可以这样认为:

泛型类的不同的封闭类是分别不同的数据类型。

例:Stack<int>和Stack<string>是两个完全没有任何关系的类,你可以把他看成类A和类B,这个解释对泛型类的静态成员的理解有很大帮助。

泛型类中数据类型的约束
程序员在编写泛型类时,总是会对通用数据类型T进行有意或无意地有假想,也就是说这个T一般来说是不能适应所有类型,但怎样限制调用者传入的数据类型呢?这就需要对传入的数据类型进行约束,约束的方式是指定T的祖先,即继承的接口或类。因为C#的单根继承性,所以约束可以有多个接口,但最多只能有一个类,并且类必须在接口之前。这时就用到了C#2.0的新增关键字:

public class Node<T, V> where T : Stack, IComparable

        where V: Stack

    {...}

以上的泛型类的约束表明,T必须是从Stack和IComparable继承,V必须是Stack或从Stack继承,否则将无法通过编译器的类型检查,编译失败。

通用类型T没有特指,但因为C#中所有的类都是从object继承来,所以他在类Node的编写中只能调用object类的方法,这给程序的编写造成了困难。比如你的类设计只需要支持两种数据类型int和string,并且在类中需要对T类型的变量比较大小,但这些却无法实现,因为object是没��比较大小的方法的。 了解决这个问题,只需对T进行IComparable约束,这时在类Node里就可以对T的实例执行CompareTo方法了。这个问题可以扩展到其他用户自定义的数据类型。

如果在类Node里需要对T重新进行实例化该怎么办呢?因为类Node中不知道类T到底有哪些构造函数。为了解决这个问题,需要用到new约束:

public class Node<T, V> where T : Stack, new()

        where V: IComparable

需要注意的是,new约束只能是无参数的,所以也要求相应的类Stack必须有一个无参构造函数,否则编译失败。

C#中数据类型有两大类:引用类型和值类型。引用类型如所有的类,值类型一般是语言的最基本类型,如int, long, struct等,在泛型的约束中,我们也可以大范围地限制类型T必须是引用类型或必须是值类型,分别对应的关键字是class和struct:

public class Node<T, V> where T : class

        where V: struct

      where T : struct 类型必须是一种值类型(struct)

  where T : class 类型必须是一种引用类型(class)

  where T : new() 类型必须有一个无参数的构造器

  where T : class_name 类型可以是class_name或者是它的一个子类

  where T : interface_name 类型必须实现指定的接口

  你可以指定约束的组合,就象: where T : IComparable, new()。这就是说,用于参数化类型的类型必须实现Icomparable接口并且必须有一个无参构造器。

泛型方法
泛型不仅能作用在类上,也可单独用在类的方法上,他可根据方法参数的类型自动适应各种参数,这样的方法叫泛型方法。看下面的类:

public class Stack2

    {

        public void Push<T>(Stack<T> s, params T[] p)

        {

            foreach (T t in p)

            {

                s.Push(t);

            }

        }

}

原来的类Stack一次只能Push一个数据,这个类Stack2扩展了Stack的功能(当然也可以直接写在Stack中),他可以一次把多个数据压入Stack中。其中Push是一个泛型方法,这个方法的调用示例如下:

Stack<int> x = new Stack<int>(100);

    Stack2 x2 = new Stack2();

    x2.Push(x, 1, 2, 3, 4, 6);

    string s = "";

    for (int i = 0; i < 5; i++)

    {

        s += x.Pop().ToString();

    }    //至此,s的值为64321

   

 

泛型中的静态成员变量
在C#1.x中,我们知道类的静态成员变量在不同的类实例间是共享的,并且他是通过类名访问的。C#2.0中由于引进了泛型,导致静态成员变量的机制出现了一些变化:静态成员变量在相同封闭类间共享,不同的封闭类间不共享。

这也非常容易理解,因为不同的封闭类虽然有相同的类名称,但由于分别传入了不同的数据类型,他们是完全不同的类,比如:

Stack<int> a = new Stack<int>();

Stack<int> b = new Stack<int>();

Stack<long> c = new Stack<long>();

类实例a和b是同一类型,他们之间共享静态成员变量,但类实例c却是和a、b完全不同的类型,所以不能和a、b共享静态成员变量。

泛型中的静态构造函数
静态构造函数的规则:只能有一个,且不能有参数,他只能被.NET运行时自动调用,而不能人工调用。

泛型中的静态构造函数的原理和非泛型类是一样的,只需把泛型中的不同的封闭类理解为不同的类即可。以下两种情况可激发静态的构造函数:

1.       特定的封闭类第一次被实例化。

2.       特定封闭类中任一静态成员变量被调用。

 

泛型类中的方法重载
方法的重载在.Net Framework中被大量应用,他要求重载具有不同的签名。在泛型类中,由于通用类型T在类编写时并不确定,所以在重载时有些注意事项,这些事项我们通过以下的例子说明:

public class Node<T, V>

    {

        public T add(T a, V b)          //第一个add

        {

            return a;

        }

        public T add(V a, T b)          //第二个add

        {

            return b;

        }

        public int add(int a, int b)    //第三个add

        {

            return a + b;

        }

}

上面的类很明显,如果T和V都传入int的话,三个add方法将具有同样的签名,但这个类仍然能通过编译,是否会引起调用混淆将在这个类实例化和调用add方法时判断。请看下面调用代码:

Node<int, int> node = new Node<int, int>();

    object x = node.add(2, 11);

这个Node的实例化引起了三个add具有同样的签名,但却能调用成功,因为他优先匹配了第三个add。但如果删除了第三个add,上面的调用代码则无法编译通过,提示方法产生的混淆,因为运行时无法在第一个add和第二个add之间选择。

Node<string, int> node = new Node<string, int>();

        object x = node.add(2, "11");

   这两行调用代码可正确编译,因为传入的string和int,使三个add具有不同的签名,当然能找到唯一匹配的add方法。

由以上示例可知,C#的泛型是在实例的方法被调用时检查重载是否产生混淆,而不是在泛型类本身编译时检查。同时还得出一个重要原则:

当一般方法与泛型方法具有相同的签名时,会覆盖泛型方法。一般方法拥有最高优先权。

 

泛型类的方法重写
方法重写(override)的主要问题是方法签名的识别规则,在这一点上他与方法重载一样,请参考泛型类的方法重载。

 

泛型的使用范围
本文主要是在类中讲述泛型,实际上,泛型还可以用在类方法、接口、结构(struct)、委托等上面使用,使用方法大致相同,就不再讲述。

小结
C# 泛型是开发工具库中的一个无价之宝。它们可以提高性能、类型安全和质量,减少重复性的编程任务,简化总体编程模型,而这一切都是通过优雅的、可读性强的语法完成的。尽管 C# 泛型的根基是 C++ 模板,但 C# 通过提供编译时安全和支持将泛型提高到了一个新水平。C# 利用了两阶段编译、元数据以及诸如约束和一般方法之类的创新性的概念。毫无疑问,C# 的将来版本将继续发展泛型,以便添加新的功能,并且将泛型扩展到诸如数据访问或本地化之类的其他 .NET Framework 领域。

Partial View---分部视图

Partial View指可以应用于View中以作为其中一部分的View的片段(类似于之前的user control), 可以像类一样,编写一次, 然后在其他View中被反复使用。

一般放在"Views/Shared"文件夹中以共享。

创建Partial View:一般直接右键"Views/Shared"文件夹添加分部视图。

使用Partial View有两类helper :

Html.Partial / Html.RenderPartial

Html.Action / Html.RenderAction

创建分部视图

如下图,创建 _PartialPageWidget.cshtml

通过Html.Partial / Html.RenderPartial 使用

直接应用Html.Partial或Html.RenderPartial辅助方法比较简单。

打开之前建好的Views/MVCDemo/Index.cshtml文件,添加如下内容:

显示结果:

通过Html.Action / Html.RenderAction 使用

通过Html.Action/Html.RenderAction使用稍微复杂一点,分成两步。

  1. 在要显示的View所对应的Controller中心增加一个Action.

    还用上面这个页面,我们在MVCDemoController.cs中增加一个Action

    sss

    做个简单的说明:

    1. [ChildActionOnly] 表示这个Action只应作为子操作进行调用。也就是说直接通过 controller/action这样的网址是不能访问的,会提示只能由子请求访问的错误。
    2. 必须返回一个PartialView
  2. 在View中添加相关代码

显示结果和上一种方法一样

两种使用方式小结

当View中引用了一个或多个分部视图时,此View与各分部视图默认得到一样的数据,也就是说View及其中所有的Partial View默认情况下共享View中的ViewData和ViewBag.

需要使View和其中引入的Partial View有不同的数据,需要通过Html.Action/Html.RenderAction辅助方法, 并在对于被调用的Action中设置对应的数据。

另外还有几点需要注意:

  1. XXX和RenderXXX的区别在于,一个是直接返回字符串,另外一个是直接写入到相应输出流,因此不能直接放在代码表达式中,必须放在代码块中。

    前面的示例中两种写法是等价的。RenderXXX有轻微的性能优势,在大量的RenderXXX运行时,才能反映出性能上的优势。

  2. Partial/RenderPartial通常在单独的文件夹中应用视图标记来帮助View渲染视图模型的一部分。

    Action/RenderAction执行单独Controller中的Action来显示结果,提供了更多的灵活性,例如利用单独的Controller传递不同值。文章最后我们会举个例子说明。

  3. Partial/RenderPartial和Action/RenderAction的参数分别是 partialView和 Action的名字。当然还有其他的重载函数,我们只说最常用的。应用时可以按照我们前面举的例子。

Html.Partial和Html.Action差异举例

最后我们举个例子说明两种用法的差别

新建一个Partial View

Controller中新建两个Action,SharedDateDemo和PartialViewDate.

根据SharedDateDemo生成主View, PartialViewDate供Html.Action调用

主View: SharedDateDemo.cshtml分别显示

主View的时间;

用Html.Partial调用Partial View的时间;

用Html.Action调用Partial View的时间。

显示结果:

  1. 上面结果可以清晰的看到:

    1. View及其中所有的Partial View默认情况下共享View中的ViewData和ViewBag
    2. 可以通过Html.Action, 配合被调用的Action改变传递不同的数据

总结

 

本次我们主要讲解了Partial View这一实用功能的使用。

Html.Partial使用方便,在不需要改变数据内容时非常方便。

Html.Action 使用更加灵活,可以根据业务要求按需定制Partial View显示内容。

Repository 设计模式介绍

仓储(Respository)是存在于工作单元和数据库之间单独分离出来的一层,是对数据访问的封装。其优点:

1)业务层不需要知道它的具体实现,达到了分离关注点。

2)提高了对数据库访问的维护,对于仓储的改变并不会改变业务的逻辑,数据库可以用Sql Server

 

我们使用repository来将业务层和数据实体层分开来,业务逻辑层应该对组成数据源层的数据类型不可知,比如数据源可能是数据库或者Web service


在数据源层和业务层之间增加一个repository层进行协调,有如下作用:


1.从数据源中查询数据

2.映射数据到业务实体

3.将业务实体数据的修改保存到数据源 (持久化数据)

这样repository就将业务逻辑和基础数据源的交互进行了分隔。


数据和业务层的分离有如下三个优点:


1.集中管理不同的底层数据源逻辑。

2.给单元测试提供分离点。

3.提供弹性架构,整体设计可以适应程序的不断进化。

我们将会对原有做法进行两轮抽象,实现我们想要的效果。

仓储和工作单元模式是用来在数据访问层和业务逻辑层之间创建一个抽象层。

应用这些模式,可以帮助用来隔离你的程序在数据存储变化。

下图比较了不使用库模式和使用库模式时controller和context 交互方式的差异。

说明:库模式的实现有多种做法,下图是其中一种。

 

采用MiniProfiler监控EF与.NET MVC项目

MVC MiniProfiler是Stack Overflow团队设计的一款对ASP.NET MVC的性能分析的小程序。可以对一个页面本身,及该页面通过直接引用、Ajax、Iframe形式访问的其它页面进行监控,监控内容包括数据库内容,并可以显示数据库访问的SQL(支持EF、EF CodeFirst等 )。并且以很友好的方式展现在页面上。

该Profiler的一个特别有用的功能是它与数据库框架的集成。除了.NET原生的 DbConnection类,profiler还内置了对实体框架(Entity Framework)以及LINQ to SQL的支持。任何执行的Step都会包括当时查询的次数和所花费的时间。为了检测常见的错误,如N+1反模式,profiler将检测仅有参数值存在差 异的多个查询。

MiniProfiler是以Apache License V2.0协议发布的,你可以在NuGet找到。配置及使用可以看这里:http://code.google.com/p/mvc-mini-profiler

 

第一章:开始监控

首先,明确一下本博文的目标,监控EF的Sql和执行时间,监控MVC页面的执行时间

那么我们开始.

第一步,从NuGet上下载所需要的包,下载内容如图:

MiniProfiler核心(所有的MiniProfiler相关资源都需要先有他)

这里需要注意,新版本的MiniProfiler.EF是需要根据你的EF版本来下载的,分为MiniProfiler.EF6,MiniProfiler.EF5,MiniProfiler.EF(EF4以下)三个版本

根据你的EF版本自行下载对应的包.

MiniProfiler.MVC4(注:这里的MVC4是可以分析MVC4,5两个版本的,使用MVC3的同学请自行下载MiniProfiler.MVC3)

至此,我们所需要安装的程序包就全部OK了,

下面我们开始监控:

首先,给你的Global.asax文件中加入:

       protected void Application_BeginRequest()
        {
            if (Request.IsLocal)//这里是允许本地访问启动监控,可不写
            {
                MiniProfiler.Start();
               
            }
        }

        protected void Application_EndRequest()
        {
            MiniProfiler.Stop();
        }

然后找到你需要监控的页面,在页面中加入:

@using StackExchange.Profiling;
@MiniProfiler.RenderIncludes();

当然,我们一般是想监控所有的页,所以我推荐加在你的布局页(_Layout)中,比如以下这种结构:

<html>
<head>
    @using StackExchange.Profiling;
</head>
<body>
        @RenderBody()

    @MiniProfiler.RenderIncludes();
</body>
</html>

然后在配置文件web.config中加入(注意,这里很重要):

<system.webServer>
      <handlers>
        <add name="MiniProfiler" path="mini-profiler-resources/*" verb="*" type="System.Web.Routing.UrlRoutingModule" resourceType="Unspecified" preCondition="integratedMode" />
      </handlers>
  </system.webServer>

这样,我们的基础监控就已经完成了,我们来看看效果.

第二章:监控EF,并对某次操作进行针对性监控

首先我们在Global.asax文件中添加代码如下:

protected void Application_Start()
        {
  ....
               
            StackExchange.Profiling.EntityFramework6.MiniProfilerEF6.Initialize();
          ....
        }

因为这是一个简单的demo,所以我们随意找一个Controller,写一些EF的查询,代码如下:

public class HomeController : Controller
    {
        public ActionResult Index()
        {
           
                using (StudentInfoEntities us = new StudentInfoEntities())
                {
                    ViewBag.data = us.LogData.Where(a => 1 == 1).ToList();
                }
            

            
            return View();
        }
}

我们来看看效果.

可以看出来,这次查询用了56.2MS,占用整个页面的加载时间71%的比例.,点击蓝色的56.2可以看到详细的SQL语句,如下:

这样,我们就可以随时监控到页面中EF所使用的SQL语句并进行分析.

针对性监控(重要)

当然,这只是简单的操作,我们在分析的过程中肯定会碰到诡异,或者后台代码更复杂的情况(比如一个页面10个查询),这个时候页面上的监控就会很混乱,不方便读,我们就需要进行针对性的监控.

我们把刚刚的代码修改如下(这里我们进行两次查询操作,用MiniProfiler进行分类):

public class HomeController : Controller
    {
        public ActionResult Index()
        {
            var profiler = MiniProfiler.Current;
            using (profiler.Step("查询数据LogData的数据"))
            {
                using (StudentInfoEntities us = new StudentInfoEntities())
                {
                    ViewBag.data = us.LogData.Where(a => 1 == 1).ToList();
                }
            }

            using (profiler.Step("查询数据LogOperate的数据"))
            {
                using (StudentInfoEntities us = new StudentInfoEntities())
                {
                    ViewBag.data = us.LogOperate.Where(a => 1 == 1).ToList();
                }
            }
            return View();
        }
}

得到监控效果如下:

这样,我们就可以根据我们的需要来详细的跟踪某一次EF操作的结果了.

第三章:监控的权限(给管理员分配监控的权限)

在实际的项目开发中,我们不可能对所有的用户全部开放监控的权限,所以我们要对他进行显示的控制.

在MiniProfiler中,提供了两个委托,如下:

 MiniProfiler.Settings.Results_Authorize //配置监控的权限

MiniProfiler.Settings.Results_List_Authorize //配置历史信息监控的权限(在~/mini-profiler-resources/results-index中可以查看最近100次的请求分析)

这里我们简单的做一下权限控制,

我们在Global.asax文件中添加代码如下:

protected void Application_Start()
        {
        ....
            MiniProfiler.Settings.Results_Authorize = Request =>
            {
                string name = Request.Cookies["name"] == null ? "" : Request.Cookies["name"].Value;
                if (name.Equals("admin"))
                    return true;
                else
                    return false;
            };
               
            StackExchange.Profiling.EntityFramework6.MiniProfilerEF6.Initialize();

        }

这样就只有cookie的name属性为admin的用户才能有监控显示了

效果如下(我们可以看到,当cookie中的name等于admin的时候才会有监控的显示):