序言

很多朋友都向我提过,希望我写一下关于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 注入框架无法对其注入。

《深入了解 Linq to SQL》之对象的标识 —— 麦叔叔呕心呖血之作的更多相关文章

  1. 《深入了解 Linq to SQL》之对象的增删改 —— 麦叔叔呕心呖血之作

    你的程序里,是否到处充斥着这种代码: db.Customers.InsertOnSubmit(customer); db.SubmitChange(); 如果某一天,因为 Customers 表的数据 ...

  2. LINQ To SQL在N层应用程序中的CUD操作、批量删除、批量更新

    原文:LINQ To SQL在N层应用程序中的CUD操作.批量删除.批量更新 0. 说明 Linq to Sql,以下简称L2S.    以下文中所指的两层和三层结构,分别如下图所示: 准确的说,这里 ...

  3. LINQ to SQL语句(17)之对象加载

    对象加载 延迟加载 在查询某对象时,实际上你只查询该对象.不会同时自动获取这个对象.这就是延迟加载. 例如,您可能需要查看客户数据和订单数据.你最初不一定需要检索与每个客户有关的所有订单数据.其优点是 ...

  4. LINQ to SQL语句(16)之对象标识

    对象标识 运行库中的对象具有唯一标识.引用同一对象的两个变量实际上是引用此对象的同一实例.你更改一个变量后,可以通过另一个变量看到这些更改. 关系数据库表中的行不具有唯一标识.由于每一行都具有唯一的主 ...

  5. linq to sql中的自动缓存(对象跟踪)

    linq to sql中,对于同一个DataContext上下文环境,根据表主键选择记录时(当然这里所指的“记录”会自动转成“对象”),如果该记录已经被select过,默认情况下会被自动缓存下来,下次 ...

  6. LINQ TO SQL:操作有层次关系的对象

    对于关系型数据与对象数据之间最大的隔阂就是由标识列连接起来的行(关系型数据)与由集合保存的对象(对象数据)之间的冲突. 例如某个Subject对象(也就是数据库中的Subject表),从Subject ...

  7. 年终巨献 史上最全 ——LINQ to SQL语句

    LINQ to SQL语句(1)之Where 适用场景:实现过滤,查询等功能. 说明:与SQL命令中的Where作用相似,都是起到范围限定也就是过滤作用的,而判断条件就是它后面所接的子句.Where操 ...

  8. LINQ to SQL语句(19)之ADO.NET与LINQ to SQL

    它基于由 ADO.NET 提供程序模型提供的服务.因此,我们可以将 LINQ to SQL 代码与现有的 ADO.Net 应用程序混合在一起,将当前 ADO.NET 解决方案迁移到 LINQ to S ...

  9. LINQ to SQL语句(10)之Insert

    1.简单形式 说明:new一个对象,使用InsertOnSubmit方法将其加入到对应的集合中,使用SubmitChanges()提交到数据库. var newCustomer = new Custo ...

随机推荐

  1. Tomcat查看用户名密码

    在非安装版的tomcat中,可以在{解压路径}/conf/tomcat_users.xml 配置文件中找到,也可以自己添加新的用户

  2. What’s the difference between an interface and an abstract class in Java?

    原文 What’s the difference between an interface and an abstract class in Java? It’s best to start answ ...

  3. WifiDog系统

    WifiDog:A captive portal suite What is it composed of ? A: It is composed of 2 components: The clien ...

  4. Linux2.6内核 -- 编码风格(3)

          9.typedef     内核开发者们强烈反对使用 typedef 语句.他们的理由是:     1> typedef 掩盖了数据的真实类型     2> 由于数据类型隐藏起 ...

  5. JUnit单元测试框架的使用

    http://blog.csdn.net/mao520741111/article/details/51462215 原文地址 http://www.open-open.com/lib/view/op ...

  6. [转] Hive 内置函数

    原文见:https://cwiki.apache.org/confluence/display/Hive/LanguageManual+UDF 1.内置运算符1.1关系运算符 运算符 类型 说明 A ...

  7. VMware+Ubuntu8.10+Skyeye+gdb实现u-boot源码调试

    系统平台:WindowsXP 虚拟机: VMware Workstation 6.5.0 Ubuntu8.10 安装程序 ubuntu-8.10-desktop-i386.iso 下载地址:http: ...

  8. root密码忘记后如何修改

    方法一: 1.在DOS窗口下输入net stop mysql5 或 net stop mysql 2.开一个DOS窗口,这个需要切换到mysql的bin目录.一般在bin目录里面创建一个批处理1.ba ...

  9. java框架BeanUtils及路径问题练习

    内省----->一个变态的反射    BeanUtils主要解决 的问题: 把对象的属性数据封装 到对象中.  使从文件中读取的数据往对象中赋值更加简单:   BeanUtils的好处:  1. ...

  10. java与.net比较学习系列(6) 数组

    这一篇文章要总结的内容是数组,数组可以简单地看成是同种数据类型变量的集合,它在我们的开发中经常用到,我们主要从以下几个方面进行总结: 1,数组的创建和初始化 2,数组的访问和遍历 3,数组的总结 数组 ...