惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

大猫的无限游戏
大猫的无限游戏
Webroot Blog
Webroot Blog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
T
Threat Research - Cisco Blogs
V2EX - 技术
V2EX - 技术
L
LINUX DO - 热门话题
Google DeepMind News
Google DeepMind News
Recorded Future
Recorded Future
S
Schneier on Security
I
InfoQ
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
The GitHub Blog
The GitHub Blog
S
Security @ Cisco Blogs
O
OpenAI News
W
WeLiveSecurity
Vercel News
Vercel News
阮一峰的网络日志
阮一峰的网络日志
Simon Willison's Weblog
Simon Willison's Weblog
人人都是产品经理
人人都是产品经理
Cloudbric
Cloudbric
The Last Watchdog
The Last Watchdog
The Hacker News
The Hacker News
Google Online Security Blog
Google Online Security Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
GbyAI
GbyAI
NISL@THU
NISL@THU
T
Tailwind CSS Blog
V
Visual Studio Blog
PCI Perspectives
PCI Perspectives
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
Jina AI
Jina AI
D
DataBreaches.Net
B
Blog RSS Feed
N
News and Events Feed by Topic
N
News and Events Feed by Topic
H
Heimdal Security Blog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
腾讯CDC
Latest news
Latest news
V
Vulnerabilities – Threatpost
Hacker News: Ask HN
Hacker News: Ask HN
WordPress大学
WordPress大学
V
V2EX
aimingoo的专栏
aimingoo的专栏
博客园 - 司徒正美
Apple Machine Learning Research
Apple Machine Learning Research
D
Darknet – Hacking Tools, Hacker News & Cyber Security
The Register - Security
The Register - Security
Help Net Security
Help Net Security

博客园 - 海南K.K

海南海口滨海大道洛杉矶城一线海景公寓出售(09年建成,面积88~126平米共六套,一次性团购可打折) 出售海南海口南渡江畔私家别墅(楼高4层,占地388平米,建筑面积570平米) 代友发布:海南海口海景豪宅转让(16层5室3厅3卫2阳台288.0m²) 百度谈人生?别再往自己脸上贴金了! douban api,你到底想做什么? Javascript: 从最受误解的语言,到最流行的语言 LINQ那些事(9)-解析Table<T>.Attach引发的异常和解决方法 你可能不熟悉的CSS 三言二拍:海南的软件业 海口房事:海口纵贯线(-海南购房-海口购房-海南二手房-海口二手房-海南国际旅游岛-养老-过冬-) 海口房事:看京城人争当海南岛民 (-海南购房-海口购房-海南二手房-海口二手房-过冬-养老-国际旅游岛-) 海南海口生态公园公寓出售-海南购房-海口购房-海南二手房-海口二手房-过冬-养老-国际旅游岛- Code4Fun: 通过XML模板系统实现对象的灵活序列化 LINQ那些事儿(5)- 动态查询 LINQ那些事儿(4)- Query Expression和Query Operator LINQ那些事儿(3)- 事务和并发冲突处理 LINQ那些事儿(1)- 定义从关系数据库到entity class的映射 LINQ那些事 LINQ那些事儿(6)-对象生命周期管理
LINQ那些事儿(2)- 简单对象的CRUD操作和Association的级联操作
海南K.K · 2009-12-31 · via 博客园 - 海南K.K

从(1)我们看到,当生成entity class定义时,entity class或xml mapping文件中都已经完整的包含了entity和关系数据库的映射信息了,LINQ2SQL会根据这些信息来把CRUD操作转化为SQL提交给数据库,并且把数据库的返回DataTable封装成我们想要的对象。

所谓简单对象,就是数据表定义中没有Foreign-key的entity class,在操作这类对象时不会涉及级联的操作。

简单对象的CRUD操作,可参考MSDN:http://msdn.microsoft.com/zh-cn/library/bb399349.aspx

有一点很方便,在插入数据时,LINQ2SQL不但生成了Insert的SQL语句,而且还生成语句把ColumnAttribute标记IsDbGenerated=true的数据列取回。这点当我们用数据库生成的uniqueidentifier列或自增id做主键时尤其方便。

下面让我们来一起讨论级联操作以及相关问题,为了方便示例我定义了两张数据表Publishers和Books:

image

其中PublisherID和BookID都是RowGuid,而且默认值为newid(),以下代码都是基于SqlMetal生成的xml mapping和entity class。

添加

下面代码示例了在添加Publisher记录时,同时添加两个关联Book记录

var context = GenerateContext();
Publisher publisher = new Publisher { Name = "Microsoft" };
publisher.Books.Add(new Book { Title = "Expert F#" });
publisher.Books.Add(new Book { Title = "Beautiful code" });

context.Publishers.InsertOnSubmit(publisher);
context.SubmitChanges();

提交成功,关联对象的添加就好像是集合操作。但是,好像缺了点什么?我们好像没有给Book.PublisherID赋值,作为外键没有赋值为什么会没抛出异常呢?这都是生成代码的功劳,我们看看Publisher.Books属性的定义

private EntitySet<Book> _Books;

public Publisher()
{
    this._Books = new EntitySet<Book>(
    new Action<Book>(this.attach_Books), new Action<Book>(this.detach_Books));
    OnCreated();
}

当向Books集合中添加元素时,会调用attach_Books让Book.Publisher指向Publisher对象

private void attach_Books(Book entity)
{
    this.SendPropertyChanging();
    entity.Publisher = this;
}

而Book.Publisher的赋值又触发事件使Book.PublisherId=Book.Publisher.PublisherID

public Publisher Publisher
	{
		get { … }
		set
		{
			Publisher previousValue = this._Publisher.Entity;
			if (((previousValue != value) 
						|| (this._Publisher.HasLoadedOrAssignedValue == false)))
			{
				this.SendPropertyChanging();
				if ((previousValue != null))
				{
					this._Publisher.Entity = null;
					previousValue.Books.Remove(this);
				}
				this._Publisher.Entity = value;
				if ((value != null))
				{
					value.Books.Add(this);
					this._PublisherID = value.PublisherID;
				}
				else
				{
					this._PublisherID = default(System.Guid);
				}
				this.SendPropertyChanged("Publisher");
			}
		}
	}

更新

下面代码示例了在更新Publisher记录时,同时更新相关的Book记录

Publisher publisher = context.Publishers.Where(
p => p.PublisherID == new Guid("ae825c5f-465d-4eb5-a2bb-cc1aeb5edb7d")).Single();
publisher.Name = "Updated Publisher";
var book = publisher.Books.First();
book.Title = "Updated book";

context.SubmitChanges();

这样的操作我们称之为什么?级联更新?事实上LINQ2SQL的实现里不存在所谓的“级联更新“。在生命周期内,每一个DataContext对象都维护着每一个查询获得的对象的引用,并且跟踪对象的修改,所有发生了修改的对象,在调用DataContext.SubmitChanges的时候,都会被保存到数据库,这方面的内容在”LINQ那些事(6)“里会详细讨论。所以,在这里不是级联更新,而是Publisher和Book对象都发生了更改,所以在调用SubmitChanges都被保存了。

涉及Association的更新,有时还会引发异常,我们来看下面这段代码:

var context = GenerateContext();
Publisher publisher = context.Publishers.Where(
p => p.PublisherID == new Guid("ae825c5f-465d-4eb5-a2bb-cc1aeb5edb7d")).Single();
var book = publisher.Books.First();
publisher.Books.Remove(book);

context.SubmitChanges();

这段代码的意图是删除Publisher对象相关的某Book对象,看起来是很漂亮的集合操作,但是运行时抛出异常:

“An attempt was made to remove a relationship between a Publisher and a Book. However, one of the relationship's foreign keys (Book.PublisherID) cannot be set to null.

还记得我们在调用Publisher.Books.Add时候,EntitySet的会调用attach_Books来修改Book.PublisherID吗?在调用Publisher.Books.Remove的时候,EntitySet会调用detach_Books来让Book.PublisherID = default(Guid)

而对于LINQ2SQL,只跟踪到了Book.PublisherID属性发生了更改,所以在调用SubmitChanges会尝试更新Book记录而产生上述外键异常。解决这个问题的方法是:在Book.PublisherID的ColumnAttribute定义中,把DeleteOnNull设为true。这样当Context发现Book.PublisherID为空(default(Guid)相当于Guid.Empty)时,不是执行Update而是Delete操作。

删除

根据msdn,目前版本的LINQ是不支持级联删除的,需要级联删除只能依赖数据库的级联删除,否则也可以考虑下面的方式:

context.Books.DeleteAllOnSubmit(publisher.Books.AsEnumerable());
context.Publishers.DeleteOnSubmit(publisher);
context.SubmitChanges();

你不需要担心事务的问题,context.SubmitChanges在执行时会自动创建本地数据库事务,来保证操作的完整。关于事务的问题,我们在“LINQ那些事(3)”中会有详细讨论。

查询

先看看下面这段代码

var context = GenerateContext();
context.Log = Console.Out;

// 查询publisher对象
Console.WriteLine("Querying publisher");
Publisher publisher = context.Publishers.Where(
p => p.PublisherID == new Guid("ae825c5f-465d-4eb5-a2bb-cc1aeb5edb7d")).Single();

Console.WriteLine("Querying books")
// 当调用publisher.Books.GetEnumerator()时,执行book对象的查询
foreach (Book book in publisher.Books)
{
Console.WriteLine(book.Title);
}

在默认情况下,DataContext并不会加载Assocation对象(EntitySet<T>或EntityRef<T>),当Assocation对象需要被访问时才会执行数据库查询,这就是所谓的lazy-loading。LINQ2SQL的Layz-loading的实现与IEnumerable<T>的deferred query execution是一样的,有兴趣可以看看EntitySet<T>.GetEnumerator或EntityRef<T>.Entity.Getter代码。

Lazy-loading的好处是避免了不必要的查询,但是在某些场合确定Assocation对象都应该加载时,我们可以设置DataContext. LoadOption来指定Assocation对象的加载:

DataLoadOptions option = new DataLoadOptions();
option.LoadWith<Publisher>(p => p.Books);
context.LoadOptions = option;

总结:本节讨论了如何使用LINQ2SQL来进行简单对象和涉及Association的对象的CRUD操作。示例代码段在只涉及一个DataContext对象或在单线程的情况下都可以正确运行,但是当涉及多个DataContext或并发访问的情况的下会怎么样呢?这是我们接下来要讨论的。

链接

LINQ那些事(总)

1、 LINQ那些事儿(1)- 定义从关系数据库到Entity Class的映射

2、 LINQ那些事儿(2)- 简单对象的CRUD操作和Association的级联操作

3、 LINQ那些事儿(3)- 事务和并发冲突处理

4、 LINQ那些事儿(4)- Query Expression和Query Operator

5、 LINQ那些事儿(5)- 动态查询

6、 LINQ那些事儿(6)- DataContext的对象生命周期管理

7、 LINQ那些事儿(7)- 通过自定义IEnumerable<T>来扩展LINQ

8、LINQ那些事儿(8)- 通过自定义IQueryable<T>和IQueryableProvider来扩展LINQ