先说IEnumerable,我们每天用的foreach你真的懂它吗? - 农码一生 - 博客园

mikel阅读(606)

来源: 先说IEnumerable,我们每天用的foreach你真的懂它吗? – 农码一生 – 博客园

先说IEnumerable,我们每天用的foreach你真的懂它吗?

我们先思考几个问题:

  1. 为什么在foreach中不能修改item的值?
  2. 要实现foreach需要满足什么条件?
  3. 为什么Linq to Object中要返回IEnumerable?

接下来,先开始我们的正文。

自己实现迭代器

.net中迭代器是通过IEnumerable和IEnumerator接口来实现的,今天我们也来依葫芦画瓢。

首先来看看这两个接口的定义:

并没有想象的那么复杂。其中IEnumerable只有一个返回IEnumerator的GetEnumerator方法。而IEnumerator中有两个方法加一个属性。

接下来开发画瓢,我们继承IEnumerable接口并实现:

下面使用原始的方式调用:

有朋友开始说了,我们平时都是通过foreache来取值的,没有这样使用过啊。好吧,我们来使用foreach循环:

为什么说基本上是等效的呢?我们先看打印结果,在看反编译代码。

由此可见,两者有这么个关系:

我们可以回答第一个问题了“为什么在foreach中不能修改item的值?”:

我们还记得IEnumerator的定义吗

接口的定义就只有get没有set。所以我们在foreach中不能修改item的值。

我们再来回答第二个问题:“要实现foreach需要满足什么条件?”:

必须实现IEnumerable接口?NO

我们自己写的MyIEnumerable删掉后面的IEnumerable接口一样可以foreach(不信?自己去测试)。

所以要可以foreach只需要对象定义了GetEnumerator无参方法,并且返回值是IEnumerator或其对应的泛型。细看下图:

也就是说,只要可以满足这三步调用即可。不一定要继承于IEnumerable。有意思吧!下次面试官问你的时候一定要争个死去活来啊,哈哈!

yield的使用

你肯定发现了我们自己去实现IEnumerator接口还是有些许麻烦,并且上面的代码肯定是不够健壮。对的,.net给我们提供了更好的方式。

你会发现我们连MyIEnumerator都没要了,也可以正常运行。太神奇了。yield到底为我们做了什么呢?

好家伙,我们之前写的那一大坨。你一个yield关键字就搞定了。最妙的是这块代码:

这就是所谓的状态机吧!

我们继续来看GetEnumerator的定义和调用:

我们调用GetEnumerator的时候,看似里面for循环了一次,其实这个时候没有做任何操作。只有调用MoveNext的时候才会对应调用for循环:

现在我想可以回答你“为什么Linq to Object中要返回IEnumerable?”:

因为IEnumerable是延迟加载的,每次访问的时候才取值。也就是我们在Lambda里面写的where、select并没有循环遍历(只是在组装条件),只有在ToList或foreache的时候才真正去集合取值了。这样大大提高了性能。

如:

这个时候得到了就是IEnumerable对象,但是没有去任何遍历的操作。(对照上面的gif动图看)

什么,你还是不信?那我们再来做个实验,自己实现MyWhere:

现在看到了吧。执行到MyWhere的时候什么动作都没有(返回的就是IEnumerable),只有执行到ToList的时候才代码才真正的去遍历筛选。

这里的MyWhere其实可以用扩展方法来实现,提升逼格。(Linq的那些查询操作符就是以扩展的形式实现的)[了解扩展方法]。

怎样高性能的随机取IEnumerable中的值

这段代码来源《深入理解C#》,个人觉得非常妙。贴出来给大家欣赏哈。

 

结束:

demo下载:http://pan.baidu.com/s/1dE94c1b

接下篇:《再讲IQueryable<T>,揭开表达式树的神秘面纱

 

本文以同步至《C#基础知识巩固系列

再讲IQueryable,揭开表达式树的神秘面纱 - 农码一生 - 博客园

mikel阅读(604)

来源: 再讲IQueryable,揭开表达式树的神秘面纱 – 农码一生 – 博客园

再讲IQueryable<T>,揭开表达式树的神秘面纱

接上篇《先说IEnumerable,我们每天用的foreach你真的懂它吗?

最近园子里定制自己的orm那是一个风生水起,感觉不整个自己的orm都不好意思继续混博客园了(开个玩笑)。那么在此之前我们有必要仔细了解下 IQueryable<T> ,于是就有了此文。

什么是树?

什么是树?这个问题好像有点白痴。树不就是树嘛。看图:

我们从最下面的主干开始往上看,主枝-分支-分支….可以说是无限分支下去。我们倒过来看就是这样:

平时我们用得最多的树结构数据就是XML了,节点下面可以无限添加子节点。我们想想平时还用过什么树结构数据,比如:菜单无限分级、评论区的楼层。

这和我们今天讲的有毛关系啊。… 我们今天主要就是来分析表达式树的。、

lambda表达式和表达式树的区别:

Lambda表达式:

Func<Student, bool> func = t => t.Name == "农码一生";

表达式树:

Expression<Func<Student, bool>> expression = t => t.Name == "农码一生";

咋一看,没啥区别啊。表达式只是用Expression包了一下而已。那你错了,这只是Microsoft给我们展示的障眼法,我们看编译后的C#代码:

第一个lambda表达式编译成了匿名函数,第二个表达式树编译成一了一堆我们不认识的东西,远比我们原来写的lambda复杂得多。

结论:

  • 我们平时使用的表达式树,是编写的lambda表达式然后编译成的表达式树,也就是说平时一般情况使用的表达式树都是编译器帮我们完成的。(当然,我们可以可以手动的主动的去创表达式树。只是太麻烦,不是必要情况没有谁愿意去干这个苦活呢)

我们来看看表达式树到底有什么神奇的地方:

有没有看出点感觉来?Body里面有Right、Left,Right里面又有Right、Left,它们的类型都是继承自 Expression 。这种节点下面有节点,可以无限附加下去的数据结构我们称为树结构数据。也就是我们的表达式树。

补:上面的 Student 实体类:

复制代码
public class Student
{
    public string Name { get; set; }

    public int Age { get; set; }

    public string Address { get; set; }

    public string Sex { get; set; }
}
复制代码

解析表达式树

上面我们看到了所谓的表达式树,其他也没有想象的那么复杂嘛。不就是一个树结构数据嘛。如果我们要实现自己的orm,免不了要解析表达式树。一般说到解析树结构数据都会用到递归算法。下面我们开始解析表达式树。

先定义解析方法:

复制代码
//表达式解析
public static class AnalysisExpression
{
    public static void VisitExpression(Expression expression)
    {
        switch (expression.NodeType)
        {
            case ExpressionType.Call://执行方法
                MethodCallExpression method = expression as MethodCallExpression;
                Console.WriteLine("方法名:" + method.Method.Name);
                for (int i = 0; i < method.Arguments.Count; i++)
                    VisitExpression(method.Arguments[i]);
                break;
            case ExpressionType.Lambda://lambda表达式
                LambdaExpression lambda = expression as LambdaExpression;
                VisitExpression(lambda.Body);
                break;
            case ExpressionType.Equal://相等比较
            case ExpressionType.AndAlso://and条件运算
                BinaryExpression binary = expression as BinaryExpression;
                Console.WriteLine("运算符:" + expression.NodeType.ToString());
                VisitExpression(binary.Left);
                VisitExpression(binary.Right);
                break;
            case ExpressionType.Constant://常量值
                ConstantExpression constant = expression as ConstantExpression;
                Console.WriteLine("常量值:" + constant.Value.ToString());
                break;
            case ExpressionType.MemberAccess:
                MemberExpression Member = expression as MemberExpression;
                Console.WriteLine("字段名称:{0},类型:{1}", Member.Member.Name, Member.Type.ToString());
                break;
            default:
                Console.Write("UnKnow");
                break;
        }
    }

}
复制代码

调用解析方法:

Expression<Func<Student, bool>> expression = t => t.Name == "农码一生" && t.Sex == "男";
AnalysisExpression.VisitExpression(expression);

我们来看看执行过程:

一层一层的往子节点递归,直到遍历完所有的节点。最后打印效果如下:

基本上我们想要的元素和值都取到了,接着怎么组装就看你自己的心情了。是拼成SQL,还是生成url,请随意!

实现自己的IQueryable<T>、IQueryProvider

仅仅解析了表达式树就可以捣鼓自己的orm了?不行,起码也要基于 IQueryable<T> 接口来编码吧。

接着我们自定义个类 MyQueryable<T> 继承接口 IQueryable<T> :

复制代码
 public class MyQueryable<T> : IQueryable<T>
 {
     public IEnumerator<T> GetEnumerator()
     {
         throw new NotImplementedException();
     }
     IEnumerator IEnumerable.GetEnumerator()
     {
         throw new NotImplementedException();
     }
     public Type ElementType
     {
         get { throw new NotImplementedException(); }
     }
     public Expression Expression
     {
         get { throw new NotImplementedException(); }
     }
     public IQueryProvider Provider
     {
         get { throw new NotImplementedException(); }
     }
 }
复制代码

我们看到其中有个接口属性 IQueryProvider ,这个接口的作用大着呢,主要作用是在执行查询操作符的时候重新创建 IQueryable<T> 并且最后遍历的时候执行SQL远程取值。我们还看见了 Expression  属性。

现在我们明白了 IQueryable<T> 和 Expression (表达式树)的关系了吧:

  •  IQueryable<T> 最主要的作用就是用来存储 Expression(表达式树)

下面我们也自定义现实了 IQueryProvider 接口的类 MyQueryProvider :

复制代码
public class MyQueryProvider : IQueryProvider
{
    public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
    {
        throw new NotImplementedException();
    }
    public IQueryable CreateQuery(Expression expression)
    {
        throw new NotImplementedException();
    }
    public TResult Execute<TResult>(Expression expression)
    {
        throw new NotImplementedException();
    }
    public object Execute(Expression expression)
    {
        throw new NotImplementedException();
    }
}
复制代码

上面全是自动生成的伪代码,下面我们来填充具体的实现:

 View Code

执行代码:

 var aa = new MyQueryable<Student>();
 var bb = aa.Where(t => t.Name == "农码一生");
 var cc = bb.Where(t => t.Sex == "男");
 var dd = cc.AsEnumerable();
 var ee = cc.ToList();

接着我们看看执行过程:

结论:

  • 每次在执行 Where 查询操作符的时候 IQueryProvider 会为我们创建一个新的 IQueryable<T>
  • 调用 AsEnumerable() 方法的时候并不会去实际取值(只是得到一个IEnumerable)[注意:在EF里面查询不要先取IEnumerable后滤筛,因为AsEnumerable()会生成查询全表的sql]
  • 执行 ToList() 方法时才去真正调用迭代器 GetEnumerator() 取值
  • 真正取值的时候,会去执行 IQueryProvider 中的 Execute 方法。(就是在调用这个方法的时候解析表达式数,然后执行取得结果)

我们看到真正应该办实事的 Execute  我们却让他返回默认值了。

现在估计有人不爽了,你到是具体实现下 Execute 。好吧!(其实通过上面说的解析表达式树,你可以自己在这里做想做的任何事了。)

首先为了简单起见,我们用一个集合做为数据源:

复制代码
//构造Student数组
public static List<Student> StudentArrary = new List<Student>()
{
        new Student(){Name="农码一生", Age=26, Sex="男", Address="长沙"},
        new Student(){Name="小明", Age=23, Sex="男", Address="岳阳"},
        new Student(){Name="嗨-妹子", Age=25, Sex="女", Address="四川"}
};
复制代码

然后,重新写一个VisitExpression2方法:(和之前的区别: 现在目的是取表达式树中的表达式,而不是重新组装成sql或别的)

复制代码
public static void VisitExpression2(Expression expression, ref List<LambdaExpression> lambdaOut)
{
    if (lambdaOut == null)
        lambdaOut = new List<LambdaExpression>();
    switch (expression.NodeType)
    {
        case ExpressionType.Call://执行方法
            MethodCallExpression method = expression as MethodCallExpression;
            Console.WriteLine("方法名:" + method.Method.Name);
            for (int i = 0; i < method.Arguments.Count; i++)
                VisitExpression2(method.Arguments[i], ref  lambdaOut);
            break;
        case ExpressionType.Lambda://lambda表达式
            LambdaExpression lambda = expression as LambdaExpression;
            lambdaOut.Add(lambda);
            VisitExpression2(lambda.Body, ref  lambdaOut);
            break;
        case ExpressionType.Equal://相等比较
        case ExpressionType.AndAlso://and条件运算
            BinaryExpression binary = expression as BinaryExpression;
            Console.WriteLine("运算符:" + expression.NodeType.ToString());
            VisitExpression2(binary.Left, ref  lambdaOut);
            VisitExpression2(binary.Right, ref  lambdaOut);
            break;
        case ExpressionType.Constant://常量值
            ConstantExpression constant = expression as ConstantExpression;
            Console.WriteLine("常量值:" + constant.Value.ToString());
            break;
        case ExpressionType.MemberAccess:
            MemberExpression Member = expression as MemberExpression;
            Console.WriteLine("字段名称:{0},类型:{1}", Member.Member.Name, Member.Type.ToString());
            break;
        case ExpressionType.Quote:
            UnaryExpression Unary = expression as UnaryExpression;
            VisitExpression2(Unary.Operand, ref  lambdaOut);
            break;
        default:
            Console.Write("UnKnow");
            break;
    }
}
复制代码

然后重新实现方法 Execute :

复制代码
public TResult Execute<TResult>(Expression expression)
{
    List<LambdaExpression> lambda = null;
    AnalysisExpression.VisitExpression2(expression, ref lambda);//解析取得表达式数中的表达式
    IEnumerable<Student> enumerable = null;
    for (int i = 0; i < lambda.Count; i++)
    {
        //把LambdaExpression转成Expression<Func<Student, bool>>类型
        //通过方法Compile()转成委托方法
        Func<Student, bool> func = (lambda[i] as Expression<Func<Student, bool>>).Compile(); 
        if (enumerable == null)
            enumerable = Program.StudentArrary.Where(func);//取得IEnumerable
        else
            enumerable = enumerable.Where(func);
    }
    dynamic obj = enumerable.ToList();//(注意:这个方法的整个处理过程,你可以换成解析sql执行数据库查询,或者生成url然后请求获取数据。)
    return (TResult)obj;
}
复制代码

执行过程:

个人对 IQueryable 延迟加载的理解:

  • 前段部分的查询操作符只是把逻辑分解存入表达式树,并没有远程执行sql。
  • foreache执行的是 IEnumerable<T> ,然而 IEnumerable<T> 同样具有延迟加载的特性。每次迭代的时候才真正的取数据。且在使用导航属性的时候会再次查询数据库。(下次说延迟加载不要忘记了 IEnumerable 的功劳哦!)

小知识:

表达式树转成Lambda表达式:

Expression<Func<Student, bool>> expression = t => t.Name == "农码一生";
Func<Student, bool> func = expression.Compile();

总结:

表达式树的分析就告一段落了,其中还有很多细节或重要的没有分析到。下次有新的心得再来总结。

感觉表达式树就是先把表达式打散存在树结构里(一般打散的过程是编译器完成),然后可以根据不同的数据源或接口重新组装成自己想要的任何形式,这也让我们实现自己的orm成为了可能。

今天主要是对表达式树的解析、和实现自己的IQueryable<T>、IQueryProvider做了一个记录和总结,其中不定有错误的结论或说法,轻点拍!

demo下载:http://pan.baidu.com/s/1nvAksgL

本文以同步至索引目录:《C#基础知识巩固

 

推荐阅读:

http://www.cnblogs.com/jesse2013/p/expressiontree-part1.html

http://www.cnblogs.com/jesse2013/p/expressiontree-part2.html

http://www.cnblogs.com/jesse2013/p/expressiontree-Linq-to-cnblogs.html

园友@风口上的猪推荐:

http://www.cnblogs.com/Ninputer/archive/2009/09/08/expression_tree3.html
http://blog.zhaojie.me/2009/03/expression-cache-1.html

Expression和Func - 心存善念 - 博客园

mikel阅读(2290)

来源: Expression<Func>和Func – 心存善念 – 博客园

1.Expression<Func<T,TResult>>是表达式

//使用LambdaExpression构建表达式树
            Expression<Func<int, int, int, int>> expr = (x, y, z) => (x + y) / z;
            Console.WriteLine(expr.Compile()(1, 2, 3));

https://msdn.microsoft.com/zh-cn/library/system.linq.expressions.expression(v=vs.100).aspx

https://msdn.microsoft.com/zh-cn/library/bb335710(v=vs.100).aspx

 

2.Func<T, TResult> 委托

封装一个具有一个参数并返回 TResult 参数指定的类型值的方法。
public delegate TResult Func<in T, out TResult>(T arg)
类型参数
in T
此委托封装的方法的参数类型。
该类型参数是逆变的。即可以使用指定的类型或派生程度更低的类型。有关协变和逆变的更多信息,请参见泛型中的协变和逆变。
out TResult
此委托封装的方法的返回值类型。
该类型参数是协变的。即可以使用指定的类型或派生程度更高的类型。有关协变和逆变的更多信息,请参见泛型中的协变和逆变。
参数
arg
类型:T
此委托封装的方法的参数。
返回值
类型:TResult
此委托封装的方法的返回值。

复制代码
  string mid = ",middle part,";
            ///匿名写法
            Func<string, string> anonDel = delegate(string param)
            {
                param += mid;
                param += " And this was added to the string.";
                return param;
            };
            ///λ表达式写法
            Func<string, string> lambda = param =>
                {
                    param += mid;
                    param += " And this was added to the string.";
                    return param;
                };
            ///λ表达式写法(整形)
            Func<int, int> lambdaint = paramint =>
                {
                    paramint = 5;
                    return paramint;
                };
            ///λ表达式带有两个参数的写法
            Func<int, int, int> twoParams = (x, y) =>
                {
                  return  x*y;
                };
            MessageBox.Show("匿名方法:"+anonDel("Start of string"));
            MessageBox.Show("λ表达式写法:" + lambda("Lambda expression"));
            MessageBox.Show("λ表达式写法(整形):" + lambdaint(4).ToString());
            MessageBox.Show("λ表达式带有两个参数:" + twoParams(10, 20).ToString());
复制代码

来自:

http://blog.csdn.net/shuyizhi/article/details/6598013

3.使用Expression进行查询拼接

复制代码
    public static class DynamicLinqExpressions
     {
 
         public static Expression<Func<T, bool>> True<T>() { return f => true; }
         public static Expression<Func<T, bool>> False<T>() { return f => false; }
 
         public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> expr1,
                                                             Expression<Func<T, bool>> expr2)
         {
             var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>());
             return Expression.Lambda<Func<T, bool>>
                   (Expression.Or(expr1.Body, invokedExpr), expr1.Parameters);
         }
 
         public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> expr1,
                                                              Expression<Func<T, bool>> expr2)
         {
             var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>());
             return Expression.Lambda<Func<T, bool>>
                   (Expression.And(expr1.Body, invokedExpr), expr1.Parameters);
         }
 
     }
复制代码

使用方法

复制代码
       public static IEnumerable<T> FilterBy<T>(this IEnumerable<T> collection, IEnumerable<KeyValuePair<string, string>> query)
         {
             var filtersCount = int.Parse(query.SingleOrDefault(k => k.Key.Contains("filterscount")).Value);
             
             Expression<Func<T, bool>> finalquery = null;
 
             for (var i = 0; i < filtersCount; i += 1)
             {
                 var filterValue = query.SingleOrDefault(k => k.Key.Contains("filtervalue" + i)).Value;
                 var filterCondition = query.SingleOrDefault(k => k.Key.Contains("filtercondition" + i)).Value;
                 var filterDataField = query.SingleOrDefault(k => k.Key.Contains("filterdatafield" + i)).Value;
                 var filterOperator = query.SingleOrDefault(k => k.Key.Contains("filteroperator" + i)).Value;
 
                 Expression<Func<T, bool>> current = n => GetQueryCondition(n, filterCondition, filterDataField, filterValue);
 
                 if (finalquery == null)
                 {
                     finalquery = current;
                 }
                 else if (filterOperator == "1")
                 {
                     finalquery = finalquery.Or(current);
                 }
                 else
                 {
                     finalquery = finalquery.And(current);
                 }
             };
 
             if (finalquery != null)
                 collection = collection.AsQueryable().Where(finalquery);
 
             return collection;
         }
复制代码

要使用AsQueryable,必须引入 LinqKit.EntityFramework;如果不使用AsQueryable将会报错。

 

4.关于SelectListItem的扩展

复制代码
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Web.Mvc;
using System.Web.UI;

namespace Web.Helper
{
    public static class SelectListItemExtensions
    {
        /// <summary>
        /// Data.ToSelectListItems(s=>s.Id,s=>s.Name);
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="baseEntities"></param>
        /// <param name="valueExpression"></param>
        /// <param name="textExpression"></param>
        /// <param name="defaultValue"></param>
        /// <returns></returns>
        public static IEnumerable<SelectListItem> ToSelectListItems<T>(this IEnumerable<T> baseEntities, Expression<Func<T, string>> valueExpression, Expression<Func<T, string>> textExpression, object defaultValue = null)
        {
            dynamic valueNameObject = valueExpression.Body.GetType().GetProperty("Member").GetValue(valueExpression.Body, null);
            dynamic textNameobject = textExpression.Body.GetType().GetProperty("Member").GetValue(textExpression.Body, null);

            var valueName = (string)valueNameObject.Name;
            var textName = (string)textNameobject.Name;

            return ToSelectListItems(baseEntities, valueName, textName, null);
        }
        /// <summary>
        /// Data.ToSelectListItems("Id", "Name")
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="baseEntities"></param>
        /// <param name="valueName"></param>
        /// <param name="textName"></param>
        /// <param name="defaultValue"></param>
        /// <returns></returns>
        public static IEnumerable<SelectListItem> ToSelectListItems<T>(this IEnumerable<T> baseEntities, string valueName, string textName, object defaultValue = null)
        {
            if (string.IsNullOrWhiteSpace(valueName) || string.IsNullOrWhiteSpace(textName))
            {
                return new List<SelectListItem>();
            }
            var selectListItem = new List<SelectListItem>();
            if (defaultValue != null)
            {
                foreach (var baseEntity in baseEntities)
                {
                    var itemValue = GetPropertyValue(baseEntity, valueName);
                    selectListItem.Add(new SelectListItem()
                    {
                        Value = itemValue.ToString(),
                        Text = GetPropertyValue(baseEntity, textName).ToString(),
                        Selected = itemValue == defaultValue
                    });
                }
            }
            else
            {
                foreach (var baseEntity in baseEntities)
                {
                    selectListItem.Add(new SelectListItem()
                    {
                        Value = GetPropertyValue(baseEntity, valueName).ToString(),
                        Text = GetPropertyValue(baseEntity, textName).ToString(),
                    });
                }
            }

            return selectListItem;
        }

        public static object GetPropertyValue(object obj, string propertyName)
        {
            //反射去获得对象的某个属性值
            //Type t = obj.GetType();
            //PropertyInfo info = t.GetProperty(propertyName);
            //return info.GetValue(obj, null); //设置为SetValue

            return DataBinder.GetPropertyValue(obj, propertyName);
            //return DataBinder.Eval(obj, propertyName);
        }


    }
}
复制代码

 

 

原文:http://blog.csdn.net/nabila/article/details/8137169

使用exec和sp_executesql动态执行SQL语句(转载) - PowerCoder - 博客园

mikel阅读(624)

来源: 使用exec和sp_executesql动态执行SQL语句(转载) – PowerCoder – 博客园

当需要根据外部输入的参数来决定要执行的SQL语句时,常常需要动态来构造SQL查询语句,个人觉得用得比较多的地方就是分页存储过程和执行搜索查询的SQL语句。一个比较通用的分页存储过程,可能需要传入表名,字段,过滤条件,排序等参数,而对于搜索的话,可能要根据搜索条件判断来动态执行SQL语句。

在SQL Server中有两种方式来执行动态SQL语句,分别是exec和sp_executesql。sp_executesql相对而言具有更多的优点,它提供了输入输出接口,可以将输入输出变量直接传递到SQL语句中,而exec只能通过拼接的方式来实现。还有一个优点就是sp_executesql,能够重用执行计划,这就大大提高了执行的性能。所以一般情况下建议选择sp_executesql来执行动态SQL语句。

使用sp_executesql需要注意的一点就是,它后面执行的SQL语句必须是Unicode编码的字符串,所以在声明存储动态SQL语句的变量时必须声明为nvarchar类型,否则在执行的时候会报“过程需要类型为 ‘ntext/nchar/nvarchar’ 的参数 ‘@statement’”的错误,如果是使用sp_executesql直接执行SQL语句,则必须在前面加上大写字母N,以表明后面的字符串是使用Unicode类型编码的。

下面来看看几种动态执行SQL语句的情况

1.普通SQL语句

(1)exec(‘select * from Student’)

(2)exec sp_executesql N’select * from Student’–此处一定要加上N,否则会报错

2.带参数的SQL语句

(1)declare @sql nvarchar(1000)
declare @userId varchar(100)
set @userId=’0001′
set @sql=’select * from Student where UserID=”’+@userId+””

exec(@sql)

(2)declare @sql nvarchar(1000)
declare @userId varchar(100)
set @userId=’0001′
set @sql=N’select * from Student where UserID=@userId’
exec sp_executesql @sql,N’@userId varchar(100)’,@userId

从这个例子中可以看出使用sp_executesql可以直接将参数写在sql语句中,而exec需要使用拼接的方式,这在一定程度上可以防止SQL注入,因此sp_executesql拥有更高的安全性。另外需要注意的是,存储sql语句的变量必须声明为nvarchar类型的。

(3)带输出参数的SQL语句

create procedure sp_GetNameByUserId

(

@userId varchar(100),

@userName varchar(100) output

)

as

declare @sql nvarchar(1000)

set @sql=N’select @userName=UserName from Student where UserId=@userId’

exec sp_executesql N’@userId varchar(100),@userName varchar(100) output’,@userId,@userName output

select @userName

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

mikel阅读(624)

来源: 由浅入深表达式树(完结篇)重磅打造 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就非常简单了。

 View Code

我们里面只覆盖了基类的两个方法,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 

2020抖音无水印视频解析真实地址(附java demo和api)_子墨的博客-CSDN博客

mikel阅读(4629)

来源: 2020抖音无水印视频解析真实地址(附java demo和api)_子墨的博客-CSDN博客

DouYinVideoCrawler

抖音无水印小视频解析真实地址的demo(java),附上原理
GitHub地址

效果

  1. 请使用浏览器访问,这里
  2. 复制返回字段url中的链接在新窗口打开,即可看到没有水印的小视频,此url即为小视频真实地址(此地址大约在第二天就会失效,猜测应该是0点自动刷新token吧,此想法未验证)

用法

  1. 使用浏览器,在地址栏输入,‘http://www.zimo.wiki:8080/douyin-video-crawler/api/analysis?url=’
  2. 打开抖音短视频APP,点开某个视频,点击右下角分享按钮,在分享弹框中点击复制链接
  3. 将复制的链接粘贴到第一步输入的链接的地址栏的最后,按Enter,正确的请求地址类似这样http://www.zimo.wiki:8080/douyin-video-crawler/api/analysis?url=https://v.douyin.com/gU8REJ/
  4. 在返回的数据字段找到url对应的字段,此地址即为没有水印的小视频的真实地址
  5. 用浏览器请求url就可以看到无水印的小视频了

原理解析

  1. 先在抖音复制一条小视频链接,在浏览器打开,f12调出开发者模式,选中video,可以看到播放地址直接在src中
    在这里插入图片描述
  2. 把src中的地址https://aweme.snssdk.com/aweme/v1/playwm/?s_vid=93f1b41336a8b7a442dbf1c29c6bbc566643c365a1a8df9d3fa4bb99aa21ac37880d88309946b2a3782771c451bbd26b87f0d18011addfc5a65b2369772af4d8&line=0复制出来,新开一个窗口请求一下看看,发现地址被重定向了,然后打开了视频播放页面,视频中有水印
    在这里插入图片描述
  3. 接着继续分析了一下此页面(电脑版),未发现什么有用的东西,在这篇博客的启发下,我尝试了一下移动端,然后发现了一些有趣的东西,在浏览器f12的页面直接选中那个标红的按钮就可以切换到移动端模式,实际上是更改了请求的user-agent
    在这里插入图片描述
  4. 和那篇博客博主所采用的实现方式不一样,老实说,这博主的实现的方式有点麻烦,但是无意中也给了我一点启发,我最开始是循着博主的思路,用java实现了一遍,发现获取到的地址是这样https://aweme.snssdk.com/aweme/v1/play/?video_id=v0200ff10000bopbhcuvld7780ioaq1g&line=0&ratio=540p&media_type=4&vr_type=0&improve_bitrate=0&is_play_url=1&is_support_h265=0&source=PackSourceEnum_PUBLISH,单开一个电脑的页面来请求,发现直接无响应,但是没有403之类的,感觉有戏,于是单开一个手机端的页面,便拿到了没有水印的视频地址,然后我接着分析移动端的页面,还是那个熟悉的video标签,src中依然是视频的地址(拿出来https://aweme.snssdk.com/aweme/v1/playwm/?s_vid=93f1b41336a8b7a442dbf1c29c6bbc566643c365a1a8df9d3fa4bb99aa21ac37880d88309946b2a3782771c451bbd26b87f0d18011addfc5a65b2369772af4d8&line=0,请求,依然是有水印的视频)
    在这里插入图片描述
  5. 通过对比分析这三个链接,我发现从src中拿出来的链接无论是手机端还是电脑端都是一模一样的,然后和iteminfo接口中获取出来的最有意思的差别就在于playplaywm,这俩应该是一个对应电脑端,一个对应手机端的播放接口
  6. 于是我尝试直接拿src中的地址,将链接中的playwm直接替换为play,然后user-agent伪装成手机端设备请求,果然,成功了,哈哈哈
  7. 具体实现请看代码CrawlerService类中的demo1demo2方法

总结

不知道上面的原理大家看懂没有,没看懂也没关系,动手实操一下,实操完应该就明白了,再次整理一下我的思路

  1. 获取抖音分享页面上的video标签,拿到src属性的链接
  2. 将链接中的playwm直接替换为play
  3. user-agent伪装成手机端设备请求
  4. 拿到重定向过后的无水印的小视频的真实地址

致谢

感谢csdn博主@杜比爱的博文

爬虫初级 - 抖音视频无水印批量下载_oyo775881的博客-CSDN博客

mikel阅读(1249)

来源: 爬虫初级 – 抖音视频无水印批量下载_oyo775881的博客-CSDN博客

最近接到了一个朋友的软件定制需求,需要爬取指定用户的所有抖音视频(无水印),确定接受这个任务之后,首先理一下实现的思路,计划实现的方案,实现的思路分三步走:

  • 解析用户首页数据,拿到视频信息
  • 一般数据都是分页展示,需要找到分页的标示,循环读取视频数据到结束标记
  • 拿到视频数据,拼接解析视频地址,实现无水印下载

在确定了这个思路之后,按照个人经验,读取视频数据应该不算太难,难点是怎么实现无水印解析,网上有各种各样的无水印解析工具下载使用,必定有无水印的解析方法,不过网上的工具是没有源码给我们参考的。不过幸好在精易论坛找到了无水印的解析方法,如下图(来源:精易论坛 -> https://bbs.125.la/thread-14560713-1-1.html):

在认真的看了前人的解析方法之后,我个人觉得第一种方法比较简单方便,有了解析方法,那就先进行解析的测试,从抖音极速版分享一个链接出来(测试链接:https://v.douyin.com/J1DWHbK/),注意这里需要调整为手机模式浏览,使用火狐自带的插件User-Agent Switcher,将浏览器设置为手机模式,并且调整浏览器界面为手机大小并访问测试链接,显示的结果如下图所示:

可以看到访问了测试链接之后,链接经过了一个跳转,这个我们后面在分析里面具体的参数,看到手机端的显示界面之后,我们右键查看源代码,在源代码里面搜索“playwm”(为什么搜索这个关键字,请认真看图一),发现并没有对应的文本,如下图所示:

居然找不到对应的文本,难道是这个方法失效了?其实并没有,在切换回了桌面模式之后,刷新页面,在右键查看源代码,查找相应的文本,这回找到了,如下图:

找打了对应的文本,复制下来再浏览器打开,能成功的播放的视频,但是是有水印的,如下图:

能成功读取到视频,说明连接有效,按照提醒,把playwm改为play,再次访问链接,结果一片空白。。。

最后经过测试是发现要把UA设置成手机模式(一定要认真阅读文档··),再次访问,得到了无水印的播放地址:

OK,测试方法一能成功拿到无水印的解析地址,剩下的就是想办法拿到用户首页的视频数据了,从抖音上分享出这个用户的个人主页地址(https://v.douyin.com/J1UuQ6X/),将UA改为手机模式,打开开发者选项,监听网络请求:

在用户主页里面,发现了抖音是通过API接口形式获取用户的视频数据,这就好办了,有一定开发经验的同学应该都知道要怎么做了,经过测试,我总结了这个接口的参数的具体意义:

  • sec_uid:用户的ID加密之后的字符串 必传
  • count:每次读取的视频数量
  • max_cursor:当前的页标,用来读取下一页数据的
  • aid:不详,照着传就好
  • _signature:验证签名,这个签名不好处理,我们可以直接使用webview来拿到
  • dytk:顾名思义抖音token,经测试传不传都可以

接口的返回数据如下图所示:

抖音毕竟也是国民级的APP,安全校验技术,服务器反爬虫技术也是TOP1级别的,所以在测试这个接口的时候有不少的坑,这里也要说一下,主要就是UA问题,打开一个无痕浏览器(无法设置UA,无cookies)直接访问连接,返回空数据:

使用POST接口,设置UA,对同一个链接提交请求(仅需要设置UA):

这里我们就能得到结论:在传入的参数正确的情况下,此接口仅在意UA,Cookies不影响此接口,UA必须设置手机端,否则无数据返回。

到这一步,实现抖音无水印批量下载的理论体系已经完善,并且合理可行,读取用户首页数据以及下一页的数据的方法,无水印下载的方法都已找到,剩下的就是实现具体的代码,这里我用易语言以及PHP实现(至于为什么需要用到PHP,下面会有详细的解释),这里我只贴主要的核心代码。

首先是第一步,我们要通过接口拿到数据,前面我们也说了,_signature参数的生产方法我没研究透,但是它是直接贴死在url里面,我们用精易web浏览器就能监听url变化,从而拿到这个url,具体代码如下:

细心的同学会发现我把url里面的secuid等参数分解了出来,这是因为我们直接用易语言精易模块的网页_访问_对象是读不出这个接口数据的,具体的原因未知,因为时间问题也没有深入去研究,既然易语言不行,那就试一下使用PHP的curl方法,这也是为什么我们用到PHP的原因(遇到这种问题,在充分考虑各种因素之后我认为多语言配合不失为一个好的解决方法,没必要死磕)我们把读接口数据部分放在PHP,做一个中继,易语言程序直接读此接口并传入分解后的参数(不能直接传URL,因为URL里面有&字符,会影响解析),就能拿到数据,PHP的代码并不难理解,curl的小知识而已,代码如下:

到这里基本上遇到的问题都给解决了,剩下的就是实现循环读取视频数据,通过接口拼接得到无水印的下载地址,易语言实现代码如下:

好啦,核心的代码就这么多,总结的话就不想写了,感觉也不是很难,就是在调试的时候需要细心认真,多试验几次,发现关键的地方,新手写文章,如果有表达不完善的地方,望包涵~

JS异常: Uncaught RangeError: Maximum call stack size exceeded_2020,决战决胜脱贫攻坚,总结使人进步,遵循事物的发展规律-CSDN博客_jquery-3.4.1.min.js:2 uncaught rangeerror: maximum

mikel阅读(674)

来源: JS异常: Uncaught RangeError: Maximum call stack size exceeded_2020,决战决胜脱贫攻坚,总结使人进步,遵循事物的发展规律-CSDN博客_jquery-3.4.1.min.js:2 uncaught rangeerror: maximum

最近经常遇到1个JS异常,Chrome运行,偶尔有问题,想复现的时候,却又不出来。

JQuery-2.1.4.min.js?_=1498552203335:3 Uncaught RangeError: Maximum call stack size exceeded.
at HTMLDivElement.trigger (JQuery-2.1.4.min.js?_=1498552203335:3)
at Object.trigger (jQuery-2.1.4.min.js?_=1498552203335:3)
at HTMLDivElement.<anonymous> (jQuery-2.1.4.min.js?_=1498552203335:3)
at Function.each (jquery-2.1.4.min.js?_=1498552203335:2)
at n.fn.init.each (jquery-2.1.4.min.js?_=1498552203335:2)
at n.fn.init.trigger (jquery-2.1.4.min.js?_=1498552203335:3)
at n.eval (eval at globalEval (jquery-2.1.4.min.js?_=1498552195035:2), <anonymous>:6:16198)
at HTMLDocument.f (jquery-2.1.4.min.js?_=1498552203335:2)
at HTMLDocument.dispatch (jquery-2.1.4.min.js?_=1498552203335:3)

at HTMLDocument.r.handle (jquery-2.1.4.min.js?_=1498552203335:3)

不过,今天又在网上喵了几眼,来了灵感。

http://blog.csdn.net/jston_learn/article/details/7799232

这篇文章说的是其中1种原因,大致瞧了瞧,感觉好复杂。

还有Stackoverflow之类的答案,都是列出了现象和常见例子,但是并不能解决我的问题。

灵感:函数递归调用,不还是“重复”的一种例子么。

会不会是我的代码里,重复引入了1个js了。

还真是。

我的项目情况:

A主体列表页->B新增对话框-加载1个新页面–C在新增对话框页面里,再选择用户。

B和C里都引入了公共的js。

把C页面里公共js去掉就行了,让他直接使用B父页面引入的。

为了验证缓存等情况,把Chrome缓存和Cookie所有数据清了,反复新增,又出现了刚刚的问题。

让人崩溃。

同样的道理,把B页面,引入公共的js,也去掉了。

让B直接使用A父页面的。

再次清除Chrome缓存和Cookie,Ok了。

2017年6月27日

朝林A座

JS异常: Uncaught RangeError: Maximum call stack size exceeded_wenpy的博客-CSDN博客_uncaught rangeerror: maximum call stack size excee

mikel阅读(603)

来源: JS异常: Uncaught RangeError: Maximum call stack size exceeded_wenpy的博客-CSDN博客_uncaught rangeerror: maximum call stack size excee

今天被一个bug弄得头大…!!!!!!!

找了无数资料…网上说是递归函数的原因

https://blog.csdn.net/qq_30100043/article/details/72642205

还是未能解决问题,继续找!!!!

最后在 https://blog.csdn.net/FansUnion/article/details/73801346 大哥的博文中找到灵感!!!

删除清除Chrome缓存和Cookie

删除清除Chrome缓存和Cookie

删除清除Chrome缓存和Cookie

c# - 如何不使用.Compile()从MemberExpression获取属性值? - IT工具网

mikel阅读(958)

来源: c# – 如何不使用.Compile()从MemberExpression获取属性值? – IT工具网

我在尝试不使用.compile()从表达式树中获取对象的值时遇到问题
目标很简单。

var userModel = new UserModel { Email = "John@Doe.com"};

给我问题的方法是这样的。

private void VisitMemberAccess(MemberExpression expression, MemberExpression left)
{
    var key = left != null ? left.Member.Name : expression.Member.Name;
    if (expression.Expression.NodeType.ToString() == "Parameter")
    {
        // add the string key
        _strings.Add(string.Format("[{0}]", key));
    }
    else
    {
        // add the string parameter
        _strings.Add(string.Format("@{0}", key));

        // Potential NullReferenceException
        var val = (expression.Member as FieldInfo).GetValue((expression.Expression as ConstantExpression).Value);

        // add parameter value
        Parameters.Add("@" + key, val);
    }
}

我做的测试很简单

[Test]  // PASS
public void ShouldVisitExpressionByGuidObject ()
{
    // Setup
    var id = new Guid( "CCAF57D9-88A4-4DCD-87C7-DB875E0D4E66" );
    const string expectedString = "[Id] = @Id";
    var expectedParameters = new Dictionary<string, object> { { "@Id", id } };

    // Execute
    var actualExpression = TestExpression<UserModel>( u => u.Id == id );
    var actualParameters = actualExpression.Parameters;
    var actualString = actualExpression.WhereExpression;

    // Test
    Assert.AreEqual( expectedString, actualString );
    CollectionAssert.AreEquivalent( expectedParameters, actualParameters );
}

 

[Test]  // FAIL [System.NullReferenceException : Object reference not set to an instance of an object.]
public void ShouldVisitExpressionByStringObject ()
{
    // Setup
    var expectedUser = new UserModel {Email = "john@doe.com"};

    const string expectedString = "[Email] = @Email";
    var expectedParameters = new Dictionary<string, object> { { "@Email", expectedUser.Email } };

    // Execute
    var actualExpression = TestExpression<UserModel>( u => u.Email == expectedUser.Email );
    var actualParameters = actualExpression.Parameters;
    var actualString = actualExpression.WhereExpression;

    // Assert
    Assert.AreEqual( expectedString, actualString );
    CollectionAssert.AreEquivalent( expectedParameters, actualParameters );
}

我应该注意到改变

var val = (expression.Member as FieldInfo).GetValue((expression.Expression as ConstantExpression).Value);

var val = Expression.Lambda( expression ).Compile().DynamicInvoke().ToString();

将允许测试通过,但是此代码需要在ios上运行,因此不能使用.Compile()

最佳答案

TLDR;
只要不使用EmitCompile,反射就可以使用。在这个问题中,值是为FieldInfo提取的,但不是为PropertyInfo提取的。一定要两样都买得到。

if ((expression.Member as PropertyInfo) != null)
{
    // get the value from the PROPERTY

}
else if ((expression.Member as FieldInfo) != null)
{
    // get the value from the FIELD
}
else
{
    throw new InvalidMemberException();
}

长卷本
所以这些评论给了我正确的方向。我有点费劲地想弄到房产信息,但最后,我想到了这个。

private void VisitMemberAccess(MemberExpression expression, MemberExpression left)
{
    // To preserve Case between key/value pairs, we always want to use the LEFT side of the expression.
    // therefore, if left is null, then expression is actually left. 
    // Doing this ensures that our `key` matches between parameter names and database fields
    var key = left != null ? left.Member.Name : expression.Member.Name;

    // If the NodeType is a `Parameter`, we want to add the key as a DB Field name to our string collection
    // Otherwise, we want to add the key as a DB Parameter to our string collection
    if (expression.Expression.NodeType.ToString() == "Parameter")
    {
        _strings.Add(string.Format("[{0}]", key));
    }
    else
    {
        _strings.Add(string.Format("@{0}", key));

        // If the key is being added as a DB Parameter, then we have to also add the Parameter key/value pair to the collection
        // Because we're working off of Model Objects that should only contain Properties or Fields,
        // there should only be two options. PropertyInfo or FieldInfo... let's extract the VALUE accordingly
        var value = new object();
        if ((expression.Member as PropertyInfo) != null)
        {
            var exp = (MemberExpression) expression.Expression;
            var constant = (ConstantExpression) exp.Expression;
            var fieldInfoValue = ((FieldInfo) exp.Member).GetValue(constant.Value);
            value = ((PropertyInfo) expression.Member).GetValue(fieldInfoValue, null);

        }
        else if ((expression.Member as FieldInfo) != null)
        {
            var fieldInfo = expression.Member as FieldInfo;
            var constantExpression = expression.Expression as ConstantExpression;
            if (fieldInfo != null & constantExpression != null)
            {
                value = fieldInfo.GetValue(constantExpression.Value);
            }
        }
        else
        {
            throw new InvalidMemberException();
        }

        // Add the Parameter Key/Value pair.
        Parameters.Add("@" + key, value);
    }
}

实际上,如果Member.NodeType是一个Parameter,那么我将把它用作一个SQL字段。[FieldName]
否则,我将它用作SQL参数@FieldName。倒过来我知道。
如果Member.NodeType不是参数,那么我检查它是modelField还是modelProperty。从那里,我得到适当的值,并将键/值对添加到字典中,用作SQL参数。
最终的结果是我构建了一个类似于

SELECT * FROM TableName WHERE
[FieldName] = @FieldName

然后传递参数

var parameters = new Dictionary<string, object> Parameters;
parameters.Add("@FieldName", "The value of the field");