在EF中使用Expression自动生成p=>new Entity(){X="",Y="",..}格式的Lambda表达式灵活实现按需更新

一、基本介绍     

回忆:最早接触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了。

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

GetUnderlyingType(Type nullableType)方法是用来返回一个可为空类型的基础类型,如果 nullableType 参数不是一个封闭的Nullable<T>泛型,则反回null

Console.WriteLine(Nullable.GetUnderlyingType(typeof(Nullable<int>)));
 2     //输出结果:System.Int32
 3
 4     Console.WriteLine(Nullable.GetUnderlyingType(typeof(Nullable<>)) == null);
 5     //输出结果:True
 6
 7     Console.WriteLine(Nullable.GetUnderlyingType(typeof(int)) == null);
 8     //输出结果:True
 9
10     Console.WriteLine(Nullable.GetUnderlyingType(typeof(string)) == null);
11     //输出结果:True

https://www.cnblogs.com/hgjmagic/p/9682196.html

原文地址:https://www.cnblogs.com/sjqq/p/10017903.html

时间: 2024-08-24 05:31:25

在EF中使用Expression自动生成p=>new Entity(){X="",Y="",..}格式的Lambda表达式灵活实现按需更新的相关文章

mysql中timestamp的自动生成与更新

转自:mysql中timestamp的自动生成与更新 MYSQL中TIMESTAMP类型可以设定默认值,就像其他类型一样.1.自动UPDATE 和INSERT 到当前的时间:表:---------------------------------Table Create Table ------ -------------------------- CREATE TABLE `t1` ( `p_c` int(11) NOT NULL, `p_time` timestamp NOT NULL DEF

asp.net中使kindeditor自动生成缩略图

kindedtor编辑器,确实很好用,但是也有很多的不足,比如,我们经常用的图片上传功能,首页如果有图片新闻或者需要显示宿略图的时候,你会发现它并没有提供图片上传自动生成缩略图的功能 ,于是,花了一点时间,对它的上传图片的功能进行了改写,废话少说,直接上代码 图片上传功能调用的是:upload_json.ashx  代码如下 复制代码 using System; using System.Collections; using System.Web; using System.IO; using

Editplus中如何取消自动生成的bak文件

Editplus是一款很好的文本编辑器,但是默认会生成后缀名为.bak的备份文件,很容易造成文件目录的混乱. 其实可以通过修改参数属性,让Editplus不要生成这种文件,以Editplus3.3.1为例, 去掉create backup file when saving 上的勾就行了. 如何设置EditPlus的默认文件格式.编码方式及制表符 首先,在Tools(工具)下拉后选择Preferences(首选项),弹出首选项对话框. 然后,在下面的对话框中的左侧选择Files设置项,在右侧面板中

Intellij IDEA 14中使用MyBatis-generator 自动生成MyBatis代码

Intellij IDEA 14 作为Java IDE 神器,接触后发现,非常好用,对它爱不释手,打算离开eclipse和myeclipse,投入Intellij IDEA的怀抱. 然而在使用的过程中会发现Intellij IDEA也有一些不尽如意的地方,难免会有些不爽:Intellij IDEA 的插件库远不及eclipse的丰富. mybatis-generator在eclipse中有专门的插件,而没有开发出Intellij IDEA能够使用的插件. 不过不用灰心,如果你的项目是使用mave

CSDN中根据文章自动生成文章目录

概述 CSDN中有根据文件内容中H标签在文章中自动生成文章目录,看起来比较专业,就想把它搬到自己的博客园中.类似下图 提取JS脚本 通过浏览器开发者工具(IE/Chrome)找到产生文章目录javascript脚本(我直接用IE开发工具中搜索相关字符串”系统根据文章中H1到H6标签自动生成文章目录”搜索到的), 并把其中文章内容ID修改成博客园的ID(#cnblogs_post_body) 产生脚本如下脚本如下: $(document).ready(function() { buildCTabl

mongoengine中collection名字自动生成机制浅探

项目碰到要使用mongodb的场景,以前只听过这一强大的文档数据库,但一直没有真正使用过,参考一下项目中已有的使用代码,是通过import mongoengine这一模块实现python服务对db中collection的增删查改. mongoengine的项目网站http://mongoengine.org 中介绍到: MongoEngine is a Document-Object Mapper (think ORM, but for document databases) for worki

Velt中的Makefile自动生成

VELT的全称是Visual EmbedLinuxTools,它是一个与visual gdb类似的visual studio插件,用以辅助完成Linux开发.利用这个插件,将可以在visual studio的IDE中进行Linux应用程序的开发(包括编译和调试),也可以进行uboot和linux内核的编译,并根据编译时的错误信息正确定位到源码.目前的版本是0.2.1,支持vs2012/vs2013/vs2015. 下载地址:http://pan.baidu.com/s/1nt6bOOL Velt

IDEA 中使用MyBatis-generator 自动生成MyBatis代码

0.在Intellij IDEA创建maven项目 1. 在maven项目的pom.xml 添加mybatis-generator-maven-plugin 插件 <build> <finalName>xxx</finalName> <plugins> <plugin> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-

SpringBoot中mybatis的自动生成

1.在pom文件中加入自动生成的插件 <!-- mybatis generator 自动生成代码插件 --> <plugin> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-maven-plugin</artifactId> <version>1.3.2</version> <configuration>