如何从C#中的MethodCallExpression调用方法

mikel阅读(667)

我有一个方法调用表达式,并尝试调用该方法。我想出了一种方法,但是在检索参数值时有问题,因为不是每个参数都用ConstantExpression描述。 

 

Expression<Action<T>> = t => t.DoSomething(Par0, Par1, Par2);
MethodCallExpression methodCallExpression = selector.Body 
                                               as MethodCallExpression;

// get the information which is needed to invoke the method from the provided 
// lambda expression.
MethodInfo methodInfo = methodCallExpression.Method;
object[] arguments = methodCallExpression.Arguments.OfType<ConstantExpression>()
                            .Select(p => p.Value).ToArray();

// invoke the expression on every item within the enumerable
foreach (TSource item in source)
{ 
    methodInfo.Invoke(item, arguments);
}

另外,我已经看到了一些其他的方法来调用该方法,现在我不知道该怎么做正确的方法。

 

var func = expression.Compile();
var success = func.Invoke();

所以我的问题是,如何从methodCallExpression.Arguments检索方法参数值?

还是有更简单的方法来实现我的目标?

您不需要担心自己检索参数并调用MethodInfo,您可以让.NET为您做。所有您需要做的是创建一个包含该方法的Lambda表达式。 

例如。

 

MethodCallExpression expression = GetExpressionSomeHow();
object result = Expression.Lambda(expression).Compile().DynamicInvoke();

这就是我在Linq提供者中处理嵌套查询的方式。

编辑:实际上,你可能已经在选择器变量中有一个LambdaExpression。在这种情况下,您应该可以直接编译和调用它:

 

object result = selector.Compile().DynamicInvoke();

Util应用程序框架公共操作类(七):Lambda表达式公共操作类 - 何镇汐 - 博客园

mikel阅读(527)

GetConstantValue

来源: Util应用程序框架公共操作类(七):Lambda表达式公共操作类 – 何镇汐 – 博客园

前一篇扩展了两个常用验证方法,本文将封装两个Lambda表达式操作,用来为下一篇的查询扩展服务。

Lambda表达式是一种简洁的匿名函数语法,可以用它将方法作为委托参数传递。在Linq中,大量使用Lambda表达式进行查询,不过这种Lambda表达式被Expression包装成表达式树。表达式树是解释器的一个实现,它的作用是将一种语法转换为另一种语法,比如将Lambda表达式解析为SQL语句。

使用SQL列名进行查询的主要问题是,列名是一个字符串,没有智能提示,如果输入错误,也没有编译时检查。使用Lambda表达式查询可以解决这些问题,这是使用强类型的主要好处,另外当列名与属性名不一致时,只需修改映射配置,业务代码不动,从而增强了系统的扩展性。

Lambda表达式的强类型在带来诸多好处的同时,也产生了一些问题,比如t => t.Name==”a”这个表达式,如果想把值”a”拿出来进行操作,怎么做到?值”a”对于查询条件来讲,是一个动态传入的参数,如果对表达式树完全不了解,这也不是一件轻松的事。另外还有动态查询的问题,这时候开始怀念弱类型的字符串了,微软提供了一个动态查询的辅助类来解决这个问题,待用到的时候我再介绍。

本文介绍的一个方法是GetValue,用来将Lambda谓词表达式中的值取出来,另一个方法是GetCriteriaCount,用来判断Lambda谓词表达式中条件的个数。这两个方法的具体应用将在下一篇介绍。

在Util项目中添加一个Lambda类,代码如下。

复制代码
using System.Linq;
using System.Linq.Expressions;

namespace Util {
    /// <summary>
    /// Lambda表达式操作
    /// </summary>
    public class Lambda {

        #region GetValue(获取值)

        /// <summary>
        /// 获取值,范例:t => t.Name == "A",返回 A
        /// </summary>
        /// <param name="expression">表达式,范例:t => t.Name == "A"</param>
        public static object GetValue( LambdaExpression expression ) {
            if ( expression == null )
                return null;
            BinaryExpression binaryExpression = GetBinaryExpression( expression );
            if ( binaryExpression != null )
                return GetBinaryValue( binaryExpression );
            var callExpression = expression.Body as MethodCallExpression;
            if ( callExpression != null )
                return GetMethodValue( callExpression );
            return null;
        }

        /// <summary>
        /// 获取二元表达式
        /// </summary>
        private static BinaryExpression GetBinaryExpression( LambdaExpression expression ) {
            var binaryExpression = expression.Body as BinaryExpression;
            if ( binaryExpression != null )
                return binaryExpression;
            var unaryExpression = expression.Body as UnaryExpression;
            if ( unaryExpression == null )
                return null;
            return unaryExpression.Operand as BinaryExpression;
        }

        /// <summary>
        /// 获取二元表达式的值
        /// </summary>
        private static object GetBinaryValue( BinaryExpression binaryExpression ) {
            var unaryExpression = binaryExpression.Right as UnaryExpression;
            if ( unaryExpression != null )
                return GetConstantValue( unaryExpression.Operand );
            return GetConstantValue( binaryExpression.Right );
        }

        /// <summary>
        /// 获取常量值
        /// </summary>
        private static object GetConstantValue( Expression expression ) {
            var constantExpression = expression as ConstantExpression;
            if ( constantExpression == null )
                return null;
            return constantExpression.Value;
        }

        /// <summary>
        /// 获取方法调用表达式的值
        /// </summary>
        private static object GetMethodValue( MethodCallExpression callExpression ) {
            var argumentExpression = callExpression.Arguments.FirstOrDefault();
            return GetConstantValue( argumentExpression );
        }

        #endregion

        #region GetCriteriaCount(获取谓词条件的个数)

        /// <summary>
        /// 获取谓词条件的个数
        /// </summary>
        /// <param name="expression">谓词表达式,范例:t => t.Name == "A"</param>
        public static int GetCriteriaCount( LambdaExpression expression ) {
            if ( expression == null )
                return 0;
            var result = expression.ToString().Replace( "AndAlso", "|" ).Replace( "OrElse", "|" );
            return result.Split( '|' ).Count();
        }

        #endregion
    }
}
复制代码

为了进行测试,需要创建一个测试样例类Test,代码如下。

复制代码
namespace Util.Tests.Samples {
    /// <summary>
    /// 测试
    /// </summary>
    public class Test {
        public string Name { get; set; }
        public int Age { get; set; }
        public int? NullableInt { get; set; }
        public decimal? NullableDecimal { get; set; }
        public TestA A { get; set; }
        public class TestA {
            public int Integer { get; set; }
            public string Address { get; set; }
            public TestB B { get; set; }
            public class TestB {
                public string Name { get; set; }
            }
        }
    }
}
复制代码

单元测试代码如下。

复制代码
using System;
using System.Linq.Expressions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Util.Tests.Samples;

namespace Util.Tests {
    /// <summary>
    /// Lambda表达式操作测试
    /// </summary>
    [TestClass]
    public class LambdaTest {

        #region GetValue(获取成员值)

        /// <summary>
        /// 获取成员值,委托返回类型为Object
        /// </summary>
        [TestMethod]
        public void TestGetValue_Object() {
            Expression<Func<Test, object>> expression = test => test.Name == "A";
            Assert.AreEqual( "A", Lambda.GetValue( expression ) );
        }

        /// <summary>
        /// 获取成员值,委托返回类型为bool
        /// </summary>
        [TestMethod]
        public void TestGetValue_Boolean() {
            //空值返回null
            Assert.AreEqual( null, Lambda.GetValue( null ) );

            //一级返回值
            Expression<Func<Test, bool>> expression = test => test.Name == "A";
            Assert.AreEqual( "A", Lambda.GetValue( expression ) );

            //二级返回值
            Expression<Func<Test, bool>> expression2 = test => test.A.Integer == 1;
            Assert.AreEqual( 1, Lambda.GetValue( expression2 ) );

            //三级返回值
            Expression<Func<Test, bool>> expression3 = test => test.A.B.Name == "B";
            Assert.AreEqual( "B", Lambda.GetValue( expression3 ) );
        }

        /// <summary>
        /// 获取可空类型的值
        /// </summary>
        [TestMethod]
        public void TestGetValue_Nullable() {
            //可空整型
            Expression<Func<Test, bool>> expression = test => test.NullableInt == 1;
            Assert.AreEqual( 1, Lambda.GetValue( expression ) );

            //可空decimal
            expression = test => test.NullableDecimal == 1.5M;
            Assert.AreEqual( 1.5M, Lambda.GetValue( expression ) );
        }

        /// <summary>
        /// 获取成员值,运算符为方法
        /// </summary>
        [TestMethod]
        public void TestGetValue_Method() {
            //1级返回值
            Expression<Func<Test, bool>> expression = t => t.Name.Contains( "A" );
            Assert.AreEqual( "A", Lambda.GetValue( expression ) );

            //二级返回值
            expression = t => t.A.Address.Contains( "B" );
            Assert.AreEqual( "B", Lambda.GetValue( expression ) );

            //三级返回值
            expression = t => t.A.B.Name.StartsWith( "C" );
            Assert.AreEqual( "C", Lambda.GetValue( expression ) );
        }

        #endregion

        #region GetCriteriaCount(获取谓词条件的个数)

        /// <summary>
        /// 获取谓词条件的个数
        /// </summary>
        [TestMethod]
        public void TestGetCriteriaCount() {
            //0个条件
            Assert.AreEqual( 0, Lambda.GetCriteriaCount( null ) );

            //1个条件
            Expression<Func<Test, bool>> expression = test => test.Name == "A";
            Assert.AreEqual( 1, Lambda.GetCriteriaCount( expression ) );

            //2个条件,与连接符
            expression = test => test.Name == "A" && test.Name == "B";
            Assert.AreEqual( 2, Lambda.GetCriteriaCount( expression ) );

            //2个条件,或连接符
            expression = test => test.Name == "A" || test.Name == "B";
            Assert.AreEqual( 2, Lambda.GetCriteriaCount( expression ) );

            //3个条件
            expression = test => test.Name == "A" && test.Name == "B" || test.Name == "C";
            Assert.AreEqual( 3, Lambda.GetCriteriaCount( expression ) );

            //3个条件,包括导航属性
            expression = test => test.A.Address == "A" && test.Name == "B" || test.Name == "C";
            Assert.AreEqual( 3, Lambda.GetCriteriaCount( expression ) );
        }

        /// <summary>
        /// 获取谓词条件的个数,运算符为方法
        /// </summary>
        [TestMethod]
        public void TestGetCriteriaCount_Method() {
            //1个条件
            Expression<Func<Test, bool>> expression = t => t.Name.Contains( "A" );
            Assert.AreEqual( 1, Lambda.GetCriteriaCount( expression ) );

            //2个条件,与连接
            expression = t => t.Name.Contains( "A" ) && t.Name == "A";
            Assert.AreEqual( 2, Lambda.GetCriteriaCount( expression ) );

            //2个条件,或连接,包含导航属性
            expression = t => t.Name.Contains( "A" ) || t.A.Address == "A";
            Assert.AreEqual( 2, Lambda.GetCriteriaCount( expression ) );
        }

        #endregion
    }
}
复制代码

需要注意的是,GetValue方法不仅要能获取t=>t.Name==”a”这样的二元表达式,还要能获取方法调用表达式中的值,比如t=>t.Name.Contains(“a”)。

下面再增加一个扩展方法,在Util项目中添加名为Extensions.Expression的文件,代码如下。

复制代码
using System;
using System.Linq.Expressions;

namespace Util {
    /// <summary>
    /// 表达式扩展
    /// </summary>
    public static partial class Extensions {

        #region Value(获取lambda表达式的值)

        /// <summary>
        /// 获取lambda表达式的值
        /// </summary>
        /// <typeparam name="T">对象类型</typeparam>
        public static object Value<T>( this Expression<Func<T, bool>> expression ) {
            return Lambda.GetValue( expression );
        }

        #endregion
    }
}
复制代码

 

Lambda表达式不仅在查询上大展身手,而且在表现层,比如Mvc上也有大量的应用。本文只介绍下一篇基础查询扩展需要用到的两个方法,其它方法我会在需要用到的时候补充进来。

 

.Net应用程序框架交流QQ群: 386092459,欢迎有兴趣的朋友加入讨论。

谢谢大家的持续关注,我的博客地址:http://www.cnblogs.com/xiadao521/

如果需要下载代码,请参考Util应用程序框架公共操作类(六):验证扩展

Util应用程序框架公共操作类(十二):Lambda表达式公共操作类(三) - 何镇汐 - 博客园

mikel阅读(511)

来源: Util应用程序框架公共操作类(十二):Lambda表达式公共操作类(三) – 何镇汐 – 博客园

今天在开发一个简单查询时,发现我的Lambda操作类的GetValue方法无法正确获取枚举类型值,以至查询结果错误。

我增加了几个单元测试来捕获错误,代码如下。

复制代码
     /// <summary>
        /// 测试值为枚举
        /// </summary>
        [TestMethod]
        public void TestGetValue_Enum() {
            var test1 = new Test1();
            test1.NullableEnumValue = LogType.Error;

            //属性为枚举,值为枚举
            Expression<Func<Test1, bool>> expression = test => test.EnumValue == LogType.Debug;
            Assert.AreEqual( LogType.Debug.Value(), Lambda.GetValue( expression ) );

            //属性为枚举,值为可空枚举
            expression = test => test.EnumValue == test1.NullableEnumValue;
            Assert.AreEqual( LogType.Error, Lambda.GetValue( expression ) );

            //属性为可空枚举,值为枚举
            expression = test => test.NullableEnumValue == LogType.Debug;
            Assert.AreEqual( LogType.Debug, Lambda.GetValue( expression ) );

            //属性为可空枚举,值为可空枚举
            expression = test => test.NullableEnumValue == test1.NullableEnumValue;
            Assert.AreEqual( LogType.Error, Lambda.GetValue( expression ) );

            //属性为可空枚举,值为null
            test1.NullableEnumValue = null;
            expression = test => test.NullableEnumValue == test1.NullableEnumValue;
            Assert.AreEqual( null, Lambda.GetValue( expression ) );
        }
复制代码

单元测试成功捕获了Bug,我打开Lambda操作类,准备修改GetValue方法,代码见Util应用程序框架公共操作类(八):Lambda表达式公共操作类(二)

面对GetValue杂乱无章的代码,我顿时感觉无法下手,必须彻底重构它。

我之前也看过一些Lambda表达式解析的代码和文章,基本都是使用NodeType来进行判断。我一直没有使用这种方式,是因为NodeType数量庞大,并且多种NodeType可能转换为同一种Expression类型。我当时认为用switch判断NodeType工作量太大,所以直接采用As转换为特定表达式,再判断是否空值。

我把这种山寨方法称为瞎猫碰到死耗子,主要依靠单元测试来捕获需求,通过断点调试,我可以知道转换为哪种特定表达式。这种方法在前期看上去貌似很有效,比判断NodeType的代码要少,但由于使用表达式的方式千差万别,负担越来越重,以至无法维护了。

为了彻底重构GetValue方法,我需要补充一点表达式解析的知识,我打开开源框架linq2db,仔细观察他是如何解析的。终于看出点眉目,依靠NodeType进行递归判断。

我以前只知道使用NodeType进行判断,但不知道应该采用递归的方式,真是知其然不知其所以然。

我对GetValue进行了重构,代码如下。

复制代码
     /// <summary>
        /// 获取值,范例:t => t.Name == "A",返回 A
        /// </summary>
        /// <param name="expression">表达式,范例:t => t.Name == "A"</param>
        public static object GetValue( Expression expression ) {
            if ( expression == null )
                return null;
            switch ( expression.NodeType ) {
                case ExpressionType.Lambda:
                    return GetValue( ( (LambdaExpression)expression ).Body );
                case ExpressionType.Convert:
                    return GetValue( ( (UnaryExpression)expression ).Operand );
                case ExpressionType.Equal:
                case ExpressionType.NotEqual:
                case ExpressionType.GreaterThan:
                case ExpressionType.LessThan:
                case ExpressionType.GreaterThanOrEqual:
                case ExpressionType.LessThanOrEqual:
                    return GetValue( ( (BinaryExpression)expression ).Right );
                case ExpressionType.Call:
                    return GetValue( ( (MethodCallExpression)expression ).Arguments.FirstOrDefault() );
                case ExpressionType.MemberAccess:
                    return GetMemberValue( (MemberExpression)expression );
                case ExpressionType.Constant:
                    return GetConstantExpressionValue( expression );
            }
            return null;
        }

        /// <summary>
        /// 获取属性表达式的值
        /// </summary>
        private static object GetMemberValue( MemberExpression expression ) {
            if ( expression == null )
                return null;
            var field = expression.Member as FieldInfo;
            if ( field != null ) {
                var constValue = GetConstantExpressionValue( expression.Expression );
                return field.GetValue( constValue );
            }
            var property = expression.Member as PropertyInfo;
            if ( property == null )
                return null;
            var value = GetMemberValue( expression.Expression as MemberExpression );
            return property.GetValue( value );
        }

        /// <summary>
        /// 获取常量表达式的值
        /// </summary>
        private static object GetConstantExpressionValue( Expression expression ) {
            var constantExpression = (ConstantExpression)expression;
            return constantExpression.Value;
        }
复制代码

运行了全部测试,全部通过,说明没有影响之前的功能。这正是自动化回归测试的威力,如果没有单元测试,我哪里敢重构这些代码呢。另外,修改Bug采用TDD的方式,能够一次修复,永绝后患,值得你拥有。

同时,我还重构了其它类似的代码,就不再贴出,下次我发放源码时,有兴趣可以看看。

.Net应用程序框架交流QQ群: 386092459,欢迎有兴趣的朋友加入讨论。

谢谢大家的持续关注,我的博客地址:http://www.cnblogs.com/xiadao521/

由浅入深表达式树(二)遍历表达式树 - 腾飞(Jesse) - 博客园

mikel阅读(601)

来源: 由浅入深表达式树(二)遍历表达式树 – 腾飞(Jesse) – 博客园

  为什么要学习表达式树?表达式树是将我们原来可以直接由代码编写的逻辑以表达式的方式存储在树状的结构里,从而可以在运行时去解析这个树,然后执行,实现动态的编辑和执行代码。LINQ to SQL就是通过把表达式树翻译成SQL来实现的,所以了解表达树有助于我们更好的理解 LINQ to SQL,同时如果你有兴趣,可以用它创造出很多有意思的东西来。

表达式树是随着.NET 3.5推出的,所以现在也不算什么新技术了。但是不知道多少人是对它理解的很透彻, 在上一篇Lambda表达式的回复中就看的出大家对Lambda表达式和表达式树还是比较感兴趣的,那我们就来好好的看一看这个造就了LINQ to SQL以及让LINQ to Everything的好东西吧。

本系列计划三篇,第一篇主要介绍表达式树的创建方式。第二篇主要介绍表达式树的遍历问题。第三篇,将利用表达式树打造一个自己的LinqProvider。

本文主要内容:

上一篇由浅入深表达式树(一)我们主要讨论了如何根据Lambda表达式以及通过代码的方式直接创建表达式树。表达式树主要是由不同类型的表达式构成的,而在上文中我们也列出了比较常用的几种表达式类型,由于它本身结构的特点所以用代码写起来然免有一点繁琐,当然我们也不一定要从头到尾完全自己去写,只有我们理解它了,我们才能更好的去使用它。

在上一篇中,我们用代码的方式创建了一个没有返回值,用到了循环以及条件判断的表达式,为了加深大家对表达式树的理解,我们先回顾一下,看一个有返回值的例子。

有返回值的表达式树

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
// 直接返回常量值
ConstantExpression ce1 = Expression.Constant(10);
            
// 直接用我们上面创建的常量表达式来创建表达式树
Expression<Func<int>> expr1 = Expression.Lambda<Func<int>>(ce1);
Console.WriteLine(expr1.Compile().Invoke());
// 10
// --------------在方法体内创建变量,经过操作之后再返回------------------
// 1.创建方法体表达式 2.在方法体内声明变量并附值 3. 返回该变量
ParameterExpression param2 = Expression.Parameter(typeof(int));
BlockExpression block2 = Expression.Block(
    new[]{param2},
    Expression.AddAssign(param2,Expression.Constant(20)),
    param2
    );
Expression<Func<int>> expr2 = Expression.Lambda<Func<int>>(block2);
Console.WriteLine(expr2.Compile().Invoke());
// 20
// -------------利用GotoExpression返回值-----------------------------------
LabelTarget returnTarget = Expression.Label(typeof(Int32));
LabelExpression returnLabel = Expression.Label(returnTarget,Expression.Constant(10,typeof(Int32)));
// 为输入参加+10之后返回
ParameterExpression inParam3=Expression.Parameter(typeof(int));
BlockExpression block3 = Expression.Block(
    Expression.AddAssign(inParam3,Expression.Constant(10)),
    Expression.Return(returnTarget,inParam3),
    returnLabel);
Expression<Func<int,int>> expr3 = Expression.Lambda<Func<int,int>>(block3,inParam3);
Console.WriteLine(expr3.Compile().Invoke(20));
// 30

我们上面列出了3个例子,都可以实现在表达式树中返回值,第一种和第二种其实是一样的,那就是将我们要返回的值所在的表达式写在block的最后一个参数。而第三种我们是利用了goto 语句,如果我们在表达式中想跳出循环,或者提前退出方法它就派上用场了。这们上一篇中也有讲到Expression.Return的用法。当然,我们还可以通过switch case 来返回值,请看下面的switch case的用法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//简单的switch case 语句
ParameterExpression genderParam = Expression.Parameter(typeof(int));
SwitchExpression swithExpression = Expression.Switch(
    genderParam,
    Expression.Constant("不详"), //默认值
    Expression.SwitchCase(Expression.Constant("男"),Expression.Constant(1)), 
Expression.SwitchCase(Expression.Constant("女"),Expression.Constant(0))
//你可以将上面的Expression.Constant替换成其它复杂的表达式,ParameterExpression, BinaryExpression等, 这也是表达式灵活的地方, 因为归根结底它们都是继承自Expression, 而基本上我们用到的地方都是以基类作为参数类型接受的,所以我们可以传递任意类型的表达式。
    );
Expression<Func<intstring>> expr4 = Expression.Lambda<Func<intstring>>(swithExpression, genderParam);
Console.WriteLine(expr4.Compile().Invoke(1)); //男
Console.WriteLine(expr4.Compile().Invoke(0)); //女
Console.WriteLine(expr4.Compile().Invoke(11)); //不详

有人说表达式繁琐,这我承认,可有人说表达式不好理解,恐怕我就没有办法认同了。的确,表达式的类型有很多,光我们上一篇列出来的就有23种,但使用起来并不复杂,我们只需要大概知道一些表达类型所代表的意义就行了。实际上Expression类为我们提供了一系列的工厂方法来帮助我们创建表达式,就像我们上面用到的Constant, Parameter, SwitchCase等等。当然,自己动手胜过他人讲解百倍,我相信只要你手动的去敲一些例子,你会发现创建表达式树其实并不复杂。

表达式的遍历

说完了表达式树的创建,我们来看看如何访问表达式树。MSDN官方能找到的关于遍历表达式树的文章真的不多,有一篇比较全的(链接),真的没有办法看下去。请问盖茨叔叔就是这样教你们写文档的么?

但是ExpressionVisitor是唯一一种我们可以拿来就用的帮助类,所以我们硬着头皮也得把它啃下去。我们可以看一下ExpressionVisitor类的主要入口方法是Visit方法,其中主要是一个针对ExpressionNodeType的switch case,这个包含了85种操作类型的枚举类,但是不用担心,在这里我们只处理44种操作类型,14种具体的表达式类型,也就是说只有14个方法我们需要区别一下。我将上面链接中的代码转换成下面的表格方便大家查阅。

认识了ExpressionVisitor之后,下面我们就来一步一步的看看到底是如果通过它来访问我们的表达式树的。接下来我们要自己写一个类继承自这个ExpressionVisitor类,然后覆盖其中的某一些方法从而达到我们自己的目地。我们要实现什么样的功能呢?

1
2
3
4
5
6
7
8
9
List<User> myUsers = new List<User>();
var userSql = myUsers.AsQueryable().Where(u => u.Age > 2);
Console.WriteLine(userSql);
// SELECT * FROM (SELECT * FROM User) AS T WHERE (Age>2)
List<User> myUsers2 = new List<User>();
var userSql2 = myUsers.AsQueryable().Where(u => u.Name=="Jesse");
Console.WriteLine(userSql2);
// SELECT * FROM (SELECT * FROM USER) AS T WHERE (Name='Jesse')

我们改造了IQueryable的Where方法,让它根据我们输入的查询条件来构造SQL语句。

要实现这个功能,首先我们得知道IQueryable的Where 方法在哪里,它是如何实现的?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static class Queryable
{
    public static IQueryable<TSource> Where<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate)
    {
        if (source == null)
        {
            throw new ArgumentNullException("source");
        }
        if (predicate == null)
        {
            throw new ArgumentNullException("predicate");
        }
        return source.Provider.CreateQuery<TSource>(
            Expression.Call(null, ((MethodInfo)MethodBase.GetCurrentMethod())
            .MakeGenericMethod(new Type[] { typeof(TSource) }),
            new Expression[] { source.Expression, Expression.Quote(predicate) }));
    }
}

通过F12我们可以跟到System.Linq下有一个Querable的静态类,而我们的Where方法就是是扩展方法的形势存在于这个类中(包括其的GroupBy,Join,Last等有兴趣的同学可以自行Reflect J)。大家可以看到上面的代码中,实际上是调用了Queryable的Provider的CreateQuery方法。这个Provider就是传说中的Linq Provider,但是我们今天不打算细说它,我们的重点在于传给这个方法的参数被转成了一个表达式树。实际上Provider也就是接收了这个表达式树,然后进行遍历解释的,那么我们可以不要Provider直接进行翻译吗? I SAY YES! WHY CAN’T?

1
2
3
4
5
6
7
8
9
10
11
12
13
public static class QueryExtensions
{
    public static string Where<TSource>(this IQueryable<TSource> source,
        Expression<Func<TSource, bool>> predicate)
    {
        var expression = Expression.Call(null, ((MethodInfo)MethodBase.GetCurrentMethod())
        .MakeGenericMethod(new Type[] { typeof(TSource) }),
        new Expression[] { source.Expression, Expression.Quote(predicate) });
        var translator = new QueryTranslator();
        return translator.Translate(expression);
    }
}

上面我们自己实现了一个Where的扩展方法,将该Where方法转换成表达式树,只不过我们没有调用Provider的方法,而是直接让另一个类去将它翻译成SQL语句,然后直接返回该SQL语句。接下来的问题是,这个类如何去翻译这个表达式树呢?我们的ExpressionVisitor要全场了!

1
2
3
4
5
6
7
8
9
class QueryTranslator : ExpressionVisitor
{
    internal string Translate(Expression expression)
    {
        this.sb = new StringBuilder();
        this.Visit(expression);
        return this.sb.ToString();
    }
}

首先我们有一个类继承自ExpressionVisitor,里面有一个我们自己的Translate方法,然后我们直接调用Visit方法即可。上面我们提到了Visit方法实际上是一个入口,会根据表达式的类型调用其它的Visit方法,我们要做的就是找到对应的方法重写就可以了。但是下面有一堆Visit方法,我们要要覆盖哪哪些呢? 这就要看我们的表达式类型了,在我们的Where扩展方法中,我们传入的表达式树是由Expression.Call方法构造的,而它返回的是MethodCallExpression所以我们第一步是覆盖VisitMethodCall。

1
2
3
4
5
6
7
8
9
10
11
12
13
protected override Expression VisitMethodCall(MethodCallExpression m)
{
    if (m.Method.DeclaringType == typeof(QueryExtensions) && m.Method.Name == "Where")
    {
        sb.Append("SELECT * FROM (");
        this.Visit(m.Arguments[0]);
        sb.Append(") AS T WHERE ");
        LambdaExpression lambda = (LambdaExpression)StripQuotes(m.Arguments[1]);
        this.Visit(lambda.Body);
        return m;
    }
    throw new NotSupportedException(string.Format("方法{0}不支持", m.Method.Name));
}

代码很简单,方法名是Where那我们就直接开始拼SQL语句。重点是在这个方法里面两次调用了Visit方法,我们要知道它们会分别调用哪两个具体的Visit方法,我们要做的就是重写它们。

第一个我们就不说了,大家可以下载源代码自己去调试一下,我们来看看第二个Visit方法。很明显,我们构造了一个Lambda表达式树,但是注意,我们没有直接Visit这Lambda表达式树,它是Visit了它的Body。它的Body是什么?如果我的条件是Age>7,这就是一个二元运算,不是么?所以我们要重写VisitBinary方法,Let’s get started。

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
protected override Expression VisitBinary(BinaryExpression b)
{
    sb.Append("(");
    this.Visit(b.Left);
    switch (b.NodeType)
    {
        case ExpressionType.And:
            sb.Append(" AND ");
            break;
        case ExpressionType.Or:
            sb.Append(" OR");
            break;
        case ExpressionType.Equal:
            sb.Append(" = ");
            break;
        case ExpressionType.NotEqual:
            sb.Append(" <> ");
            break;
        case ExpressionType.LessThan:
            sb.Append(" < ");
            break;
        case ExpressionType.LessThanOrEqual:
            sb.Append(" <= ");
            break;
        case ExpressionType.GreaterThan:
            sb.Append(" > ");
            break;
        case ExpressionType.GreaterThanOrEqual:
            sb.Append(" >= ");
            break;
        default:
            throw new NotSupportedException(string.Format(“二元运算符{0}不支持”, b.NodeType));
    }
    this.Visit(b.Right);
    sb.Append(")");
    return b;
}

我们根据这个表达式的操作类型转换成对应的SQL运算符,我们要做的就是把左边的属性名和右边的值加到我们的SQL语句中。所以我们要重写VisitMember和VisitConstant方法。

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
protected override Expression VisitConstant(ConstantExpression c)
{
    IQueryable q = c.Value as IQueryable;
    if (q != null)
    {
        // 我们假设我们那个Queryable就是对应的表
        sb.Append("SELECT * FROM ");
        sb.Append(q.ElementType.Name);
    }
    else if (c.Value == null)
    {
        sb.Append("NULL");
    }
    else
    {
        switch (Type.GetTypeCode(c.Value.GetType()))
        {
            case TypeCode.Boolean:
                sb.Append(((bool)c.Value) ? 1 : 0);
                break;
            case TypeCode.String:
                sb.Append("'");
                sb.Append(c.Value);
                sb.Append("'");
                break;
            case TypeCode.Object:
                throw new NotSupportedException(string.Format("The constant for '{0}' is not supported", c.Value));
            default:
                sb.Append(c.Value);
                break;
        }
    }
    return c;
}
protected override Expression VisitMember(MemberExpression m)
{
    if (m.Expression != null && m.Expression.NodeType == ExpressionType.Parameter)
    {
        sb.Append(m.Member.Name);
        return m;
    }
    throw new NotSupportedException(string.Format("The member '{0}' is not supported", m.Member.Name));
}

到这里,我们的来龙去脉基本上就清楚了。来回顾一下我们干了哪些事情。

  1. 重写IQuerable的Where方法,构造MethodCallExpression传给我们的表达式访问类。
  2. 在我们的表达式访问类中重写相应的具体访问方法。
  3. 在具体访问方法中,解释表达式,翻译成SQL语句。

实际上我们并没有干什么很复杂的事情,只要了解具体的表达式类型和具体表访问方法就可以了。看到很多园友说表达式树难以理解,我也希望能够尽我的努力去把它清楚的表达出来,让大家一起学习,如果大家觉得哪里不清楚,或者说我表述的方式不好理解,也欢迎大家踊跃的提出来,后面我们可以继续完善这个翻译SQL的功能,我们上面的代码中只支持Where语句,并且只支持一个条件。我的目地的希望通过这个例子让大家更好的理解表达式树的遍历问题,这样我们就可以实现我们自己的LinqProvider了,请大家关注,我们来整个Linq To 什么呢?有好点子么? 之间想整个Linq to 博客园,但是好像博客园没有公开Service。

点这里面下载文中源代码。

参考引用:

http://msdn.microsoft.com/en-us/library/bb397951(v=vs.120).aspx
http://msdn.microsoft.com/en-us/library/system.linq.expressions.aspx
http://msdn.microsoft.com/en-us/library/system.linq.expressions.expression.aspx
http://blogs.msdn.com/b/mattwar/archive/2007/07/30/linq-building-an-iqueryable-provider-part-i.aspx

 

作者:Jesse 出处: http://jesse2013.cnblogs.com/

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。如果觉得还有帮助的话,可以点一下右下角的【推荐】,希望能够持续的为大家带来好的技术文章!想跟我一起进步么?那就【关注】我吧。

由浅入深表达式树(一)创建表达式树 - 腾飞(Jesse) - 博客园

mikel阅读(923)

来源: 由浅入深表达式树(一)创建表达式树 – 腾飞(Jesse) – 博客园

  为什么要学习表达式树?表达式树是将我们原来可以直接由代码编写的逻辑以表达式的方式存储在树状的结构里,从而可以在运行时去解析这个树,然后执行,实现动态的编辑和执行代码。LINQ to SQL就是通过把表达式树翻译成SQL来实现的,所以了解表达树有助于我们更好的理解 LINQ to SQL,同时如果你有兴趣,可以用它创造出很多有意思的东西来。

表达式树是随着.NET 3.5推出的,所以现在也不算什么新技术了。但是不知道多少人是对它理解的很透彻, 在上一篇Lambda表达式的回复中就看的出大家对Lambda表达式和表达式树还是比较感兴趣的,那我们就来好好的看一看这个造就了LINQ to SQL以及让LINQ to Everything的好东西吧。

本系列计划三篇,第一篇主要介绍表达式树的创建方式。第二篇主要介绍表达式树的遍历问题。第三篇,将利用表达式树打造一个自己的LinqProvider。

本文主要内容:

创建一个简单的Lambda表达式树

在 上一篇Lambda表达式中我们提到了可以直接根据Lambda表达式来创建表达式树,这应该是最直接的创建表达式树的方式了。

1
2
3
4
5
6
Expression<Func<intint>> expr = x => x + 1;
Console.WriteLine(expr.ToString());  // x=> (x + 1)
// 下面的代码编译不通过
Expression<Func<intintint>> expr2 = (x, y) => { return x + y; };
Expression<Action<int>> expr3 = x => {  };

但是别想象的太美好,这种方式只能创建最简单的表达式树,复杂点的编译器就不认识了。

右边是一个Lambda表达式,而左边是一个表达式树。为什么可以直接赋值呢?这个就要多亏我们的Expression<TDelegate>泛型类了。而Expression<TDelegate>是直接继承自LambdaExpression的,我们来看一下Expression的构造函数:

1
2
3
4
internal Expression(Expression body, string name, bool tailCall, ReadOnlyCollection<ParameterExpression> parameters)
    base(typeof(TDelegate), name, body, tailCall, parameters)
{
}

实际上这个构造函数什么也没有做,只是把相关的参数传给了父类,也就是LambdaExpression,由它把我们表达式的主体,名称,以及参数保存着。

1
2
3
4
5
6
7
8
9
10
11
12
13
Expression<Func<intint>> expr = x => x + 1;
Console.WriteLine(expr.ToString());  // x=> (x + 1)
var lambdaExpr = expr as LambdaExpression;
Console.WriteLine(lambdaExpr.Body);   // (x + 1)
Console.WriteLine(lambdaExpr.ReturnType.ToString());  // System.Int32
foreach (var parameter in lambdaExpr.Parameters)
{
    Console.WriteLine("Name:{0}, Type:{1}, ",parameter.Name,parameter.Type.ToString());
}
//Name:x, Type:System.Int32

简单的来说,Expression<TDelegate>泛型类做了一层封装,方便我们根据Lambda表达式来创建Lambda表达式树。它们中间有一个转换过程,而这个转换的过程就发生在我们编译的时候。还记得我们Lambda表达式中讲的么?Lambda表达式在编译之后是普通的方法,而Lambda式树是以一种树的结构被加载到我们的运行时的,只有这样我们才可以在运行时去遍历这个树。但是为什么我们不能根据Expression<TDelegate>来创建比较复杂的表达式树呢?您请接着往下看。

创建一个复杂的Lambda表达式树

下面我们就来一步一步的创建一个复杂的表达式树,你们准备好了么?上面我们讲到直接由Lambda表达式的方式来创建表达式树,可惜只限于一种类型。下面我们就来演示一下如何创建一个无参无返回值的表达式树。

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
// 下面的方法编译不能过
/*
Expression<Action> lambdaExpression2 = () =>
{
    for (int i = 1; i <= 10; i++)
    {
        Console.WriteLine("Hello");
    }
};
*/
       
// 创建 loop表达式体来包含我们想要执行的代码
LoopExpression loop = Expression.Loop(
    Expression.Call(
        null,
        typeof(Console).GetMethod("WriteLine"new Type[] { typeof(string) }),
        Expression.Constant("Hello"))
        );
// 创建一个代码块表达式包含我们上面创建的loop表达式
BlockExpression block = Expression.Block(loop);
// 将我们上面的代码块表达式
Expression<Action> lambdaExpression =  Expression.Lambda<Action>(block);
lambdaExpression.Compile().Invoke();

上面我们通过手动编码的方式创建了一个无参的Action,执行了一组循环。代码很简单,重要的是我们要熟悉这些各种类型的表达式以及他们的使用方式。上面我们引入了以下类型的表达式:

看起来神密的表达式树也不过如此嘛?如果大家去执行上面的代码,就会陷入死循环,我没有为loop加入break的条件。为了方便大家理解,我是真的一步一步来啊,现在我们就来终止这个循环。就像上面那一段不能编译通过的代码实现的功能一样,我们要输出10个”Hello”。

上面我们先写了一个LoopExpression,然后把它传给了BlockExpresson,从而形成的的一块代码或者我们也可以说一个方法体。但是如果我们有多个执行块,而且这多个执行块里面需要处理同一个参数,我们就得在block里面声明这些参数了。

1
2
3
4
5
6
7
8
9
10
11
ParameterExpression number=Expression.Parameter(typeof(int),"number");
            
BlockExpression myBlock = Expression.Block(
    new[] { number },
    Expression.Assign(number, Expression.Constant(2)),
    Expression.AddAssign(number, Expression.Constant(6)),
    Expression.DivideAssign(number, Expression.Constant(2)));
Expression<Func<int>> myAction = Expression.Lambda<Func<int>>(myBlock);
Console.WriteLine(myAction.Compile()());
// 4

我们声明了一个int的变量并赋值为2,然后加上6最后除以2。如果我们要用变量,就必须在block的你外面声明它,并且在block里面把它引入进来。否则在该表达式树时会出现,变量不在作用域里的错。

下面我们继续我们未完成的工作,为循环加入退出条件。为了让大家快速的理解loop的退出机制,我们先来看一段伪代码:

1
2
3
4
5
6
7
LabelTarget labelBreak = Expression.Label();
Expression.Loop(
    "如果 条件 成功"
        "执行成功的代码"
    "否则"
        Expression.Break(labelBreak) //跳出循环
    , labelBreak); 

我们需要借助于LabelTarget 以及Expression.Break来达到退出循环的目地。下面我们来看一下真实的代码:

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
LabelTarget labelBreak = Expression.Label();
ParameterExpression loopIndex = Expression.Parameter(typeof(int), "index");
BlockExpression block = Expression.Block(
new[] { loopIndex },
// 初始化loopIndex =1
    Expression.Assign(loopIndex, Expression.Constant(1)),
    Expression.Loop(
        Expression.IfThenElse(
            // if 的判断逻辑
            Expression.LessThanOrEqual(loopIndex, Expression.Constant(10)),
            // 判断逻辑通过的代码
            Expression.Block(
                Expression.Call(
                    null,
                    typeof(Console).GetMethod("WriteLine"new Type[] { typeof(string) }),
                    Expression.Constant("Hello")),
                Expression.PostIncrementAssign(loopIndex)),
            // 判断不通过的代码
            Expression.Break(labelBreak)
            ),labelBreak));
// 将我们上面的代码块表达式
Expression<Action> lambdaExpression =  Expression.Lambda<Action>(block);
lambdaExpression.Compile().Invoke();

希望上面的代码没有阻止你学习表达式树的决心J 。

好吧,我们又学了几个新的类型的表达式,来总结一下:

到这里,我想大家应该对表达式树的构建有了一个清楚的认识。至于为什么不允许我们直接基于复杂的Lambda表达式来创建表达式树呢?

  • 这里的Lambda表达式实际上是一个Expression Body。
  • 这个Expression Body实际上就是我们上面讲到的Expression中的一种。
  • 也就是说编译器需要时间去分析你到底是哪一种?
  • 最简单的x=> x+1之类的也就是Func<TValue,TKey> 是很容易分析的。
  • 实际这里面允许的Expression Body只有BinaryExpression。

最后,我们来完整的看一下.NET都为我们提供了哪些类型的表达式(下面这些类都是继承自Expression)。

TypeBinaryExpression

1
2
3
4
5
6
7
TypeBinaryExpression typeBinaryExpression =
    Expression.TypeIs(
        Expression.Constant("spruce"),
        typeof(int));
Console.WriteLine(typeBinaryExpression.ToString());
// ("spruce" Is Int32)

IndexExpression

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
ParameterExpression arrayExpr = Expression.Parameter(typeof(int[]), "Array");
ParameterExpression indexExpr = Expression.Parameter(typeof(int), "Index");
ParameterExpression valueExpr = Expression.Parameter(typeof(int), "Value");
Expression arrayAccessExpr = Expression.ArrayAccess(
    arrayExpr,
    indexExpr
);
Expression<Func<int[], intintint>> lambdaExpr = Expression.Lambda<Func<int[], intintint>>(
        Expression.Assign(arrayAccessExpr, Expression.Add(arrayAccessExpr, valueExpr)),
        arrayExpr,
        indexExpr,
        valueExpr
    );
Console.WriteLine(arrayAccessExpr.ToString());
// Array[Index]
Console.WriteLine(lambdaExpr.ToString());
// (Array, Index, Value) => (Array[Index] = (Array[Index] + Value))
Console.WriteLine(lambdaExpr.Compile().Invoke(new int[] { 10, 20, 30 }, 0, 5));
// 15

NewExpression

1
2
3
NewExpression newDictionaryExpression =Expression.New(typeof(Dictionary<intstring>));
Console.WriteLine(newDictionaryExpression.ToString());
// new Dictionary`2()

InvocationExpression

1
2
3
4
5
6
7
8
9
10
Expression<Func<intintbool>> largeSumTest =
    (num1, num2) => (num1 + num2) > 1000;
InvocationExpression invocationExpression= Expression.Invoke(
    largeSumTest,
    Expression.Constant(539),
    Expression.Constant(281));
Console.WriteLine(invocationExpression.ToString());
// Invoke((num1, num2) => ((num1 + num2) > 1000),539,281)

今天我们演示了如何通过代码的方式去创建表达式树,然后总结了一下.NET为我们提供的表达式类型。下一篇,我们将继续研究表达式树的遍历问题,敬请期待,如果对于表达式树有兴趣的同学欢迎持续关注~,

作者:Jesse 出处: http://jesse2013.cnblogs.com/

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。如果觉得还有帮助的话,可以点一下右下角的【推荐】,希望能够持续的为大家带来好的技术文章!想跟我一起进步么?那就【关注】我吧。

由浅入深表达式树(完结篇)重磅打造 Linq To 博客园 - 腾飞(Jesse) - 博客园

mikel阅读(488)

来源: 由浅入深表达式树(完结篇)重磅打造 Linq To 博客园 – 腾飞(Jesse) – 博客园

  一个多月之后,由浅入深表达式系列的最后一篇终于要问世了。想对所有关注的朋友说声:“对不起,我来晚了!” 希望最后一篇的内容对得起这一个月时间的等待。在学习完表达式树的创建和遍历之后,我们要利用它的特性来写一个我们自己的Linq Provider。人家都有Linq to Amazon为什么我们不能有Linq to cnblogs呢?今天我们就来一步一步的打造自己的Linq Provider,文章未尾已附上源码下载地址。如果对于表达式树的创建和遍历还是熟悉的话,建议先看前面两篇:

创建表达式树
http://www.cnblogs.com/jesse2013/p/expressiontree-part1.html

遍历表达式树
http://www.cnblogs.com/jesse2013/p/expressiontree-part2.html

更新:之前没有描述清楚本篇博客的意图,导致很多朋友的误解表示抱歉。本系列重在理解表达式目录树,以及Linq Provider。最后一篇是Linq Provider的实现,之所有会写这么多的代码去做一件简单的事(拉取博客园首页文章列表)完全是为了有一个生动的例子去展示如何实现自己的Linq Provider。和我们项目中的三层架构,或者直接序列化到本地是没有可比性的。

  当然,表达式目录树以及Linq Provider的强大也远非这个小小的Demo能体现得了的,如果你真正知道Linq Provider和表达式树目录树是什么,用来干什么的,也许你就能明白本篇博客的意图了。如果不了解的,建议读完前面两篇之后再做评论。因为你在自己不理解的情况下就直接去评论其它的领域,你就失去了一个了解它的机会。:)

目录

实现目标

我们实现的目标就像Linq to SQL一样,可以用Linq查询语句来查询数据,我们这里面的数据用到了博客园官方的Service去查询到最新的发布到首页的博客信息。看下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
var provider = new CnblogsQueryProvider();
var queryable = new Query<Post>(provider);
var query =
    from in queryable
    where p.Diggs >= 10 &&
    p.Comments > 10 &&
    p.Views > 10 &&
    p.Comments < 20
    select p;
var list = query.ToList();

作为实时访问远程的Service,我们还应该具体以下几点要求:

  • 延迟加载,即到最后使用的时候才真正的去请求数据
  • 只返回需要的数据,不能把所有的数据全下载过来再到本地过滤,那就没有意义了

最后实现的结果:

数据准备

根据博客园公开的API显示,获取首页文章列表非常容易,大家可以点下面的URL来体检一把。我们最后给的参数是100000,当然真实返回肯定是没有那么多的,我们是希望把能够取回来的都取回来。

http://wcf.open.cnblogs.com/blog/sitehome/recent/100000

点击完上面的URL之后呢,问题就来了,它只有一个参数。我并不能传给它查询条件,比如说根据标题来搜索,或者根据评论数,浏览量来过滤。难道我的计划就此要泡汤了么,刚开始我很不开心,为什么博客园就不能提供灵活一点的Service呢?但是事实就是这样,咋是程序员呀,需求摆在这,怎么着还得实现是不?没有办法,我给它封装了一层。在它的基础上做了一个自己的Service。

封装博客园Service

我们如何在博客园公开Service的基础上加一层实现条件查询呢?主要思路是这样的:

  • 为文章建立实体类(Post)
  • 将博客园Service返回的数据解析成Post的集合,我们可以加上自己的缓存机制,可以采用1分钟才到博客园取一次数据
  • 把我们上面创建的post集合当作数据库,建立查询Service

我们首先要做的就是为博客园的博客建立实体类。

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
public class Post
{
    // Id
    public int Id { getset; }
    // 标题
    public string Title { getset; }
    // 发布时间
    public DateTime Published { getset; }
    // 推荐数据
    public int Diggs { getset; }
    // 访问人数
    public int Views { getset; }
    // 评论数据
    public int Comments { getset; }
    // 作者
    public string Author { getset; }
    // 博客链接
    public string Href { getset; }
    // 摘要
    public string Summary { getset; }
}

接着,我们要将博客园返回给我们的XML数据转换成我们要的post集合,所以我们要用到Linq to XML。

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
_list = new List<CnblogsLinqProvider.Post>();
var document = XDocument.Load(
    "http://wcf.open.cnblogs.com/blog/sitehome/recent/100000"
    );
var elements = document.Root.Elements();
var result = from entry in elements
                where entry.HasElements == true
                select new CnblogsLinqProvider.Post
                {
                    Id = Convert.ToInt32(entry.Elements()
                        .SingleOrDefault(x => x.Name.LocalName == "id").Value),
                    Title = entry.Elements()
                        .SingleOrDefault(x => x.Name.LocalName == "title").Value,
                    Published = Convert.ToDateTime(entry.Elements()
                        .SingleOrDefault(x => x.Name.LocalName == "published").Value),
                    Diggs = Convert.ToInt32(entry.Elements()
                        .SingleOrDefault(x => x.Name.LocalName == "diggs").Value),
                    Views = Convert.ToInt32(entry.Elements()
                        .SingleOrDefault(x => x.Name.LocalName == "views").Value),
                    Comments = Convert.ToInt32(entry.Elements()
                        .SingleOrDefault(x => x.Name.LocalName == "comments").Value),
                    Summary = entry.Elements()
                        .SingleOrDefault(x=>x.Name.LocalName=="summary").Value,
                    Href = entry.Elements()
                        .SingleOrDefault(x => x.Name.LocalName == "link")
                        .Attribute("href").Value,
                    Author = entry.Elements()
                        .SingleOrDefault(x => x.Name.LocalName == "author")
                        .Elements()
                        .SingleOrDefault(x => x.Name.LocalName == "name").Value
                };
_list.AddRange(result);

然后就到了我们的查询了,实际上我们有了IEnumrable<Post>的数据就可以直接在本地用Linq去查询它了。但是这不是我们想要的,因为我们上面的步骤是把所有的数据一次性全部下载下来了,而不是根据我们的需求返回数据。另外我们这里面是在博客园Service的基础上做一层封装,实现通过Url直接查询首页的文章。为什么要通过Url来查询?因为我们最后会通过我们自己的LinqProvider将Linq查询语句直接翻译成Url这样就能够实现远程的返回数据了。来看看我们对Url参数的定义:

利用JsonResult 返回json数据来创建我们的Service

作为Service,我们返回Json或者XML格式的数据都是可以的。当然实现这个需求的方法有很多种,我们这里面有选了一种最简单方便又比较适合我们需求方式。不需要WCF Service也不需要Web API,直接用MVC里面的Action返回JsonResult就可以了。

利用Action来做这种Service还有一个好处就是我们不需要一个一个的声明查询参数,只需要把所有的参数放到一个model中就可以了。剩下的事就交给Model Binder吧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class SearchCriteria
   {
       public string Title { getset; }
       public string Author { getset; }
       
       public DateTime? Start { getset; }
       public DateTime? End { getset; }
       public int MinDiggs { getset; }
       public int MaxDiggs { getset; }
       public int MinViews { getset; }
       public int MaxViews { getset; }
       public int MinComments { getset; }
       public int MaxComments { getset; }
   }

如果大家想更熟悉这个Service的功能,可以参考上面的参数自己去体验一下(用IE会直接下载.json的文件,用Chrom是可以直接在浏览器里面看数据的)。但是我没有做任何安全性的措施,希望大侠高抬贵手,别把网站整挂了就行。

认识IQueryable和IQueryProvider接口

有了上面的Service之后,我们要做的事情就简单多了,但是在我们真正开始动手写自己的Linq Provider之前,先来看看IQueryable和IQueryProvider这两个重要的接口。

IQueryable

IQueryable本身并没有包含多少的东西,它只有三个属性:

  • ElementType 代表当然这个Query所对应的类型
  • Expression 包含了我们当然Query所执行的所有查询或者是其它的操作
  • IQueryProvider则是负责处理上面的Expression的实现

更为重要的是,在IQueryable这个接口之上,.net为我们提供了很多的扩展方法:

我们平常用到的Where,Select,Max,Any都包括在其中,具体的方法大家可以到System.Linq.Queryable这个静态类下去看。大家注意一下,传给Where方法的正是我们现在学习的Expression。

在另外一个很重要的接口IEnumrable下,也有着同样的扩展方法:

这些扩展方法来自System.Linq.Enumrable这个静态类下。我们可以看到两组扩展方法的不同之处在于IQueryable下传入的Expression类型,而IEnumrable下传入的是委托。这样做的用意是什么呢?您请接着往下看。

 

IQueryProvider

我们上面讲到了Enumrable和Queryable这两个静态类下的扩展方法,对于Enumrable下的扩展方法来说他们传入的是委托,对于委托而言直接执行就可以了。

1
2
3
4
5
6
7
8
9
10
11
public static IEnumerable<T> Where<T>(this IEnumerable<T> list, Func<T, bool> predicate)
{
    var result = new List<T>();
    foreach (var element in list)
    {
        // 调用委托是验证这个元素是否符合条件
        if(predicate(element))
            result.Add(element);
    }
    return result;
}

上面的代码给大家作一个参考,相信不难理解,所以Enumrable下的静态方法都是操作本地数据的。而对于Queryable下的静态方法而言,他们接收的是表达式,还记得表达式的最大特征吗?可以在运行时去遍历解释然后执行,那么这样就可以将表达式转换成各种其它的方式去获取数据,伟大的Linq to SQL就是这么实现的。而这背后的大功臣就是我们的Linq Provider了,而IQueryProvider就是LinqProvider的接口。

IQueryProvider只有两个操作,CreateQuery和Execute分别有泛型版本和非泛型版本。 CreatQuery用于构造一个IQueryable<T>的对象,这个类其实没有任何实现,只是继承了IQueryable和IEnumrable接口。主要用于计算指定表达式目录树所表示的查询,返回的结果是一个可枚举的类型。 而Execute会执行指定表达式目录树所表示的查询,返回指定的结果。所有的内幕就在这个Execute方法里面,拿我们要进行的Linq to cnblogs方法来举例,我们将把传入的表达式目录树翻译成一个URL就是指向我们封装好的Service的URL,通过发起web request到这个URL,拿到response进行解析,最终得到我们所要的数据,这就是我们Linq to cnblogs的思路。

Linq to cnblogs的实现

有了前面的数据准备和一些实现的大致思路以后,我们就可以着手开始实现我们的CnblogsQueryProvider了。我们的思路大致是这样的:

  1. 实现自己的ExpressionVisitor类去访问表达式目录数,将其翻译成可以访问Service的Url
  2. 调用WebRequest去访问这个Url
  3. 将上面返回的Response解析成我们要的对象

实现PostExpressionVisitor

关于表达式树的访问,我们在第二篇中已经有了比较详细的介绍。如果对于表达式树的遍历不清楚的,可以去第二篇《遍历表达式》中查阅。在这里,我们创建一个我们自己的ExpressionVisitor类,去遍历表达式树。我们暂时只需要生成一个SearchCriteria(我们上面已经定义好了,对于查询条件建的模)对象即可。

复制代码
  1 public class PostExpressionVisitor
  2 {
  3 private SearchCriteria _criteria;
  4 
  5 // 入口方法
  6 public SearchCriteria ProcessExpression(Expression expression)
  7 {
  8     _criteria = new SearchCriteria();
  9     VisitExpression(expression);
 10     return _criteria;
 11 }
 12 
 13 private void VisitExpression(Expression expression)
 14 {
 15     switch (expression.NodeType)
 16     { 
 17         // 访问 &&
 18         case ExpressionType.AndAlso:
 19             VisitAndAlso((BinaryExpression)expression);
 20             break;
 21         // 访问 等于
 22         case ExpressionType.Equal:
 23             VisitEqual((BinaryExpression)expression);
 24             break;
 25         // 访问 小于和小于等于
 26         case ExpressionType.LessThan:
 27         case ExpressionType.LessThanOrEqual:
 28             VisitLessThanOrEqual((BinaryExpression)expression);
 29             break;
 30         // 访问大于和大于等于
 31         case ExpressionType.GreaterThan:
 32         case ExpressionType.GreaterThanOrEqual:
 33             GreaterThanOrEqual((BinaryExpression)expression);
 34             break;
 35         // 访问调用方法,主要有于解析Contains方法,我们的Title会用到
 36         case ExpressionType.Call:
 37             VisitMethodCall((MethodCallExpression)expression);
 38             break;
 39         // 访问Lambda表达式
 40         case ExpressionType.Lambda:
 41             VisitExpression(((LambdaExpression)expression).Body);
 42             break;
 43     }
 44 }
 45 
 46 // 访问  &&
 47 private void VisitAndAlso(BinaryExpression andAlso)
 48 {
 49     VisitExpression(andAlso.Left);
 50     VisitExpression(andAlso.Right);
 51 }
 52 
 53 // 访问 等于
 54 private void VisitEqual(BinaryExpression expression)
 55 {
 56     // 我们这里面只处理在Author上的等于操作
 57     // Views, Comments, 和 Diggs 我们都是用的大于等于,或者小于等于
 58     if ((expression.Left.NodeType == ExpressionType.MemberAccess) &&
 59         (((MemberExpression)expression.Left).Member.Name == "Author"))
 60     {
 61         if (expression.Right.NodeType == ExpressionType.Constant)
 62             _criteria.Author = 
 63                 (String)((ConstantExpression)expression.Right).Value;
 64 
 65         else if (expression.Right.NodeType == ExpressionType.MemberAccess)
 66             _criteria.Author = 
 67                 (String)GetMemberValue((MemberExpression)expression.Right);
 68 
 69         else
 70             throw new NotSupportedException("Expression type not supported for author: " + 
 71                 expression.Right.NodeType.ToString());
 72     }
 73 }
 74 
 75 // 访问大于等于
 76 private void GreaterThanOrEqual(BinaryExpression expression)
 77 {
 78     // 处理 Diggs >= n  推荐人数
 79     if ((expression.Left.NodeType == ExpressionType.MemberAccess) &&
 80         (((MemberExpression)expression.Left).Member.Name == "Diggs"))
 81     {
 82         if (expression.Right.NodeType == ExpressionType.Constant)
 83             _criteria.MinDiggs = 
 84                 (int)((ConstantExpression)expression.Right).Value;
 85 
 86         else if (expression.Right.NodeType == ExpressionType.MemberAccess)
 87             _criteria.MinDiggs =
 88                 (int)GetMemberValue((MemberExpression)expression.Right);
 89 
 90         else
 91             throw new NotSupportedException("Expression type not supported for Diggs:"
 92                 + expression.Right.NodeType.ToString());
 93     }
 94     // 处理 Views>= n   访问人数
 95     else if ((expression.Left.NodeType == ExpressionType.MemberAccess) &&
 96     (((MemberExpression)expression.Left).Member.Name == "Views"))
 97     {
 98         if (expression.Right.NodeType == ExpressionType.Constant)
 99             _criteria.MinViews = 
100                 (int)((ConstantExpression)expression.Right).Value;
101 
102         else if (expression.Right.NodeType == ExpressionType.MemberAccess)
103             _criteria.MinViews =
104                 (int)GetMemberValue((MemberExpression)expression.Right);
105 
106         else
107             throw new NotSupportedException("Expression type not supported for Views: " 
108                 + expression.Right.NodeType.ToString());
109     }
110     // 处理 comments >= n   评论数
111     else if ((expression.Left.NodeType == ExpressionType.MemberAccess) &&
112     (((MemberExpression)expression.Left).Member.Name == "Comments"))
113     {
114         if (expression.Right.NodeType == ExpressionType.Constant)
115             _criteria.MinComments =
116                 (int)((ConstantExpression)expression.Right).Value;
117 
118         else if (expression.Right.NodeType == ExpressionType.MemberAccess)
119             _criteria.MinComments = 
120                 (int)GetMemberValue((MemberExpression)expression.Right);
121 
122         else
123             throw new NotSupportedException("Expression type not supported for Comments: "
124                 + expression.Right.NodeType.ToString());
125     }
126     // 处理 发布时间>=
127     else if ((expression.Left.NodeType == ExpressionType.MemberAccess) &&
128     (((MemberExpression)expression.Left).Member.Name == "Published"))
129     {
130         if (expression.Right.NodeType == ExpressionType.Constant)
131             _criteria.Start = 
132                 (DateTime)((ConstantExpression)expression.Right).Value;
133 
134         else if (expression.Right.NodeType == ExpressionType.MemberAccess)
135             _criteria.Start = 
136                 (DateTime)GetMemberValue((MemberExpression)expression.Right);
137 
138         else
139             throw new NotSupportedException("Expression type not supported for Published: " 
140                 + expression.Right.NodeType.ToString());
141     }
142 }
143 
144 // 访问 小于和小于等于
145 private void VisitLessThanOrEqual(BinaryExpression expression)
146 {
147     // 处理 Diggs <= n  推荐人数
148     if ((expression.Left.NodeType == ExpressionType.MemberAccess) &&
149         (((MemberExpression)expression.Left).Member.Name == "Diggs"))
150     {
151         if (expression.Right.NodeType == ExpressionType.Constant)
152             _criteria.MaxDiggs =
153                 (int)((ConstantExpression)expression.Right).Value;
154 
155         else if (expression.Right.NodeType == ExpressionType.MemberAccess)
156             _criteria.MaxDiggs =
157                 (int)GetMemberValue((MemberExpression)expression.Right);
158 
159         else
160             throw new NotSupportedException("Expression type not supported for Diggs: " 
161                 + expression.Right.NodeType.ToString());
162     }
163     // 处理 Views<= n   访问人数
164     else if ((expression.Left.NodeType == ExpressionType.MemberAccess) &&
165     (((MemberExpression)expression.Left).Member.Name == "Views"))
166     {
167         if (expression.Right.NodeType == ExpressionType.Constant)
168             _criteria.MaxViews = 
169                 (int)((ConstantExpression)expression.Right).Value;
170 
171         else if (expression.Right.NodeType == ExpressionType.MemberAccess)
172             _criteria.MaxViews =
173                 (int)GetMemberValue((MemberExpression)expression.Right);
174 
175         else
176             throw new NotSupportedException("Expression type not supported for Views: " 
177                 + expression.Right.NodeType.ToString());
178     }
179     // 处理 comments <= n   评论数
180     else if ((expression.Left.NodeType == ExpressionType.MemberAccess) &&
181     (((MemberExpression)expression.Left).Member.Name == "Comments"))
182     {
183         if (expression.Right.NodeType == ExpressionType.Constant)
184             _criteria.MaxComments =
185                 (int)((ConstantExpression)expression.Right).Value;
186 
187         else if (expression.Right.NodeType == ExpressionType.MemberAccess)
188             _criteria.MaxComments = 
189                 (int)GetMemberValue((MemberExpression)expression.Right);
190 
191         else
192             throw new NotSupportedException("Expression type not supported for Comments: "
193                 + expression.Right.NodeType.ToString());
194     }
195 
196         // 处理发布时间 <= 
197     else if ((expression.Left.NodeType == ExpressionType.MemberAccess) &&
198     (((MemberExpression)expression.Left).Member.Name == "Published"))
199     {
200         if (expression.Right.NodeType == ExpressionType.Constant)
201             _criteria.End = 
202                 (DateTime)((ConstantExpression)expression.Right).Value;
203 
204         else if (expression.Right.NodeType == ExpressionType.MemberAccess)
205             _criteria.End =
206                 (DateTime)GetMemberValue((MemberExpression)expression.Right);
207 
208         else
209             throw new NotSupportedException("Expression type not supported for Published: " 
210                 + expression.Right.NodeType.ToString());
211     }
212 }
213 
214 // 访问 方法调用
215 private void VisitMethodCall(MethodCallExpression expression)
216 {
217     if ((expression.Method.DeclaringType == typeof(Queryable)) &&
218         (expression.Method.Name == "Where"))
219     {
220         VisitExpression(((UnaryExpression)expression.Arguments[1]).Operand);
221     }
222     else if ((expression.Method.DeclaringType == typeof(String)) &&
223         (expression.Method.Name == "Contains"))
224     {
225         // Handle Title.Contains("")
226         if (expression.Object.NodeType == ExpressionType.MemberAccess)
227         {
228             MemberExpression memberExpr = (MemberExpression)expression.Object;
229             if (memberExpr.Expression.Type == typeof(Post))
230             {
231                 if (memberExpr.Member.Name == "Title")
232                 {
233                     Expression argument;
234                     argument = expression.Arguments[0];
235                     if (argument.NodeType == ExpressionType.Constant)
236                     {
237                         _criteria.Title = 
238                             (String)((ConstantExpression)argument).Value;
239                     }
240                     else if (argument.NodeType == ExpressionType.MemberAccess)
241                     {
242                         _criteria.Title = 
243                             (String)GetMemberValue((MemberExpression)argument);
244                     }
245                     else
246                     {
247                         throw new NotSupportedException("Expression type not supported: " 
248                             + argument.NodeType.ToString());
249                     }
250                 }
251             }
252         }
253     }
254     else
255     {
256         throw new NotSupportedException("Method not supported: "
257             + expression.Method.Name);
258     }
259 }
260 
261 // 获取属性值
262 private Object GetMemberValue(MemberExpression memberExpression)
263 {
264     MemberInfo memberInfo;
265     Object obj;
266 
267     if (memberExpression == null)
268         throw new ArgumentNullException("memberExpression");
269 
270 
271     if (memberExpression.Expression is ConstantExpression)
272         obj = ((ConstantExpression)memberExpression.Expression).Value;
273     else if (memberExpression.Expression is MemberExpression)
274         obj = GetMemberValue((MemberExpression)memberExpression.Expression);
275     else
276         throw new NotSupportedException("Expression type not supported: "
277             + memberExpression.Expression.GetType().FullName);
278 
279     memberInfo = memberExpression.Member;
280     if (memberInfo is PropertyInfo)
281     {
282         PropertyInfo property = (PropertyInfo)memberInfo;
283         return property.GetValue(obj, null);
284     }
285     else if (memberInfo is FieldInfo)
286     {
287         FieldInfo field = (FieldInfo)memberInfo;
288         return field.GetValue(obj);
289     }
290     else
291     {
292         throw new NotSupportedException("MemberInfo type not supported: " 
293             + memberInfo.GetType().FullName);
294     }
295 }
296 }
复制代码

 实现CnblogsQueryProvider

有了上面的访问类之后,我们的CnblogsQueryProvider就非常简单了。

复制代码
 1 public class CnblogsQueryProvider:QueryProvider
 2 {
 3     public override String GetQueryText(Expression expression)
 4     {
 5         SearchCriteria criteria;
 6 
 7         // 翻译查询条件
 8         criteria = new PostExpressionVisitor().ProcessExpression(expression);
 9 
10         // 生成URL
11         String url = PostHelper.BuildUrl(criteria);
12 
13         return url;
14     }
15 
16     public override object Execute(Expression expression)
17     {
18         String url = GetQueryText(expression);
19         IEnumerable<Post> results = PostHelper.PerformWebQuery(url);
20         return results;
21     }
22 }
复制代码

我们里面只覆盖了基类的两个方法,GetQueryText和Execute。

  • GetQueryText根据访问类得到的SearchCriteria去和成访问Service的Url
  • Execute访问则负责去请求这个Url拿到数据返回即可

PostHelper请求数据

我们这里面有一个帮助类PostHelper,就负责了根据criteria生成Url以及请求Url获取数据的功能。

复制代码
 1 static internal string BuildUrl(SearchCriteria criteria,string url=null)
 2 {
 3     if (criteria == null)
 4         throw new ArgumentNullException("criteria");
 5 
 6     var sbUrl = new StringBuilder(url ?? "http://linqtocnblogs.cloudapp.net/");
 7     var sbParameter = new StringBuilder();
 8 
 9     if (!String.IsNullOrEmpty(criteria.Title))
10         AppendParameter(sbParameter, "Title", criteria.Title);
11                 
12     if (!String.IsNullOrEmpty(criteria.Author))
13         AppendParameter(sbParameter, "Author", criteria.Author);
14 
15     if (criteria.Start.HasValue)
16         AppendParameter(sbParameter, "Start", criteria.Start.Value.ToString());
17 
18     if (criteria.End.HasValue)
19         AppendParameter(sbParameter, "End", criteria.End.Value.ToString());
20 
21     if (criteria.MinDiggs > 0)
22         AppendParameter(sbParameter, "MinDiggs", criteria.MinDiggs.ToString());
23 
24     if (criteria.MinViews > 0)
25         AppendParameter(sbParameter, "MinViews", criteria.MinViews.ToString());
26 
27     if (criteria.MinComments> 0)
28         AppendParameter(sbParameter, "MinComments",
29             criteria.MinComments.ToString());
30 
31     if (criteria.MaxDiggs > 0)
32         AppendParameter(sbParameter, "MaxDiggs", criteria.MaxDiggs.ToString());
33 
34     if (criteria.MaxViews > 0)
35         AppendParameter(sbParameter, "MaxViews", criteria.MaxViews.ToString());
36 
37     if (criteria.MaxComments > 0)
38         AppendParameter(sbParameter, "MaxComments",
39             criteria.MaxComments.ToString());
40 
41     if (sbParameter.Length > 0)
42         sbUrl.AppendFormat("?{0}", sbParameter.ToString());
43 
44     return sbUrl.ToString();
45 }
46 
47 static internal void AppendParameter(StringBuilder sb,string name,string value)
48 {
49     if (sb.Length > 0)
50         sb.Append("&");
51 
52     sb.AppendFormat("{0}={1}",name,value);
53 }
54 
55 static internal IEnumerable<Post> PerformWebQuery(string url)
56 {
57     var request = WebRequest.Create(url);
58     request.Credentials = CredentialCache.DefaultCredentials;
59 
60     var response = (HttpWebResponse)request.GetResponse();
61     using(var reader= new StreamReader(response.GetResponseStream()))
62     {
63         var body = reader.ReadToEnd();
64         return JsonConvert.DeserializeObject<List<Post>>(body);
65     }
66 }
67 }
复制代码

因为我们的Service是返回的Json数据,所以这里,我们借助了Json.Net将其转成我们所要的List<Post>的数据。

就是这么简单,我们的Linq to cnblogs就大功告成了。

点击这里下载源码:http://pan.baidu.com/s/1gd85l1T 

作者:Jesse 出处: http://jesse2013.cnblogs.com/

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。如果觉得还有帮助的话,可以点一下右下角的【推荐】,希望能够持续的为大家带来好的技术文章!想跟我一起进步么?那就【关注】我吧。

sql server 2008 R2 x86下载及安装说明_muxixi1_的博客-CSDN博客_sqlserver2008 x86

mikel阅读(574)

来源: sql server 2008 R2 x86下载及安装说明_muxixi1_的博客-CSDN博客_sqlserver2008 x86

软件链接:链接:http://pan.baidu.com/s/1hrMUwYC 密码:6jeq

安装说明请参考http://jingyan.baidu.com/article/49711c61486072fa441b7cb2.html

温馨提示:链接失效可以关注我微信公众号  amxash  ,发送  SQLServer2008r2 索要最新链接。

备注:该SQLServer版本为 SQLServer 2008 r2   x86版本,亲测在windows xp电脑安装成功。
————————————————
版权声明:本文为CSDN博主「muxixi1_」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/muxixi1_/article/details/52089564

sql server几种读写分离方案对比_tiantian1980的专栏-CSDN博客

mikel阅读(485)

来源: sql server几种读写分离方案对比_tiantian1980的专栏-CSDN博客

读写分离方案

实时同步

副本数据是否直接可读

副本数

最小粒度

副本建立索引

环境

缺点

镜像

否(需要开启快照,只读)

1

域/非域(使用证书)

在高安全模式下对主库性能有一定影响

log shipping

是(只读)

N

UNC方式可访问

副本库在做resotre时会断开已连接用户连接/可能影响常规日志备份

发布订阅

是(读写,但写可能会产生数据不一致)

N

表(查询)

域/非域

在主库上有大量DML操作时,对分发服务器会有一定影响,且订阅数据库可能有数据同步延迟

always on

是(只读)

4(SQL 2012)

8(SQL 2014)

非域环境无法使用

前端的oltp业务很繁忙,但是需要对这些运营数据进行olap,为了不影响前端正常业务,所以需要将数据库进行读写分离。

这里我将几种可以用来进行读写分离的方案总结一下,这里并不考虑数据库是否高可用,只针对读写分离场景,方案本身并无优劣可言,只看是否适合业务使用场景,所以只把几个方案的特点罗列出来,遇到具体的问题时按自己需求和环境综合考虑后再进行取舍。

 

注:oltp也称为面向交易的处理过程,其基本特征是前台接收的用户数据可以立即传送到计算中心进行处理,并在很短的时间内给出处理结果,是对用户操作快速响应的方式之一。

DDL:数据库模式定义语言,关键字:create

DML:数据操纵语言,关键字:Insert、delete、update

DCL:数据库控制语言 ,关键字:grant、remove

DQL:数据库查询语言,关键字:select

SQL Server数据库大型应用解决方案总结-技术开发专区

mikel阅读(657)

随着互联网应用的广泛普及,海量数据的存储和访问成为了系统设计的瓶颈问题。对于一个大型的互联网应用,每天百万级甚至上亿的PV无疑对数据库造成了相当高的负载。对于系统的稳定性和扩展性造成了极大的问题。

来源: SQL Server数据库大型应用解决方案总结-技术开发专区

【IT168 技术】随着互联网应用的广泛普及,海量数据的存储和访问成为了系统设计的瓶颈问题。对于一个大型的互联网应用,每天百万级甚至上亿的PV无疑对数据库造成了相当高的负载。对于系统的稳定性和扩展性造成了极大的问题。

一、负载均衡技术

负载均衡集群是由一组相互独立的计算机系统构成,通过常规网络或专用网络进行连接,由路由器衔接在一起,各节点相互协作、共同负载、均衡压力,对客户端来说,整个群集可以视为一台具有超高性能的独立服务器。

1、实现原理

实现数据库的负载均衡技术,首先要有一个可以控制连接数据库的控制端。在这里,它截断了数据库和程序的直接连接,由所有的程序来访问这个中间层,然后再由中间层来访问数据库。这样,我们就可以具体控制访问某个数据库了,然后还可以根据数据库的当前负载采取有效的均衡策略,来调整每次连接到哪个数据库。

2、实现多数据库数据同步

对于负载均衡,最重要的就是所有服务器的数据都是实时同步的。这是一个集群所必需的,因为,如果数不据实时、不同步,那么用户从一台服务器读出的数据,就有别于从另一台服务器读出的数据,这是不能允许的。所以必须实现数据库的数据同步。这样,在查询的时候就可以有多个资源,实现均衡。比较常用的方法是Moebius for SQL Server集群,Moebius for SQL Server集群采用将核心程序驻留在每个机器的数据库中的办法,这个核心程序称为Moebius for SQL Server 中间件,主要作用是监测数据库内数据的变化并将变化的数据同步到其他数据库中。数据同步完成后客户端才会得到响应,同步过程是并发完成的,所以同步到多个数据库和同步到一个数据库的时间基本相等;另外同步的过程是在事务的环境下完成的,保证了多份数据在任何时刻数据的一致性。正因为Moebius 中间件宿主在数据库中的创新,让中间件不但能知道数据的变化,而且知道引起数据变化的SQL语句,根据SQL语句的类型智能的采取不同的数据同步的策略以保证数据同步成本的最小化。

SQL Server数据库大型应用解决方案总结

数据条数很少,数据内容也不大,则直接同步数据

数据条数很少,但是里面包含大数据类型,比如文本,二进制数据等,则先对数据进行压缩然后再同步,从而减少网络带宽的占用和传输所用的时间。

数据条数很多,此时中间件会拿到造成数据变化的SQL语句, 然后对SQL语句进行解析,分析其执行计划和执行成本,并选择是同步数据还是同步SQL语句到其他的数据库中。此种情况应用在对表结构进行调整或者批量更改数据的时候非常有用。

3、优缺点

(1) 扩展性强:当系统要更高数据库处理速度时,只要简单地增加数据库服务器就 可以得到扩展。

(2) 可维护性:当某节点发生故障时,系统会自动检测故障并转移故障节点的应用,保证数据库的持续工作。

(3) 安全性:因为数据会同步的多台服务器上,可以实现数据集的冗余,通过多份数据来保证安全性。另外它成功地将数据库放到了内网之中,更好地保护了数据库的安全性。

(4) 易用性:对应用来说完全透明,集群暴露出来的就是一个IP

(1) 不能够按照Web服务器的处理能力分配负载。

(2) 负载均衡器(控制端)故障,会导致整个数据库系统瘫痪。

二、数据库的读写分离

1,实现原理:读写分离简单的说是把对数据库读和写的操作分开对应不同的数据库服务器,这样能有效地减轻数据库压力,也能减轻io压力。主数据库提供写操作,从数据库提供读操作,其实在很多系统中,主要是读的操作。当主数据库进行写操作时,数据要同步到从的数据库,这样才能有效保证数据库完整性。

SQL Server:数据库的读写分离
▲(ebay的读写比率是260:1,ebay的读写分离)

SQL Server:数据库的读写分离
▲(微软数据库分发)

2,实现方法:在MS Sql server中可以使用发布定义的方式实现数据库复制,实现读写分离,复制是将一组数据从一个数据源拷贝到多个数据源的技术,是将一份数据发布到多个存储站点上的有效方式。使用复制技术,用户可以将一份数据发布到多台服务器上。复制技术可以确保分布在不同地点的数据自动同步更新,从而保证数据的一致性。SQL SERVER复制技术类型有三种,分别是:快照复制、事务复制、合并复制。SQL SERVER 主要采用出版物、订阅的方式来处理复制。源数据所在的服务器是出版服务器,负责发表数据。出版服务器把要发表的数据的所有改变情况的拷贝复制到分发服务器,分发服务器包含有一个分发数据库,可接收数据的所有改变,并保存这些改变,再把这些改变分发给订阅服务器。

3,优缺点

(1)数据的实时性差:数据不是实时同步到自读服务器上的,当数据写入主服务器后,要在下次同步后才能查询到。

(2)数据量大时同步效率差:单表数据量过大时插入和更新因索引,磁盘IO等问题,性能会变的很差。

(3)同时连接多个(至少两个)数据库:至少要连接到两个数据数据库,实际的读写操作是在程序代码中完成的,容易引起混乱

(4)读具有高性能高可靠性和可伸缩:只读服务器,因为没有写操作,会大大减轻磁盘IO等性能问题,大大提高效率;只读服务器可以采用负载均衡,主数据库发布到多个只读服务器上实现读操作的可伸缩性。

三、数据库/数据表 拆分(分布式)

通过某种特定的条件,将存放在同一个数据库中的数据分散存放到多个数据库上,实现分布存储,通过路由规则路由访问特定的数据库,这样一来每次访问面对的就不是单台服务器了,而是N台服务器,这样就可以降低单台机器的负载压力。提示:SQLServer 2005版本之后,可以友好的支持“表分区”。

垂直(纵向)拆分:是指按功能模块拆分,比如分为订单库、商品库、用户库…这种方式多个数据库之间的表结构不同。

水平(横向)拆分:将同一个表的数据进行分块保存到不同的数据库中,这些数据库中的表结构完全相同。

SQL Server:数据库/数据表 拆分
▲(纵向拆分)

SQL Server:数据库/数据表 拆分
▲(横向拆分)

1,实现原理:使用垂直拆分,主要要看应用类型是否合适这种拆分方式,如系统可以分为,订单系统,商品管理系统,用户管理系统业务系统比较明的,垂直拆分能很好的起到分散数据库压力的作用。业务模块不明晰,耦合(表关联)度比较高的系统不适合使用这种拆分方式。但是垂直拆分方式并不能彻底解决所有压力问题,例如 有一个5000w的订单表,操作起来订单库的压力仍然很大,如我们需要在这个表中增加(insert)一条新的数据,insert完毕后,数据库会针对这张表重新建立索引,5000w行数据建立索引的系统开销还是不容忽视的,反过来,假如我们将这个表分成100个table呢,从table_001一直到table_100,5000w行数据平均下来,每个子表里边就只有50万行数据,这时候我们向一张只有50w行数据的table中insert数据后建立索引的时间就会呈数量级的下降,极大了提高了DB的运行时效率,提高了DB的并发量,这种拆分就是横向拆分

2,实现方法:垂直拆分,拆分方式实现起来比较简单,根据表名访问不同的数据库就可以了。横向拆分的规则很多,这里总结前人的几点,

(1)顺序拆分:如可以按订单的日前按年份才分,2003年的放在db1中,2004年的db2,以此类推。当然也可以按主键标准拆分。

优点:可部分迁移

缺点:数据分布不均,可能2003年的订单有100W,2008年的有500W。

(2)hash取模分: 对user_id进行hash(或者如果user_id是数值型的话直接使用user_id的值也可),然后用一个特定的数字,比如应用中需要将一个数据库切分成4个数据库的话,我们就用4这个数字对user_id的hash值进行取模运算,也就是user_id%4,这样的话每次运算就有四种可能:结果为1的时候对应DB1;结果为2的时候对应DB2;结果为3的时候对应DB3;结果为0的时候对应DB4,这样一来就非常均匀的将数据分配到4个DB中。

优点:数据分布均匀

缺点:数据迁移的时候麻烦;不能按照机器性能分摊数据 。

(3)在认证库中保存数据库配置

就是建立一个DB,这个DB单独保存user_id到DB的映射关系,每次访问数据库的时候都要先查询一次这个数据库,以得到具体的DB信息,然后才能进行我们需要的查询操作。

优点:灵活性强,一对一关系

缺点:每次查询之前都要多一次查询,会造成一定的性能损失。

SQL Server 2012中的AlwaysOn尝试 - CareySon - 博客园

mikel阅读(517)

来源: SQL Server 2012中的AlwaysOn尝试 – CareySon – 博客园

简介

SQL Server2012中新增的AlwaysOn是一个新增高可用性解决方案。在AlwaysOn之前,SQL Server已经有的高可用性和数据恢复方案,比如数据库镜像,日志传送和故障转移集群.都有其自身的局限性。而AlwaysOn作为微软新推出的解决方案,提取了数据库镜像和故障转移集群的优点。本文旨在通过实现一个AlwaysOn的实例来展现AlwaysOn。

 

配置AlwaysOn

虽然AlwaysOn相比较之前版本的故障转移集群而言,步骤已经简化了许多。但配置AlwaysOn依然是一件比较麻烦的事,不仅需要你对SQL Server比较熟悉,还需要对Windows Server有所了解。本文配置AlwaysOn分为两个板块,分别为:配置Windows和配置SQL Server。

在开始说道配置Windows之前,首先简单介绍一下测试环境。

我搭了三台Windows Server 2008的虚拟机(SQL SERVER 2012比较麻烦,必须2008 sp2以上版本windows server才能安装),如图1所示。其中将活动目录和DNS服务器安在了Windows Server2008 Server 1.没有启用DHCP服务器,三台服务器的IP分别为192.168.100.1/24,192.168.100.2/24,192.168.100.3/24。

1

图1.三台装有Windows Server2008的测试虚拟机

 

三台服务器都加入了由Windows Server 2008 Server1作为AD建立域SQL2012.TEST。三台虚拟机的名称分别为SQLServerTest1,SQLServerTest2,SQLServerTest3。

 

配置Windows Server

首先在分别在三台测试服务器上安装故障转移集群的功能,如图2所示。

Windows_failover_cluster

图2.在测试服务器上安装故障转移集群的功能

 

在安装好故障转移集群的功能之后,在Server1上进行对集群的配置.如图3所示。

2

图3.在故障转移集群管理中创建集群

 

然后在接下来的步骤中,将三台服务器加入集群,如图4所示。

WindowsCluster1

图4.将三台测试服务器加入集群

 

点击下一步,进行测试,如图5所示。

WindowsCluster2

WindowsCluster3

图5.对集群进行验证测试

 

点击下一步,添加集群名称,如图6所示。

WindowsCluster4

图6.添加集群IP和集群名称

 

然后点击下一步确认后,最后完成集群的创建,如图7所示。

WindowsCluster7

图7.完成集群的创建

 

配置SQL Server

在配置完Windows Server之后,就该配置SQL SERVER了。分别在三台测试机上安装SQL Server 2012,所安装的功能如图8所示。

SetupComponent

图8.SQL Server 2012安装的功能

 

安装完SQL Server 2012之后,运行SQL Server配置管理器,如图9所示。

3

图9.运行SQL Server 配置管理器

然后在SQL Server实例中开启alwaysOn选项,如图10所示.

4

图10.开启AlwaysOn

 

 

未完待续….