EntityFramework之你不知道的那些事(七)

前言

前面一系列几乎都是循序渐进式的进行叙述,似乎脚步走得太快了,于是我开始歇一歇去追寻一些我所不太了解的细枝末节,在此过程中也屡次碰壁,但是唯有如此才能更好的成长,不是吗!希望此文对你亦有帮助。

属性私有化

我们之前有点太循规蹈矩对于模型的建立,所以你才不会遇到问题(当然我也是),也许你大概也这样做过,当建立实体时我们都是建立公有的(public)的,那为什么不试试私有(private)的呢?建立一个学生(Student)类并给其一个私有属性,如下:

    public class Student
    {
        public string Id { get; set; }
        public string Name { get; set; }
        private string TestPrivate { get; set; }
        public string FlowerId { get; set; }
        public virtual Flower Flower { get; set; }

    }

当初始化数据库生成表时,着实令我惊讶了一番,该私有属性并未映射进来。如下:

我依然不相信,于是我将其该属性访问换成受保护的(protected)的,结果依然是岿然不变。

那问题来了,这样的需求肯定是存在的,要是在类中我们不想暴露有些属性想将其设为私有的,我们该如何使其映射到数据库表中呢?

在EF Code First中有一个特性叫自定义Code First约定(Custom Code First Conventions),也就是我们可以自定义配置实体的模式以及规则,然后将其注册到 OnModelCreating 方法中即可。

上述只是讲述进行注册,但是我们需要在上述方法中得到属性集合并设置其属性为 BindingFlags.NonPublic|BindingFlags.Instance ,这就是我们整个完整的想法。基于此,我们开始进行第一次尝试:

            modelBuilder.Properties().Configure(p =>
            {
                var privatePropertity = p.ClrPropertyInfo;                p.HasColumnName(privatePropertity.Name);

            });

          /*看到上述有个Properties方法,但是令人厌恶的是此方法无参数,也就是无法获得私有的属性,所以在约定中进行注册尝试失败*/

想到映射是基于 EntityTypeConfiguration<TEntity> 实现,将其单独放到一个类里,想必是不可能了,因为无法访问其私有属性,于是将其放在Student里,就可以访问私有属性了,那进行第二次尝试,在Student类中进行改造如下:

    public class Student
    {
        public string Id { get; set; }
        public string Name { get; set; }
        private string TestPrivate { get; set; }
        public string FlowerId { get; set; }
        public virtual Flower Flower { get; set; }

        public  class StudentMap : EntityTypeConfiguration<Student>
        {
            public StudentMap()
            {
                ToTable("Student");
                HasKey(key => key.Id);
                HasRequired(p => p.Flower).WithMany(p => p.Students).HasForeignKey(p => p.FlowerId);
                Property(p => p.TestPrivate);
            }
        }

    }

接下来就是验证的时刻了,结果通过,如下:

根据上述EF就属性私有化的问题,我将其描述如下

默认情况下,EF Code First仅仅只映射实体中的公有属性,但是如果你想映射一个属性访问符为受保护的或者私有的,则配置类必须要嵌套在实体类中。

上述虽然能够满足要求,看着也就差强人意,因为实体类不是太干净,并且该实体类也不满足POCO了,并且不利于解耦,想想怎么去改善下即能映射又能使配置类在实体类中,既然只能在本类中访问到该私有属性,那考虑用部分类来实现,所以我们进行第三次尝试来作为改善。我们将Student类原有的一切不动,只是将其标记为部分类(partial)即可,保持其为POCO,在另一个部分类中获得其私有属性,当映射时来访问该部分类即可,基于此,我们给出完整代码:

学生部分类:

    public partial class Student
    {
        public string Id { get; set; }
        public string Name { get; set; }
        private string TestPrivate { get; set; }
        public string FlowerId { get; set; }
        public virtual Flower Flower { get; set; }

    }

获得学生类中私有属性的学生部分类:

    public partial class Student
    {

        public class PrivatePropertyExtension
        {
            public static readonly Expression<Func<Student, string>> test_private = p => p.TestPrivate;
        }
    }

接着在映射中访问该私有属性学生部分类:

    public  class StudentMap :EntityTypeConfiguration<Student>
    {
        public StudentMap()
        {
            ToTable("Student");
            HasKey(key => key.Id);
            HasRequired(p => p.Flower).WithMany(p => p.Students).HasForeignKey(p => p.FlowerId);
            Property(Student.PrivatePropertyExtension.test_private);
        }

    }

经过这么小小的改善后,如果有多个私有属性,只需要将其添加到私有属性的扩展中去即可。

惊喜发现

当你看到上述时是不是感觉已经接近完美了,其实还有一劳永逸的办法【特此声明:非为了制造意外,刚开始在其过程中,我也是这样做的,当我继续向下学习过程中,偶然发现EF居然还是有根本上的解决办法,写到这里只是为了说明我学习的过程,仅此而已。】。在 OnModelCreating 方法中去注册的想法是正确的,只是未找对方法。只需如下一点代码就可以解决所有实体中的非私有属性都会映射到数据库中。

            modelBuilder.Types().Configure(d =>
            {
                var nonPublicProperties = d.ClrType.GetProperties(BindingFlags.NonPublic | BindingFlags.Instance);
                foreach (var p in nonPublicProperties)
                {
                    d.Property(p).HasColumnName(p.Name);
                }
            });

对于上述我们最终将属性私有化总结如下:

默认情况下,EF Code First仅仅只映射实体中的公有属性,但是如果你想映射实体中的所有非公有属性,只需通过上述操作然后在 OnModelCreating 中注册即可。

【注意】上述是在EF 6.0中,如果你EF版本6.0之前可以试试通过下面来进行注册【本人未进行验证,查的资料】

      modelBuilder.Entities().Configure(c =>
      {
          var nonPublicProperties = c.ClrType.GetProperties(BindingFlags.NonPublic|BindingFlags.Instance);

          foreach (var p in nonPublicProperties)
          {
            c.Property(p).HasColumnName(p.Name);
          }
     });

属性internal

上述我们只是测试属性private、protected的情况,似乎落了internal,接下我们来看看,将如下属性设置成internal

  internal string TestPrivate { get; set; }

当我们再次映射时,是不会映射到数据库的,默认情况下,属性能够被映射到数据库必须是public的也就是你无需通过显示指定,当属性被internal修饰时,必须显示指定映射该属性,否则不予映射。在学生映射类显示指定如下:

        public StudentMap()
        {
            ToTable("Student");
            HasKey(key => key.Id);
            HasRequired(p => p.Flower).WithMany(p => p.Students).HasForeignKey(p => p.FlowerId);
            Property(p => p.TestPrivate);
        }

鉴于此,我们总结如下:

当属性被internal修饰时,若想该属性被映射到数据库必须显示指定映射该属性,否则不予映射。

至此属性被修饰符映射的情况就已全部被囊括了。通过上述【惊喜发现】则用这个就多余了,所以就当学习了。

变更追踪

原来不知道变更追踪还有两种形式,在查阅资料过程中才明白过来,这里就稍微详细的来叙述下。【这一部分,个人不是很有信心能说的很好,若有不妥之处,望指出】

快照式变更追踪(Snapshot based Change Tracking)

在【我为EF正名】这个小节中,对此跟踪只是稍微讲了其用途,接下来我们详细的来讨论下该方法。

快照式变更追踪实际上使用的是我们定义的POCO而非从该实体中派生出来的代理类(子类),这是一种很简单的变更追踪方案!下面我们一起来学习下(所用类依然是上述学生类):

            using (var ctx = new EntityDbContext())
            {var entity = ctx.Set<Student>().FirstOrDefault(d => d.Name == "xpy0929");

                Console.WriteLine(ctx.Entry(entity).State);

                entity.Name = "xpy0928";

                Console.WriteLine(ctx.Entry(entity).State);

             }

上述我们查出xpy0929的学生并将其姓名修改为xpy0928,同时打印修改前后的状态。在进行此操作之前我们在上下文中配置如下:

        public EntityDbContext()
            : base("name=DBConnectionString")
        {
            this.Configuration.AutoDetectChangesEnabled = false;
        }

那问题来了,修改前后状态会变还是不变呢?结果打印如下:

接下来我们将此句  this.Configuration.AutoDetectChangesEnabled = false; 去掉再试试看,结果就又变了,如下:

那问题来了,只是关闭了AutoDetectChangesEnabled为什么会出现不同结果呢?

当你对实体进行更改后,此更改不能自动与Entry中的State状态保持同步,因为在POCO实体和EF之间没有能够自动通知的机制,所以即使你显示修改了其值其状态依然为Unchanged。但是当你令  this.Configuration.AutoDetectChangesEnabled = true; 此时就明确了可以调用 DetectChanges 方法,有人到这里估计就得说,尽瞎说,在上下文中哪有DetectChanges方法,肯定没有,因为不在DbContext中,而在ObjectContext上下文中,如下:

  var obj = ((IObjectContextAdapter)ctx).ObjectContext;

  obj.DetectChanges();

因为默认情况下在调用Entry方法时就自动会调用DetectChanges方法,所以其值就进行了修改(Modified)【不知道DetectChanges是什么,请参看前面文章】。

快照式变更追踪利用的是POCO,当查询数据时EF上下文便捕获了数据的快照,当调用DetectChanges方法时,会扫描上下中所有实体并将当前值和快照中的值进行比较,然后作出相关的行为。但是基于上述应意识到它的缺点,实体对象的改变与EF的ObjectStateManager之间是无法进行同步的。(所以这也就解释了在EF中,将AutoDetectChangesEnabled默认启动的原因。)

代理式变更追踪(Notification based Change Tracking with Proxies)

首先还是看官方关于代理的定义:代理是依赖注入的一种形式,其附加功能被注入到实体中,在POCO情况下,因为它们无法进行创建或执行查询,或告诉状态管理器关于发生的改变,但是代理是动态生成的能够覆盖各种属性的一个实体的派生类型,以至于能使额外的代码能运行在该派生类型上,在延迟加载的情况下,其表现在对导航属性的加载。

上面一大堆废话,还不如点实在的,创建一个Flower类的代理类,一目了然

                var obj = ((IObjectContextAdapter)ctx).ObjectContext;

                var entity = obj.CreateObject<Flower>();

如图动态代理类Flower后面紧跟着的大概是哈希码:

接下来,把上述所给数据以及类和相关配置依然不变,我们只是将Student进行修改如下(将所有属性加上virtual)【此时此类将为代理类】

    public class Student
    {
        public virtual int Id { get; set; }
        public virtual string Name { get; set; }
        public virtual int FlowerId { get; set; }
        public virtual virtual Flower Flower { get; set; }

    }

那问题来了,这个操作之后,是否如快照式跟踪状态一样,不会改变呢?

这是什么情况,不是令 this.Configuration.AutoDetectChangesEnabled = false; 了吗,也就是不会调用DetectChanges方法了,应该是UnChanged才对,对吧??当你将类中所有属性设为virtual后,此时与上文中打交道的非POCO而是其派生类即动态代理类,此时实体将总是被跟踪的,并且在状态上与EF是保持同步的,因为当发生改变时,代理类会通知EF来改变值以及关系的修正。总体上来看,这可能使得其更加高效一点,因为ObjectStateManager(对象状态管理器)知道未发生改变的话就会跳过原始值和当前值之间的比较。

所以上述EF上下文能够通过代理类知道数据进行了修改,然后更新其状态。当然,我们也可以在配置中来禁止创建代理类,如下:

        public EntityDbContext()
            : base("name=DBConnectionString")
        {

            this.Configuration.ProxyCreationEnabled = false;

        }

基于此我们可以将数据的更新作出如下描述:

当变更追踪代理关闭后,在查询结果返回之前缓存了该实体的副本也就是利用快照式跟踪,数据更新后调用SaveChanges时会回调DetecChanges来比较其差异并更新到数据库。当未关闭变更追踪代理时,数据更新后,EF上下文知道变更追踪代理当前正在执行,于是将不会创建该实体副本,变更追踪代理会立即修改其值以及进行关系修正最后更新数据到数据库而不会等到调用SaveChanges才更改。

之前一直觉得要加载一个实体中关联的另一个实体只能用Include,后来在dudu老大Where查询条件中用到的关联实体不需要Include的这篇文章中提到当查询条件含有实体的话则将会自动加载该实体,动手一试果然如此。这句话说的没错,但是你们有没有想过,是在什么前提下,这才是正确的呢?(也许当你用时就出错了,难道是人品的问题吗,绝非偶然,存在即合理)下面我们来演示这种情况。

 var list = ctx.Set<Student>().Where(d => d.Flower.Remark == "so bad").ToList();

我们查询出学生的数据,查询条件为关联的实体Flower,这样就自动加载实体Flower了,似乎一切都挺合理,我们通过快速监视来看看如下图分析分析:

查询居然为空,难道dudu老大骗我们吗?No,you wrong,细心的你不知道发现什么别的东西没,你看看查询出来的实体Student的类型居然是ConsoleApplication1.Student,想必到了这里,你应该知道答案了,就是我们在上面演示时,关闭了变更追踪代理,所以这里的实体类不是代理类,导致你查询出来的关联实体为空。不信,我将其关闭,给出下面实体乱七八糟的实体类型的结果你看:

关于此就一句话来解释变更追踪代理的主要作用: 变更追踪代理是非常严格关于集合属性的类型 。同时当我们序列化实体时,非常容易导致循环引用,这时可以考虑关闭变更追踪代理。

那问题来了,当延迟加载时为什么导航属性必须要加上修饰符virtual呢?

因为动态生成的实体的派生类即代理类会指向EF并覆盖修饰符为virtual的导航属性,当EF被触发时,使得这些属性能够被监听和延迟加载。这也就说明了延迟加载必须满足三个条件。

this.Configuration.ProxyCreationEnabled = true;

this.Configuration.LazyLoadingEnabled = true;

导航属性修饰符必须为Virtual

那问题又来了,我们什么时候应该用代理式变更追踪呢,它的优缺点又在哪里呢?

代理式变更追踪的优点

  1. 被跟踪的实体,当数据发生改变时会立即被检测到并进行关系修正而不是等到DetectChanges方法被调用
  2. 在某些场景下,性能也会有所提高

代理式变更追踪的缺点

启用变更追踪代理的规则具有严格性和限制性

  1. 实体类必须是public并且不能是密封类
  2. 所有的属性必须为访问修饰符为public或者protected并且修饰符必须为virtual且访问器有get和set
  3. Collection导航属性保证声明为为ICollection<T>(当然也可以为IList<T>等,建议为IColletion<T>以免出现意想不到的结果)
  4. 在某些场景下,性能也可能会比较差

代理式变更追踪的性能

值得注意的是,在深入一些具体的性能场景下,对于许多应用程序来说利用代理式变更追踪和快照式变更追踪来跟踪实体,这两者在性能上没有什么显著的区别。我们只需谨记一条法则:

在一个应用程序的上下文实例中跟踪成百上千个实体的同时你将会看到不同,但是对于大多数应用程序来说使用简单的快照式变更追踪就已经好了。

变更追踪代理提供最大的性能优势是当追踪很多实体时,只更改实体中少数,这是因为我之前有讲过快照式变更追踪会扫描和比较实体,而变更追踪代理无需进行扫描而是每个实体被更改时会被立即检测。

现在我们从反面来想,在每个被追踪的实体上都有修改,此时需要对每个实体的改变作出反应这样就产生了额外的代价,这种额外的代价就是设置下实体的属性通常还是非常快的,当然,这也意味着要通过额外的代码来记录状态的变化,但是很快实体多了,这影响就显而易见了。

就以下简单的实体类型我们来进行分析:

    public class SimpleClass
    {
        public int id { get; set; }

        public int property1 { get; set; }

        public int property2 { get; set; }

        public string property3 { get; set; }

        public string property4 { get; set; }

    }

我们通过修改10000个被追踪的实体的数据并调用SaveChanges来考虑其性能,代码如下:

                ctx.Database.Log = Console.WriteLine;

                Stopwatch watch = new Stopwatch();

                watch.Start();

                ctx.Set<SimpleClass>().ToList().ForEach(d =>
                {

                    var prop1 = d.property1;
                    d.property1 = d.property2;
                    d.property2 = prop1;

                    var prop3 = d.property3;
                    d.property3 = d.property4;
                    d.property4 = prop3;
                });
                ctx.SaveChanges();

                watch.Stop();

                Console.WriteLine(watch.ElapsedMilliseconds);

此时更新10000条数据需要92115毫秒即92秒,如下:

此时将SimpleClass所有属性加上Virtual,再来看看更新需要90460毫秒即90秒

【注意】根据笔记本配置不同可能结果会略有差异,但是可以得出结论的是即使将其变为变更追踪代理类其性能也没有多大提升。

在一个应用程序中,在调用SaveChanges方法之前如果对一个属性作出多处更改,此时使用快照式变更追踪的时间比代理式变更追踪的时间就稍微短一点点,但是至此代理式变更追踪的性能会越来越差。

接下来我们再来看一种情况,在应用程序中假如我们将属性设置到值那么代理式变更追踪的性能又会如何呢?假设这样一个情景我们现在要序列化类,此时肯定必然要将代理类DTO然后序列化,鉴于此我们给出如下代码:

代理类:

    public class SimpleClass
    {
        public virtual int id { get; set; }

        public virtual int property1 { get; set; }

        public virtual int property2 { get; set; }

        public virtual string property3 { get; set; }

        public virtual string property4 { get; set; }
    }

进行DTO然后序列化该代理类

           using (var ctx = new EntityDbContext())
            {

                ctx.Database.Log = Console.WriteLine;

                Stopwatch watch = new Stopwatch();

                watch.Start();

                ctx.Set<SimpleClass>().ToList().ForEach(d =>
                {
                    var simple = new SimpleClass();
                    simple.id = d.id;
                    simple.property1 = d.property1;
                    simple.property2 = d.property2;
                    simple.property3 = d.property3;
                    simple.property4 = d.property4;
                    new Program().Clone(simple);
                });

                ctx.SaveChanges();

                watch.Stop();

                Console.WriteLine(watch.ElapsedMilliseconds);

深度复制方法:

        public object Clone<TEntity>(TEntity t) where TEntity : class
        {
            using (MemoryStream stream = new MemoryStream())
            {
                XmlSerializer xml = new XmlSerializer(typeof(TEntity));
                xml.Serialize(stream, t);
                stream.Seek(0, SeekOrigin.Begin);
                return xml.Deserialize(stream) as TEntity;
            }

        }

我们测试依据代理式更追踪来序列化10000个实体的时间为1.713秒,如下:

接下来我们去掉所有属性的virtual,再来测试利用快照式变更追踪的时间为1.667秒

【注】可能跟我笔记本配置低(cpu为i3,内存 4g)有关,理论上来说快照式变更追踪的所需时间应该比代理式变更追踪的时间要少很多,为何这样说呢?因为基于快照式变更追踪数据没有发生任何变化,我们只是进行了赋值而已,所以不会调用SaveChanges时不会对数据库进行写的操作,但是代理式变更追踪只要对属性进行了写的操作就会标记为已修改,所以所花时间应该更多。

总结

对于代理式变更追踪个人而言似乎没有什么可用之处,似乎在园子里也没看见园友们将所有属性标记为virtual作为代理类来用,再者EF 团队在代理式变更追踪方面并没有过多的去投入,因为我是查资料看到了这个,所以就纯属了解了,知道有这个东西就ok了。

所以我的建议是:用virtual只是用于导航属性的延迟加载,如果你想用代理式变更追踪除非你真正的理解了它的问题并有一个合理的理由去使用它(暂时我是没发现它好在什么地方),当然这也就意味着当用快照式变更追踪遇到了性能瓶颈而考虑用代理式变更追踪。(大部分情况下还是不会)。

基类映射

在EF 6.0以上版本中在派生类中无需手动添加对基类的映射,当派生于基类时,只需对派生类进行配置即可,基类属性自动进行映射。

假设这样一个场景:我们定义一个基类Base,将此类中的Id作为Blog和Post类中主键。一个Blog当然对应多个Post。

基类:

    public class Base
    {
        public int Id { get; set; }

        public byte[] Token { get; set; }
    }

Blog和Post类

    public class Blog : Base
    {
        public string Name { get; set; }

        public string Url { get; set; }

        public virtual ICollection<Post> Posts { get; set; }
    }
    public class Post : Base
    {
        public string Title { get; set; }

        public string Content { get; set; }

        public int BlogId { get; set; }

        public virtual Blog Blog { get; set; }
    }

相关映射:

        public BlogMap()
        {
            ToTable("Blog");
            HasMany(p => p.Posts).WithRequired(p => p.Blog).HasForeignKey(p => p.BlogId);
        }

        public PostMap()
        {
            ToTable("Post");
        }

上述我们未对基类进行任何映射,但是生成表时基类的Id将作为派生类Blog和Post的主键以及Token的映射,如下:

但是在EF 6.0之前无法配置一个不能映射的基类,如果你想配置一个定义在基类上的属性,此时你可以一劳永逸通过配置来进行完成,如下:

            modelBuilder.Types<Base>().Configure(c =>
            {
                c.HasKey(e => e.Id);
                c.Property(e => e.Token);
            });
时间: 2024-12-03 05:09:14

EntityFramework之你不知道的那些事(七)的相关文章

开源 VS 商业,消息中间件你不知道的那些事

11月23日,新炬网络中间件技术专家刘拓老师在DBA+社群中间件用户组进行了一次主题为“开源 VS 商业,消息中间件你不知道的那些事”的线上分享.小编特别整理出其中精华内容,供大家学习交流. 嘉宾简介 新炬网络中间件技术专家 曾任职于IBM华南GTS 4年,IBM WebSphere.MQ.CICS产品线技术专家 5年移动运营商(广东移动.浙江移动)运维经验,3年JAVA开发及售后经验 演讲实录 随着云计算的兴起,Docker.微服务的流行,分布式消息队列技术成为云计算平台中不可或缺的组件.今天

老斜两宗事-七层代理模式还是IP层VPN

1.七层代理模式还是IP层VPN 很多人会问,我到底是使用代理模式呢,还是使用VPN模式,如果我想数据在中间不安全的链路上实现加密保护的话.这个问题有一个背景,那就是,你想保护你的数据,可以使用VPN,但是有时候,第七层的代理模式或许更好,比如SSL卸载器,比如内置SSL处理的代理,分为正向代理和反向代理.正向代理:代理的是访问者.一般位于访问者一端,访问者能意识到代理的存在,直接访问代理,由代理向服务器发起访问.反向代理:反向代理代理的是被访问者.位于被访问者一端,访问者意识不到代理的存在,访

Asp.Net Core 中间件应用实践中你不知道的那些事

一.概述 这篇文章主要分享Endpoint 终结点路由的中间件的应用场景及实践案例,不讲述其工作原理,如果需要了解工作原理的同学, 可以点击查看以下两篇解读文章: Asp.Net Core EndPoint 终结点路由工作原理解读 ASP.NET CORE 管道模型及中间件使用解读 1.1 中间件(Middleware)的作用 我们知道,任何的一个web框架都是把http请求封装成一个管道,每一次的请求都是经过管道的一系列操作,最终到达我们写的代码中.那么中间件就是在应用程序管道中的一个组件,用

EntityFramework Code-First 简易教程(七)-------领域类配置之Fluent API

Fluent API配置: 前面我们已经了解到使用DataAnotations特性来覆写Code-First默认约定,现在我们来学习Fluent API. Fluent API是另一种配置领域类的方法,它比DataAnnotations特性提供更多的配置方法,下表是Fluent API支持的类型映射. 映射种类 配置数据库 模型(Model-wide)映射 设置默认架构 设置自定义约定 实体(Entity)映射 设置单表或多表和设置架构 设置复杂类型 设置继承层次结构 属性(Property)映

transform你不知道的那些事

transform是诸多css3新特性中最打动我的,因为它让方方正正的box module变得真实了. transform通过一组函数实现了对盒子大小.位置.角度的2D或者3D变换.不过很长时间内,我对以下问题都想不太明白: 1.尺寸缩放scale与zoom变换有何不同,为什么被scale的盒子里的内容不会错位,但zoom不是. 2.位移(transform:translate)与相对定位.绝对定位(position:relative | absolute)有何关系? 3.在实际项目中发现,位图

关于cout&lt;&lt;ends你不知道的那些事

关于ends是C++中比较基础的一个东西,但是可能不是每个人都能够清楚的理解这是个什么东西,我就经历了这么一个过程,写出来让大家看看,有什么理解的不对的地方欢迎拍砖. 今天以前我对ends的理解是:输出空格的工具,或者说这就是一个逼格比较高的" ".(这貌似是拜老师所赐,特地翻出课件发现就是这么写的,输出空格...相信有不少人是这么看的吧) 今天由于某些原因发现 cout<<ends;和cout<<" ";貌似不是一个东西,于是开始探究: 一

editplus - 你不知道的那些事

一. 使用editplus去重: 1. 菜单栏选择"工具" -> "排序" 2. 勾选"删除重复行", 再点击"排序"就可以了 二. 矩形选取: 按住alt键, 然后用鼠标划矩形选取.这个在批量删掉前端行号的时候很有效.

iOS应用安全开发,你不知道的那些事

来源:http://www.csdn.net/article/2014-04-30/2819573-The-Secret-Of-App-Dev-Security 摘要:iOS应用由于其直接运行在手机上,相比运行在服务器的后台服务,更有可能被黑客攻击.本文将从网络安全.本地文件和数据安全.源代码安全三个方面,阐述iOS应用在安全性上遇到的挑战. 联网领域,安全已然是一个老生常谈的话题.许多大公司都设置有专门的安全部门,用于检测自己产品的安全性.但即便是这样,业界仍然时常爆出许多安全问题引发的新闻.

Javascript你不知道的那些事!(数字计算篇-变态篇)

javascript:alert(0.1 + 0.2) 如果看到这样一道题你会怎么思考了!大家肯定第一反应0.3,但是考虑到我已经这样问了!那么幼稚的答案我会专门写篇文章吗 然后人就开始折磨自己了会不会是 0.10.2呢 然后结果还是很拉风的 0.30000000000000004 再来0.1+0.7 0.7999999999999999 这是由于十进制到二进制的转换导致的精度问题!因为计算机执行的是二进制算术,当一个十进制数不能准确的转化着二进制数时,这种精度误差就无法避免.如果对这简单的原因