项目2.0上线,回想过后杂谈总结基础回顾一番

前言

项目2.0基本已经上线了,之前老大问我最近还有在更博客没,我说没,同事说是不是已经挖掘尽了没有什么可写的了,其实不然,对于项目而言,我们都只是负责项目中的冰山一角,没有一个全局观来看待整个项目,急急忙忙的赶着项目,项目中有很多优秀的地方都值得我去效仿和学习,尤其是老大的技术令我折服,这两天不是太忙,就随手看了下自己写的代码,一脸懵逼,心想这是哪个傻逼写的代码,就像新入职同事看着刚离职交接项目的同事的代码一样复杂的心情,但是项目已经划了基线,除非是bug已经不能轻易再发布了,所以让这份记忆暂且存放自己博客中吧。本篇属于各种杂谈,没有一个统一方向,想到哪里写到哪里。

EntityFramework/EntityFramework Core缓存

之前一直有在谈无论是之前的EntityFramework还是现已经跨平台的EntityFramework Core都有其变更追踪,说白了就是缓存,那么到底怎么知道是缓存了还是没有缓存呢,下面我们来看看例子。首先看看数据库中id=2的数据

上述我们只需知道id = 2和Name = "Jeffcky",下面我们进行如下查询:

        public void Query()
        {
            var blog1 = _efCoreContext.Blogs.Find(2);
            blog1.Name = "Jeff";

            var blog2 = _efCoreContext.Blogs.FirstOrDefault(d => d.Name == "Jeffcky");
            var blog2Name = blog2.Name;

            var compareResult = ReferenceEquals(blog1, blog2);

            var blog3 = _efCoreContext.Blogs.FromSql(@"SELECT TOP (1) [Id], [Name], [Url], [Status], [CreatedTime]
            FROM dbo.[Blog]").Single();

            var compareResult1 = ReferenceEquals(blog1, blog3);
        }

如果不存在缓存那么blog2Name = "Jeffcky"且compareResult = false,compareResult1 = false,但是请看如下演示结果:

如果关闭变更追踪,此时如下则compareResult = false:

            var blog1 = _efCoreContext.Blogs.Find(2);
            blog1.Name = "Jeff";

            var blog2 = _efCoreContext.Blogs
                .AsNoTracking()
                .FirstOrDefault(d => d.Name == "Jeffcky");
            var blog2Name = blog2.Name;

            var compareResult = ReferenceEquals(blog1, blog2);

多次循环遍历数据修改

这样的场景应该很常见,查出一个集合中包含另外一个集合,但是呢,查询出数据后需要对该集合中的另外一个集合数据进行处理,比如查询出中Blog集合中存在Post集合,此时需要将Posts中的集合数据中的Title进行修改,此时你会怎样做呢?怎样做才会更加优雅呢,下面是我原始做法:

        public void Query()
        {
            var blogs = _efCoreContext.Blogs
                .Include(d => d.Posts)
                .ToList();

            foreach (var blog in blogs)
            {
                foreach (var post in blog.Posts)
                {
                    post.Title = "[置顶]EntityFramework之DetectChanges's Secrets(三)(我为EF正名)";
                }
            }
        }

此时到这里任务算是完成了,事后返回再看感觉做法是不是有点low,说到底还是对集合中各种方法了解太少的缘故去看了看对集合操作的扩展方法有两种方法就可规避这种循环遍历问题,很多时候我们需要查找判断一个集合中是否存在满足条件的数据然后进行下一步操作这个时候我们想到会利用Any解决如下:

            var blogs = _efCoreContext.Blogs
                .Include(d => d.Posts)
                .ToList();

            var isExist = blogs.Any(d => d.Status == 0);

            if (isExist)
            { }

又或者全部满足才进行下一步操作,此时利用All判断:

            var blogs = _efCoreContext.Blogs
                .Include(d => d.Posts)
                .ToList();

            var isAllSatisfy = blogs.All(d => d.Status == 0);

            if (isAllSatisfy)
            { }

但是我们只是仅止于此对二者的使用,对于上述循环遍历修改数据的问题就可用All来优雅解决上述遍历恶心的问题:

 

            var blogs = _efCoreContext.Blogs
                .Include(d => d.Posts)
                .ToList();

            blogs.All(b =>
            {
                b.Posts.All(p => { p.Title = "[置顶]EntityFramework之DetectChanges's Secrets(三)(我为EF正名)";return true; });
                return true;
            });

如此优雅的修改集合中集合的数据,可能是个人感觉吧,有了几层循环就感觉特别恶心就想着是不是有更加简洁或者优雅的解决方式,我们追求的是代码的优雅和简洁而不是繁琐和臃肿。 下面我们再来看看其他解决方案,通过SelectMany投影解决:

 

            var blogs = _efCoreContext.Blogs
                .Include(d => d.Posts)
                .ToList();

            var posts = blogs.SelectMany(b => b.Posts);

            foreach (var post in posts)
            {
                post.Title = "[置顶]EntityFramework之DetectChanges's Secrets(三)(我为EF正名)";
            }

如此一看也就一层循环比最土最low的方案甚是优雅别致多了。

那么问题就来了,Select和SelectMany有什么区别哟?

我们直接查看如下二者返回值即可:

 

 或许将上述二者返回值用更具体的返回值类型给出更加明了,如下:

            IEnumerable<IEnumerable<Post>> selectBlogs = blogs.Select(d => d.Posts);

            IEnumerable<Post> selectManyPosts = blogs.SelectMany(b => b.Posts);

由上知Select返回的是Posts集合的集合,即将Blogs中的每一项Posts作为一个集合最外围是这整个每一项的集合,而SelectMany则是返回Blogs中的所有Posts将其作为一个集合而返回。

数字类型集合比较是否相等 

在项目中有这样一个场景:一个产品有许多属性,比如颜色,内存,大小,第一次则是进行创建生成唯一sku,下次可以重新添加属性中的值,比如第一次创建时只添加了属性是颜色,属性值为金色的手机,第二次再来添加属性为颜色,属性值为白色的手机,如此一来则和其他属性值进行重新生成新的sku,但是此时又要保证不能和之前已创建的sku重复,此时就要将每组属性中属性值组成的数据和已组合的属性值进行比较,若存在则跳过,否则则创建sku,由于此时生成的属性值顺序可能又不同,所以此时我将每一组组合的属性值放在List集合中,然后再排序,然后再来比较,但是如何比较两个集合中的数字类型的数据是一样的呢,于是最终转换成了字符串的判断:

            var data1 = new List<int>() { 2, 3, 4, 6, 8, 9 };

            var data2 = new List<int>() { 3, 6, 8, 2, 4, 9 };
            data2.Sort();

            var str1 = string.Join("-", data1.ToArray());
            var str2 = string.Join("-", data2.ToArray());

            var isEquals = str1.Equals(str2);

也算是达到了预期,但是看起来还是有点low,于是今天又去看了看集合中的扩展方法,居然可以直接判断两个集合是否相等的方法,当然这是证对于值类型而言,若是引用类型,引用地址都不一样即使数据一样肯定是不相等的,这点大家都明白就无需我废话了。

            var data1 = new List<int>() { 2, 3, 4, 6, 8, 9 };

            var data2 = new List<int>() { 3, 6, 8, 2, 4, 9 };
            data2.Sort();

            var isSequenEqual = data1.SequenceEqual(data2);

 

 这样一写又比上述将集合转换成数组,然后转换成字符串的形式更加优雅,一步到位,我居然没想到,shit。

 Cast和OfType区别

 在集合中需要对集合数据进行类型转换有Cast和OfType两种形式,例如如下皆可:

            var list = new List<int>() { 2, 3, 4, 6, 8, 9 };

            IEnumerable<string> casList = list.Cast<string>();

            IEnumerable<string> ofTypeList = list.OfType<string>();

上述二者转换皆可,那给出二者转换的意义在哪里呢?这个需要好好想想。

OfType:只是将集合中的数据能/可以转换成需要转换的类型进行转换。

Cast:将集合中所有数据转换成需要转换的类型。

Cast相对OfType而言将鼠标放在此方法上会发现多了如下转换异常的类(InvalidCastException)

例如对如下数据进行类型转换:

            object[] objArray = new object[] { "12345", 12 };
            var objCast = objArray.Cast<string>().ToArray();
            var objOfType = objArray.OfType<string>().ToArray();

上述我们已经讨论过二者的区别,此时利用Cast则转换失败出现InvalidCastException。而OfType则将字符串“12345”进行转换。

 

 

如上Cast和OfType内部本质实现原理如下:

        public  IEnumerable<T> Cast<T>(this IEnumerable source)
        {
            foreach (object o in source)
                yield return (T)o;
        }

        public  IEnumerable<T> OfType<T>(this IEnumerable source)
        {
            foreach (object o in source)
                if (o is T)
                    yield return (T)o;
        }

这样就不难解释Cast是全盘转换,而OfType是满足条件类型才转换。

数据类型正确且对数据进行重复过滤 

对于客户端传过来的数据永不可信,在客户端调用接口时第一时间就要对参数进行校验才进行下一步操作,有这样一个场景,客户端将数据拼接成字符串,我们需要获取其中整型且过滤其中重复以免返回重复数据,如下一个字符串:

 var str = "1,2,4,eee,4,7,8,s,j,1";

自从有了新语法特性出现后,对于TryParse无需再额外定义out类型参数,若转换失败则out类型参数为默认值,如此一来进行如下几行代码即可解决问题。

            var list = new List<int>();
            var str = "1,2,4,eee,4,7,8,s,j,1";
            var splitArray = str.Split(',');
            foreach (var data in splitArray)
            {
                int.TryParse(data, out int intData);
                if (list.Contains(intData) || intData <= 0) { continue; };
                list.Add(intData);
            }

当然则是客户端传来的为字符串,直接返回int数组就无需转换了不是,都可以,办法总是有的,就看如何简便的解决不是,不必纠结于此。

判断引用类型为NULL

有些知识点都了解且都知道,但是没有应用场景,等到有了应用场景却忘却了技术知识点,所以还是要擦亮眼睛,所以需要多看看别人优秀的代码或者开源的东西就知道什么时候该用,什么时候不该用,比如如何判断引用类型是否为空的情况。50%以上的人判断类型是否为NULL,通过如下判断.

            var list = new List<int>() { 1, 2, 3, 4, 5 };
            if (list == null)
            {

            }

你是否还记得ReferenceEquals,它只判断引用类型,值类型永远为false,估计学过就知道这么回事,然而判断引用类型是否为空就可以用它来判断。

            var list = new List<int>() { 1, 2, 3, 4, 5 };
            if (ReferenceEquals(list, null))
            {

            }

关于利用ReferenceEquals来判断为NULL的情况还是看的EntityFramework Core源码,里面判断为NULL都是这么判断,不好听一点就是装装逼,好听一点则是优雅一点,C#语法就是两个字【优雅】,当然在这里并不是想推翻什么或者建议什么,二者皆可,只是想表明任何语法的出现都有其应用场景,有些你很少用到的语法就遗漏了,有时候要适当的去补补基础,去对基础回回炉,这是我想说的观点,且勿断章取义。

总结

无论是日常自学还是项目过后也好,都需要抽时间去整理和总结一下,要不然下次还是会采取同样不合理的方案去解决,上述说述各种方案且不说本质上性能是一样的,至少看起来更加优雅且代码书写量更少不是,而不是一眼放去几层遍历,有时候我们觉得代码有点看不下去这个时候就要想想重构或者是否有更加简洁的方式来实现,这样才能更快成长起来,才能走得更远,技术才能积累的更多,日积月累,总结的多了,技术也就上了,不过是花费半天的功夫而已,哪有现成的事情,有些东西没接触过,亲身验证或者走过坑,也就长见识了,后续会陆陆续续更新项目当中遇到的问题和开始学习VUE,项目一直在用VUE,接下来可能会开始更新VUE,不止于此其他也会同步更新,see u。

posted @ 2017-08-11 00:57  Jeffcky  阅读(5142)  评论(24编辑  收藏  举报