posts - 1,  comments - 3,  trackbacks - 0

一、基本介绍                                                                                                                                                                                                                                                                        

回忆:最早接触Expression是在学校接触到EF的时候,发现where方法里的参数是Expression<Func<T,bool>>这么一个类型,当初也只是看到了,也没有过多的去探究,只是知道传入lambda表达式使用即可,对于Expression和里面的Func<T,bool>到底是怎么一种关系,都不清楚。目前也不是很了解,只知道一些简单的使用,但是可以解决自己目前的一些问题就好了。毕竟作为一名18年的应届毕业生,能力有限。

        今天,就简单的以我遇到的问题介绍下我使用Expression的一种使用。

1、Expression和Func委托的区别。

        对于这两者的区别,网上有大把的介绍。可自行去搜索看看。讲的肯定比我这一知半解的好。我就不说了。

二、使用Expression和Func委托创建新的对象并初始化其一个或多个成员的运算,如 C# 中的 new Point { X = 1, Y = 2 }                                                                                              

       可能一些人不知道生成这样的有什么用?貌似废话了,会来看,应都是需要用到的。但还是总会有些爱好学习的偶然翻到就看了看,所以我还是以自己遇到的问题来说说有什么作用吧。以下观点仅是我个人感受,不喜勿碰。也希望大佬给点建议。

       我刚开始使用EF进行开发操作数据库的时候,并不习惯,感觉没ADO.NET来得快,使用ADO.NET几乎没有不是拼接一个字符串就能解决,如果有,那就两个。但是自从习惯了EF后,就再也不想去使用ADO.NET了,感觉使用EF太舒服了。但是用着用着又遇到新的让我难受的事情。大概是习惯了然后对EF的要求更高了,想让我开发时更舒服些。

  比如,在使用EF做修改操作时,一般情况是要给实体所有的必要属性赋值:

数据库中原有的一条数据

现在对其进行修改操作

        public static Users GetUser()
        {
            var user = new Users()
            {
                Id = 1,
                CreateTime = DateTime.Now,
                ModifiedTime = DateTime.Now,
                Age = 20,
                Sex = "",
                Name = "那谁"
            };
            return user;
        }    
        static void Main(string[] args)
        {
            using (var ctx = new EfContext())
            {
                var user = GetUser();
                var dataBaseUser = ctx.Users.AsNoTracking().FirstOrDefault(u => u.Id == user.Id);
                if (dataBaseUser != null)
                {
                    ctx.Users.Attach(user);
                    ctx.Entry(user).State = EntityState.Modified;           

                    if (!ctx.ChangeTracker.HasChanges())
                    {
                        Console.WriteLine("更新成功!");
                    }
                    else if (ctx.SaveChanges() > 0)
                    {
                        Console.WriteLine("更新成功!");
                    }
                    else
                    {
                        Console.WriteLine("更新失败!");
                    }
                }
                Console.ReadKey();
            }
            
        }        

  更新成功。但是对于CreateTime和ModifiedTime而言,一旦数据创建后,CreateTime就不会改变,只会更新ModifiedTime。鉴于此,我改变一下获取User的方法如下,最后依然在控制台中提交上述更新成功的代码:

        public static Users GetUser()
        {
            var user = new Users()
            {
                Id = 1,
                ModifiedTime = DateTime.Now,
                Age = 20,
                Sex = "",
                Name = "那谁"
            };
            return user;
        }

 

  上述抛出的错误是因为CreateTime赋值为空引起的。解决这个异常的方法我们不去讨论。抛出这个异常说明即使你把不需要修改的属性从赋值这个阶段去掉,EF是默认这个属性值为空或者是默认值(假如你设置了默认值的话)。

  下面我将获取Main方法修改一下,用一般常用的方法去完成这样的操作。

 

  更新成功。这样是实现了按需更新。但是我认为这不够灵活,而且如果一个表需要修改的字段比较多的话,那么这样的赋值会让代码看起来很多。而且有些系统可能会有不同的界面对同一张表进行不同字段的修改,这样你就需要又写一个方法去进行这样的操作。这样我感觉是很难受的。而且EF的性能一直都被说不好。你看这样的修改就知道了。第一步,查出来,第二步修改。用ADO.NET一步就可以解决得,EF却要用两步。网络环境好的情况这一步两步的差别也不是很明显,但是网络环境比较差就会让人抓狂。

  鉴于此,我又学到了一些方法更好的去实现修改。比如使用EntityFramework.Extended这个插件。只需在NuGet中搜索安装就行。上面都是我在使用EF对数据库进行更新操作时的一些经过,下面才正式开始进入和标题相符的内容。

  安装好EntityFramework.Extended这个插件后,引用EntityFramework.Extensions命名空间。对数据库进行修改只需要这样:

        static void Main(string[] args)
        {
            using (var ctx = new EfContext())
            {                
                int sum=ctx.Users.Where(w => w.Id == 1).Update(p => new Users() { ModifiedTime = DateTime.Now, Age = 21, Sex = "" });
                if(sum>0)
                {
                    Console.WriteLine("更新成功!");
                }
                else
                {
                    Console.WriteLine("更新失败!");
                }
                Console.ReadKey();
            }
            
        }

  更新成功。而且是一步完成。整个代码更是很少,看着就舒服很多。但是这样还是不够灵活,因为对同一张表不同界面修改不同的字段的时候,还是要重新写个方法去对Update括号里的User(){}进行赋于不同字段值。(是不是发现了Update后需要的表达式和标题中的一样?)然后我就去网上看了下,通过Expression拼接lambda表达式的有不少文章介绍。但是和这个Update需要的表达式是有区别。大概是我搜索方式有问题,一直都没有看到有关的文章介绍。最后通过文档自己找到了这个方法。

  我们只需添加一个这样的方法:

        public static Expression<Func<Users,Users>> GetUpdatePredicate(Users model)
        {
            var list = new List<MemberBinding>();

            var p = Expression.Parameter(typeof(Users), "p");

            foreach (var item in model.GetType().GetProperties())
            {
                var value = item.GetValue(model);

                if(value!=null)
                {
                    list.Add(Expression.Bind(typeof(Users).GetMember(item.Name)[0], Expression.Constant(value)));
                }
            }
            Expression expr = Expression.MemberInit(Expression.New(typeof(Users)), list);

            var lambda = Expression.Lambda<Func<Users, Users>>(expr, p);
            return lambda;
        }    

  如果前端传入的是一个实体,在Main中就这样调用。(因为我是控制台应用程序,所以只能模拟一下前端传入实体)。

        static void Main(string[] args)
        {
            //模拟前端传入的实体
            var user = new Users()
            {
                Id = 1,
                Name = "小米"
            };

            using (var ctx = new EfContext())
            {                
                int sum=ctx.Users.Where(w => w.Id == user.Id).Update(GetUpdatePredicate(user));
                if(sum>0)
                {
                    Console.WriteLine("更新成功!");
                }
                else
                {
                    Console.WriteLine("更新失败!");
                }
                Console.ReadKey();
            }
            
        }

  然后调试一下每一步生成的是什么东西:

  一看不对啊。Age、CreateTime、ModifiedTime并没有赋值也出现了啊。这是因为值类型的变量系统都会给他一个系统的默认值,因为我们判断的是当item.GetValue(model)!=null时就将其加入到赋值里去。像int型就的系统默认值是0,DateTime的系统默认值根据不同的应用程序有不一样的默认值。所以才会加入到赋值里面去。解决这个问题也简单我们只需在为值类型的实体属性的数据类型后加个“?”表示可空就行了。比如“int?”表示可空。但是有些属性需要设置默认值怎么办?设置默认值的方法我经常用的就是[DefaultValue(1)],但是没有找到获取设置的默认值的方法,如果有知道的,麻烦留言告诉我一下,感激不尽。所以我使用的默认值是通过无参和有参构造函数实现的。就是添加数据和一些需要默认值的时候通过有参构造函数赋默认值,修改就是无参。这是我的解决办法,如有更好的想法欢迎留言。处理好这个默认值问题后再看运行结果:

 

  发现生成的表达式对了,但是更新的时候抛出了异常。上述异常是因为设置了主键自增引起的。主键也并不需要修改。所以在我们遍历实体属性的时候,可以跳过主键。跳过的方法目前只能笨一点。首先可以根据注解来跳过:item.GetCustomAttributesData()[0].AttributeType.Name.ToString().Contains("KeyAttribute"),这个获取到的注解是[Key]:

其次根据EF的默认主键规则跳过,EF默认属性名为“Id”或者“类名”+“Id”的属性名为主键。根据这些跳过主键就行。你也可以用你自己的方法跳过主键,达到目的就行。做好这些后再来运行一下看看结果:

 

  更新成功。然后你可以将这个GetUpdatePredicate方法封装成泛型,就可以让所有实体类都可以使用这个方法。如果将更新的方法封装成泛型,所有实体的基本更新调用这一个方法就可以了。只需传入不同的实体类。

  可能认为给实体赋新值还是不够灵活。上述已经提到过一种,前端传入实体。用这种方式就就可以比较灵活的通过这种方式去更新数据库。第二种是,当前端传入多个参数的时候,只需将参数名修改成与对应的实体属性名相同即可。然后通过遍历传入的参数,给对应的实体属性赋值就可以比较灵活的用于不同页面对于同一表格不同字段的更新。这里贴上我实现遍历参数给对应实体赋值的代码:

                var task = new TaskModel();

                foreach (var item in Request.QueryString)
                {
                    var taskmodel = task.GetType().GetProperty(item.ToString());
                    string value = Request.QueryString[item.ToString()];
                    var s = Nullable.GetUnderlyingType(taskmodel.PropertyType);
                    taskmodel.SetValue(task, Convert.ChangeType(value, s == null ? typeof(string) : s), null);
                }        

可能你后台使用Request.QueryString并不能接收到参数,就使用Request.Form。然后我还遇到过将所有数据通过Json传入后台的。这也好解决。解决方式个人能力有不同的解决方式。中心思想就是,可以灵活的将这些数据赋值给对应实体对应的属性就好。实现了这个就OK了。

  文中有些地方会有所纰漏,就不要那么计较了啦,毕竟是个刚毕业的菜鸟的第一篇博客。喜欢的喜欢,拿去自己改改用。不喜欢的勿喷。欢迎指点。

posted on 2018-09-26 11:33  含光君  阅读(...)  评论(... 编辑 收藏