zoukankan      html  css  js  c++  java
  • 《深入了解 Linq to SQL》之对象的标识 —— 麦叔叔呕心呖血之作

    序言

    很多朋友都向我提过,希望我写一下关于Linq to SQL 或者 VS 插件方面的文章。尽管市面上有很多 Linq to SQL 的书籍,但是都是介绍怎么用,缺乏深度。关于 VS 插件方面的书籍也是很显浅,按书籍做出来的东西,只能是学生级别的东西,根本拿不出手。他们觉得我有这个能力写好。

    从技术能力的角度来说,的确是不存在什么问题,但是,要把一门技术讲精讲透,是花很时间的事情。自己付出了很多,如果不能得到读者的认同,那这个专题写下去也没什么意义了。这个专题不是教你怎么使用Linq to SQL,而是让你明白Linq to SQL的原理,对于想写ORM的朋友,绝对不可错过。写完《深入了解 Linq to SQL》这个系列后,下一个系列就是《VS 插件开发了》,所以,大家如果希望我继续写下去,请记得点推荐。你们的推荐,就是我写下去的动力。

    概述

    关于对象的标识,简单点说,就是主键相同的对象,在数据上下文的缓存中,只有一个。数据上下文,在加载数据,创建对象之后,接着对所创建的每个实体类的对象,都会克隆一份对象副本(浅复制)记住这点,我们在后面要用到,用来保存对象的初始值,当对象的属性值修改后,副本的属性值是不改的。注意,只有实体类对象才会创建对象副本,而匿名类对象是不会生成副本,也只实体。我们看下面一段代码:

    例一

    var c1 = db.Categories.Single(o => o.CategoryId == 1);
    var c2 = db.Categories.Single(o => o.CategoryId == 1);

    在例一这个例子中,c1、c2 是相等的,我们再来看下面一个例子:

    例二

    var c1 = db.Categories.Select(o => new { o.CategoryId, o.CategoryName }).Single(o => o.CategoryId == 1);
    var c2 = db.Categories.Select(o => new { o.CategoryId, o.CategoryName }).Single(o => o.CategoryId == 1);

    这个例二这个例子中,c1、c2 是不相等的。

    为什么匿名对象不支持对象标识呢?因为匿名对象,不一定有主键。而 Linq to SQL ,是通过实体的主键来标识一个实体对象的,当从数据库加载完数据,会先根据主键检索缓存中是否有已经存在的对象,没有才会创建对象。这样会引起一个什么样的问题呢?执行下面的代码。

    var c1 = db.Categories.Single(o => o.CategoryId == 1);

    假设 CategoryId 为 1 的 CategoryName 为 “Beverages”,打开数据库,把该值改为“New Name”后,如下图:

    图一

    然后再执行下面的代码:

    var c2 = db.Categories.Single(o => o.CategoryId == 1);

    这时候 c2 修改后的 c2.CategoryName 的值是什么呢?仍然为“Beverages”!因为第二次加载完数据的时候,由于已经存在了主键(CategoryID)为“1”的Category,所以在第二次的加载中,不再创建新的对象,以是使用之前所创建的Category对象。那怎么样才能使用 c2 的 CategoryName 的值为最新值“New Name”呢?调用数据上下文的 Refresh 方法即可。

     db.Refresh(RefreshMode.OverwriteCurrentValues, c2);

    跟踪属性的更改

    我们先来看一个更新的例子:

    var c1 = db.Categories.Single(o => o.CategoryId == 1);
    c1.CategoryName = "xxx";
    db.SubmitChanges();
    
    c1.CategoryName = "xxx";
    db.SubmitChanges();

    通过查看生成的SQL,我们可以发现,尽管 SubmitChagens 方法执行了两次,但是实际上SQL只执行了一次,因为第二次的 CategoryName 并没有修改,那么Linq to SQL如何得知某一个属性是修改了或者没有呢?我们看Category实体类的定义,为了节省篇幅,只选取一部份。

    [Table(Name="Categories")]
    public partial class Category : INotifyPropertyChanging, INotifyPropertyChanged
    {
        [Column(Storage="_CategoryName", DbType="VarChar(15)", CanBeNull=false, UpdateCheck=UpdateCheck.Never)]
        public string CategoryName
        {
            get
            {
                return this._CategoryName;
            }
            set
            {
                if ((this._CategoryName != value))
                {
                    this.OnCategoryNameChanging(value);
                    this.SendPropertyChanging();
                    this._CategoryName = value;
                    this.SendPropertyChanged("CategoryName");
                    this.OnCategoryNameChanged();
                }
            }
        }
            
            
        protected virtual void SendPropertyChanged(String propertyName)
        {
            if ((this.PropertyChanged != null))
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }

    我们可以看得到,实体类Category实现了 INotifyPropertyChanged 的接口,通过这个接口,就可以得知某个属性是否已经更改了,但是,事实上Category实体类不实现上面的接口,也是没有问题的,如下所示:

    [Table(Name = "Categories")]
    public partial class Category
    {
        [Column(Storage = "_CategoryName", DbType = "VarChar(15)", CanBeNull = false, UpdateCheck = UpdateCheck.Never)]
        public string CategoryName
        {
            get { return this._CategoryName; }
            set { this._CategoryName = value; }
        }
    }

     为什么这样也可以呢?记得我们刚才说到对象副本吗?当 Category 没有实现 INotifyPropertyChanged 这个接口时,Linq to SQL会将Category实体对象和原始的对象副本作比较来得知Category的属性值是否更改了。

    但是下面的定义,会导致 CategoryName 的值即使用修改了,也不会更新到数据库。因为,当 CategoryName 的值发生变化的,并没有通知到接口 INotifyPropertyChanged,而当实体类继承于INotifyPropertyChanged时,Linq to SQL是依赖INotifyPropertyChanged来获取值被更改的属性。

    [Table(Name = "Categories")]
    public partial class Category: INotifyPropertyChanged
    {
        [Column(Storage = "_CategoryName", DbType = "VarChar(15)", CanBeNull = false, UpdateCheck = UpdateCheck.Never)]
        public string CategoryName
        {
            get { return this._CategoryName; }
            set { this._CategoryName = value; }
        }
    }

    由上面我们可以知道,通过对象标识,可以使得Model的定义最为简洁,在Linq to SQL的设计里,你会常常可以看到,很多地方都遵循简洁模型这个原则,这个原则,在后面的内容里会给大家介绍。同时你会发现,如果对于没有定义好主键的实体,是不能进行添加、更新、删除的操作,因为这些操作都依赖于对象标识,而对象标识又需要实体的主键。又因为主键是用来标识对象的,所以主键是不能何改的。

    备注

    ALinq 提供了 Insert、Update、Delete 三个方法,这三个方法,只是简单地将 Linq 的 Lamdba 表达式转成 SQL 语句然后再执行。因此,它不依赖于对象标识。所以它可以:

    1、没有主键也能更新

    2、可以更新主键的值

    总之,SQL 能操作的,它都可以。关于这几个方法的使用,ALinq 的文档中都有介绍,看文档即可。

    它不可以:

    1、该方法不能被扩展方法置换。

    例如:如果你使用的是 InsertOnSubmit(customer), 你可以通过重写 InsertCustomer 方法,替换原有的方法。当然你需要将一个表拆分成两个时,非常有用。如果你用的是 Insert 方法,则无效。

    2、ALinq Inject 注入框架无法对其注入。

     Insert方法的应用实例 

     http://cn.alinq.org/blog/InsertMethodSample

     

  • 相关阅读:
    二分图最大匹配的König定理及其证明
    HDOJ 2389 Rain on your Parade
    HDOJ 1083 Courses
    HDOJ 2063 过山车
    POJ 1469 COURSES
    UESTC 1817 Complete Building the Houses
    POJ 3464 ACM Computer Factory
    POJ 1459 Power Network
    HDOJ 1532 Drainage Ditches
    HDU 1017 A Mathematical Curiosity
  • 原文地址:https://www.cnblogs.com/ansiboy/p/3130240.html
Copyright © 2011-2022 走看看