Pro LINQ 之四:LINQ to SQL

写在前面

LINQ to SQL是一个相对复杂的专题。尽管许多内容仍遵循了LINQ to Objects的一般规范,但是LINQ to SQL与Entity的捆绑,使这一部分的内容更加庞杂。

到整理笔记的时候,我已经得到了这本书的最新版本《Pro LINQ in C# 2010》。在新版中,作者仍旧保持了本书的章节结构,根据LINQ的演进修正了一些知识点,并补充和更新了包括LINQ to Entities与Parallel LINQ在内的一些内容。

新版图书补充和更新的内容,待旧版的笔记整理完毕再行奉上。


P381  LINQ to SQL 初步

随着时间的发展和技术的创新,支持LINQ to SQL的数据库平台,已经从SQL Server及其Express版本,扩展到Oracle、MySQL、SQLite、DB2等主流数据库系统,从而使LINQ to SQL这个实体级别的ORM工具在越来越大的范围内得到应用和推广。

LINQ to SQL的组成,主要包括以下几个方面:DataContext、Entity类、Entity类之间的关联、并发冲突检测与处理。

其中,Entity作为数据库表向应用实体的映射,是LINQ to SQL的最核心部分。

其次,是关联Associations中涉及的EntityRef<>与EntitySet<>这两个Entity的泛型支持,它是实现表间关系和LINQ表间查询的关键。

再者,是我们要关心的DataContext类,正如其名,这是为LINQ提供一个上下文。简单地,在建立与数据库的映射后,我们可以把它理解成整个数据库或者DataConnection,Entity则对应数据库中的每一张表。DataContext有个重要属性Log,在对其赋值进行重定位后,将可以把LINQ to SQL产生的SQL查询语句导出来。就象下面这样输出到一个文件里:

NorthwindDataContext db = new NorthwindDataContext();
StreamWriter outer = new StreamWriter(@".\test.txt");
outer.AutoFlush = true;
db.Log = outer;

// to do something.
    
outer.Close();

书中在讲到生成Entity类时,使用的还是SQLMetal等命令行工具。在新版中已经加入了Visual Studio内建生成工具相关的内容。

最后,要特别注意的是,LINQ to SQL中使用的仍是C#的语法,因此SQL中的相等比较"="在LINQ中应写作"=="。还要尽量避免在Entity类上使用C#或者.NET提供的函数,因为多数时候我们获得只是一个引用。

注:在Visual Studio 2010中,有两种项目:ADO.NET实体数据模型,LINQ to SQL类。前者会在项目中添加一个*.edmx文件,直接映射整个数据库,包括表、存储过程、约束和触发器等,并自动建立表间关系(被称之为导航属性:发件人=>收件人)。后者在项目中添加一个*.dbml文件,并打开一个设计器供开发人员手动从数据库资源中拖放出表。二者在Entity的实现上有一些非常细微的差别。在学习LINQ to SQL的过程中,我选择了后者,因为它更轻量一些。而前者,是目前最热的ADO.NET Entity Framework,相当于一个LINQ to SQL的升级版本,已经有点面向对象数据库的味道了。Entity SQL与LINQ to SQL已经有一些不能完全兼容的操作了:http://msdn.microsoft.com/zh-cn/library/bb738550.aspx

 

P457  Entity类的内部实现

这部分的内容,作者原本是放在第15章:LINQ to SQL Entities的。但我在阅读的过程中,始终被Entity类吸引。它完全不同于DataSet,它是轻量的;它不是Cache,由LINQ提供迟延查询;它比类型化的DataSet提供更完整的类型匹配和检查。所以,我单独地选出了一个Entity类,仔细地查看地了它的内部构成。这也会之后理解LINQ to SQL的各种操作打下了一个基础。所以,这很重要!

这是Customers表对应的Entity类

[global::System.Data.Linq.Mapping.TableAttribute(Name = "dbo.Customers")]
public partial class Customers : INotifyPropertyChanging, INotifyPropertyChanged
{
    #region 基本的数据成员
    // 属性改变的事件参数
    private static PropertyChangingEventArgs emptyChangingEventArgs = new PropertyChangingEventArgs(String.Empty);
    // 列
    private string _CustomerID;
    // 子表引用
    private EntitySet<Orders> _Orders;
    #endregion

    #region 可扩展性方法定义
    // 固定属于Entity类的扩展方法
    partial void OnLoaded();
    partial void OnValidate(System.Data.Linq.ChangeAction action);
    partial void OnCreated();
    // 对应每个列改变的一对事件
    partial void OnCustomerIDChanging(string value);
    partial void OnCustomerIDChanged();    
    #endregion

    #region 构造子
    public Customers()
    {
        // 为每个子表制作一对attach/detach
        this._CustomerCustomerDemo = new EntitySet<CustomerCustomerDemo>(new Action<CustomerCustomerDemo>(this.attach_CustomerCustomerDemo), new Action<CustomerCustomerDemo>(this.detach_CustomerCustomerDemo));
        this._Orders = new EntitySet<Orders>(new Action<Orders>(this.attach_Orders), new Action<Orders>(this.detach_Orders));
        OnCreated();
    }
    #endregion

    #region 对应于列的Property
    [global::System.Data.Linq.Mapping.ColumnAttribute(Storage = "_CustomerID", DbType = "NChar(5) NOT NULL", CanBeNull = false, IsPrimaryKey = true)]
    public string CustomerID
    {
        get
        {
            return this._CustomerID;
        }
        set
        {
            if ((this._CustomerID != value))
            {
                // 留心触发事件的成对匹配
                this.OnCustomerIDChanging(value);
                this.SendPropertyChanging();

                this._CustomerID = value;
                
                this.SendPropertyChanged("CustomerID");
                this.OnCustomerIDChanged();
            }
        }
    }
    #endregion

    #region 对应于子表引用的Property
    [global::System.Data.Linq.Mapping.AssociationAttribute(Name = "Customers_Orders", Storage = "_Orders", ThisKey = "CustomerID", OtherKey = "CustomerID")]
    public EntitySet<Orders> Orders
    {
        get
        {
            return this._Orders;
        }
        set
        {
            this._Orders.Assign(value);
        }
    }
    #endregion

    #region 属于Entity类的属性改变事件与委托
    public event PropertyChangingEventHandler PropertyChanging;
    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void SendPropertyChanging()
    {
        if ((this.PropertyChanging != null))
        {
            this.PropertyChanging(this, emptyChangingEventArgs);
        }
    }

    protected virtual void SendPropertyChanged(String propertyName)
    {
        if ((this.PropertyChanged != null))
        {
            this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }    

    // 为子表制作attach/detach
    private void attach_Orders(Orders entity)
    {
        this.SendPropertyChanging();
        entity.Customers = this;
    }

    private void detach_Orders(Orders entity)
    {
        this.SendPropertyChanging();
        entity.Customers = null;
    }
}

其中的PropertyChanging与PropertyChanged,是Entity暴露出的2个事件。

 

以下是子表Orders生成的Entity类中对应Customers类的重要部分

[global::System.Data.Linq.Mapping.AssociationAttribute(Name="Customers_Orders", Storage="_Customers", ThisKey="CustomerID", OtherKey="CustomerID", IsForeignKey=true)]
public Customers Customers
{
    get
    {
        return this._Customers.Entity;
    }
    set
    {
        // 因为this._Customers是一个EntityRef<Customers>的引用,所以要使用属性Entity取出父对象。
        Customers previousValue = this._Customers.Entity;
        // 这个判断非常重要,避免陷入set操作的递归而不能自拔
        // 因为在父类Customers的detach_Orders()中,有一个赋值Orders对象的语句:entity.Cusomers = null;
        if (((previousValue != value) 
                    || (this._Customers.HasLoadedOrAssignedValue == false)))
        {
            this.SendPropertyChanging();

            #region 同时修改父子的相互引用,注意都要通过属性Entity,把引用Reference变为实体
            if ((previousValue != null))
            {
                this._Customers.Entity = null;
                // Remove的内部,就是调用父类Customers中的子表detach()
                previousValue.Orders.Remove(this);
            }
            this._Customers.Entity = value;            
            if ((value != null))
            {
                value.Orders.Add(this);
                this._CustomerID = value.CustomerID;
            }
            else
            {
                this._CustomerID = default(string);
            }
            #endregion

            this.SendPropertyChanged("Customers");
        }
    }
}
P416  LINQ to SQL的CUD操作

CUD操作,一直是数据库应用的核心。在LINQ to SQL的环境下,CUD操作变成了在DataContext支撑下,各种Entity类的成员方法调用。

父表行的插入customer = new Customers;
Customers.InsertOnSubmit(customer);
对应子表行的添加order = new Orders;
Customers.Orders.Add(order);
行中非键列的更新直接修改列属性的值,然后等最后SubmitChanges();
子表行的删除Orders.DeleteAllOnSubmit(customer.Orders);
父表行的删除Customers.DeleteOnSubmit(customer);
数据的更新context.SubmitChanges();

添加子表的行,可以在父表行的构造中利用对象赋值方式进行构造。

Customers cust = new Customers
{
    CustomerID = "LAWN",
    // ... ...
    Orders = { new Orders
    {
        CustomerID = "LAWN",
        // ... ...
    }}
}

而通过观察Context.Log的输出,可以发现Delete操作要求所有列的匹配,而不只是主键。若干个列的AND条件组成老长的查询串,很让人咋舌。与数据库的约束一致的是,子表中涉及父表的行要先于父表中的行被删除。

P420  LINQ to SQL的查询

LINQ to SQL的查询,区别于LINQ to Objects的最重要一点,是从IEnumerable<T>向IQueryable<T>的转变。所以这里只记录自己在学习过程中一些特别的知识点。

要深刻理解的一点是,LINQ to SQL仍然是迟延执行,Entity不象DataSet那样是本地的Cache,所以不用担心加载子表等多余数据而导致Entity实例对象的膨胀。这点,集中体现在EntityRef<T>与EntitySet<T>的关系(已在Entity类的实现中展示),以及DataContext的DataLoadOptions属性上。DataContext的策略决定了灵活地生成LINQ to SQL对应的查询语句,避免了子表查询的无谓生成。

(注:.NET 4.0大大丰富了LINQ。为提供方便LINQ查询语句的复用,出现了经过编译的LINQ查询。还出现了一个新的方法Include(),可以取代下面的LoadWith()等。)

通过修改DataLoadOptions,我们可以选择性地使子表数据被即时加载。DataLoadOptions有2个重要的方法:

LoadWith<父表>(p => p.子表),以及AssociateWith<父表>(p => 子表LINQ查询的结果)。前者是加载整个子表,后者是只加载子表中符合特定条件的行。

DataLoadOptions dlo = new DataLoadOptions();
dlo.LoadWith<Customers>(c => c.Orders);
db.LoadOptions = dlo;

dlo.AssociateWith<Customers>(c => from o in c.Orders
                                    where o.OrderID < 10700
                                    orderby o.OrderDate descending
                                    select o);

顺理成章地,我们会想到同时建立父表对多个子表的LoadWith(),并由此建立一个加载链。于是,避免循环链、交叉链成为了必然。另一方面,当没有遗传加载的关系时,LoadWith()仍表现为即时加载。有遗传加载关系时,则在遗传树下层Entity被引用时,才予加载。如下例:

// Orders与CustomerCustomerDemo是兄弟,于是即便没有被引用也被即时加载
dlo.LoadWith<Customers>(c => c.Orders);
dlo.LoadWith<Customers>(c => c.CustomerCustomerDemo);

// Customers -> Orders -> Order_Details 决定了后两者只在被引用时才加载。
dlo.LoadWith<Customers>(c => c.Orders);
dlo.LoadWith<Orders>(o => o.Order_Details);

 

通过查询生成Entity的嵌套类

书中提到,利用LINQ to SQL查询结果new的匿名类型,被称为平面化查询结果,这种结果将不受DataContext的管理,无法自动实现数据的更新。

var result = from s in db.Suppliers
                join c in db.Customers
                on s.City equals c.City
                into temp
                from t in temp.DefaultIfEmpty()
                select new
                {
                    SupplierName = s.CompanyName,
                    Customers = db.Customers.Where(c => c.City == t.City)
                };

 

P442  SQL语法中in的实现(反着的)

string[] cities = { "London", "Madrid" };
IQueryable<Customer> custs = db.Customers.Where(c => cities.Contains(c.City));

 

P443  更新父子表间的引用

1. 从子表的角度出发,先找到要修改父表引用的子表行order,再取出准备指向的父表行employee,然后直接修改子表行order.Employee的指向:order.Employee = employee;

2. 从父表的角度出发,先找到要修改引用的子表行order,再取出order当前所属的父表行employeeOld,然后准备好order要指向的的新的父表行employeeNew。随后,从employeeOld中删除对子表行order的引用:employeedOld.Orders.Remove(order); 再将order添加到新父表行中:employeeNew.Orders.Add(order);

3. 要删除特定的父表行customer时,要按倒序删除所有遗传树上的子孙表行。

db.Order_Details.DeleteAllOnSubmit(customer.Orders.SelectMany(o => o.Order_Details));
db.Orders.DeleteAllOnSubmit(customer.Orders);
db.Customers.DeleteOnSubmit(customer);

4. 要删除特定子表行与父表行之间的关系,直接将彼此的引用置为null即可。

 

P449 重载CUD操作

使用统一的partial方法定义格式,可以重载Entity的CUD操作(类似动态联编:virtual-override):

partial void [Insert/Update/Delete][Entity类名] (Entity类名 instance);

其中调用默认方法的语句为:

this.ExecuteDynamic[Insert/Update/Delete](instance);

结合书中P490:利用partial方法扩展Entity类,可以把partial方法看作一根“穿刺针”,实现对Entity内部的窥探。

 

P468 EntityRef<T>与EntitySet<T>对关联双方的影响

简言之,在get操作中,对EntityRef<T>的引用,在返回值时要采取return order.Customers.Entity这样的方式,利用Entity属性返回实体而不是引用;对EntitySet<T>,则可以直接采取return customer.Orders的方式返回引用。

而在set操作中,EntityRef<T>的赋值同样要通过Entity属性实现,并且要特别注意不能陷入递归陷阱(参见此前Entity类内部细节);而对EntitySet<T>,则建议通过customer.Orders.Assign(order)这样的方式进行添加。

在Entity外对列属性的使用时,对EntityRef<T>形成的1:1关系,直接赋值即可;对EntitySet<T>形成的1:M关系,使用集合类操作Add()与Remove()。

 

P471  Entity类中的特性Attribute

这部分内容很长,但多数为IDE自动为我们生成。吸引我的是第482页的继承映射,或称为继承支持。对此MSDN中的表述如下:

LINQ to SQL支持单表映射。换言之,整个继承层次结构存储在单个数据库表中。该表包含整个层次结构中的所有可能数据列的平展联合(联合:两表union,结构包括两表中的所有行。)。其中每行中,不适合于该行所表示的实例类型,则其列为null。

说得很绕,其实平时我们利用数据库单表实现的“省-市-县-乡”的这种地域管辖的层次结构,便是上述机制的直接体现。主要的实现步骤包括以下6步:

1. 根类(对应整个表)需有TableAttribute特性修饰;

2. 为层次结构中的每个类添加继承InheritanceMapping特性修饰;

3. 定义鉴别器识别码(discriminator code),它将最终被显示在数据库表中被修饰为IsDiscriminator的列上,以指示该行数据所属的类或者子类;

4. 为每个类指定对应的Type属性,指示键值表示的类或子类;

5. 为其中一个InheritanceMapping修饰指定IsDefault = true属性,以在数据库表中的鉴别器值在继承映射中未与任何鉴别器识别码匹配时,指定的默认的回退映射值;

6. 为每个列的Column特性添加IsDiscriminator = true属性,表示该列是保存code值的列。

示例的类结构如下(MSDN中有一个Vehicle-Car/Trunk的示例):

[Table]
public class Shape
{
    [Column(IsPrimaryKey = true, IsDbGenerated = true,    DbType = "Int NOT NULL IDENTITY")]
    public int Id;

    [Column(IsDiscriminator = true, DbType = "NVarChar(2)")]
    public string ShapeCode;
    
    [Column(DbType = "Int")]
    public int StartingX;
    
    [Column(DbType = "Int")]
    public int StartingY;
}

public class Square : Shape
{
    [Column(DBType = "Int")]
    public int Width;
}

public class Rectangle : Square
{
    [Column(DBType = "Int")]
    public int Length;
}

并由此形成如下的结构图:

Shape

 

最终生成的数据库表,大致如下图:

InheritanceMapping

 

查询所有的矩形的LINQ语句示例如下:

IQueryable<Shape> rectangles = from r in db.Shapes
                               where r is Rectangle
                               select r;
 
P485 LINQ to SQL零碎知识点

注入Entity类 vs 注入非Entity类

前者可以得到DataContext的支持,数据的更新将自动实现。后者要自己实现数据更新和关联引用的逻辑。要说明的是,如果是二者的混用,比如非Entity内嵌一个Entity类,则可享受DataContext的服务。

 

对象初始化值方式 vs 参数化构造子

即采用“成员 = 初始值”这样的对象初始化值方式,与带参数的构造子的比较。最终的建议是采用前者,以方便DataContext跟踪Entity的列属性。

var contacts = from c in db.Customers
                where c.City == "Buenos Aires"
                select new { Name = c.ContactName, Phone = c.Phone } into co
                orderby co.Name
                select co;

var contacts = from c in db.Customers
                where c.City == "Buenos Aires"
                select new CustomerContact(c.ContactName, c.Phone) into co
                orderby co.Name
                select co;

对书中第P489提到的使用参数化构造子返回结果后无法排序的问题,个人思考是加入一个AsEnumerable()再OrderBy()。因为整个查询返回的CustomerContact序列,是IQueryable<T>序列,将其转换为IEnumerable<T>后再排序即可。

P492 System.Data.Linq重要的API列表

这部分内容太长、也太杂,需要的时候再查字典吧。


P499 DataContext

映射后的LINQ类,都由DataContext的派生类管理。DataContext有许多的重载形式。在Visual Studio 2010里,数据库的连接串保存在App.config文件中,由一个采用Singleton模式的类Settings管理。该连接串,将被DataContext的构造子自动取用。

namespace LinqtoSql.Properties
{
    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "10.0.0.0")]
    internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
    {
        private static Settings defaultInstance = ((Settings) (global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));

        public static Settings Default
        {
            get
            {
                return defaultInstance;
            }
        }

        [global::System.Configuration.ApplicationScopedSettingAttribute()]
        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
        [global::System.Configuration.SpecialSettingAttribute(global::System.Configuration.SpecialSetting.ConnectionString)]
        [global::System.Configuration.DefaultSettingValueAttribute("Data Source=.;Initial Catalog=Northwind;Integrated Security=True")]
        public string NorthwindConnectionString
        {
            get
            {
                return ((string) (this["NorthwindConnectionString"]));
            }
        }
    }
}
P503 DataContext的四大功用

对象跟踪:在整个数据库内,唯一性地标识一个对象或者实体(面向对象数据库中的概念)。如同OOP中使用“对象引用”唯一地指示一个对象。

Entity的Cache:保证即便有多个DataContext对同一个Entity对象做出改动时,仍能保持数据的一致性,或者提供冲突的处理办法。但是,当有不受DataContext管控的数据库更新时,比如直接使用ADO.NET对象更新数据库,数据库将与DataContext管理的Entity Cache失配。(作者在讲解并发冲突检测与处理时,便采取此种方式,人为地造成冲突。)所以,作者有一句话:the data retrieved by a query is considered to be stale the moment you retrieve it, and that the data cached by the DataContext is not meant to be cached for long periods of time. 我姑且译作:DataContext的Cache并非是为长久使用而设计的,查询到的数据总是获得那一刻最陈旧的。因此,通常建议在执行完DataContext.SubmitChanges()之后就释放掉这个DataContext,而不要让DataContext对象活得更长,这通常是在一个方法域内完成。

跟踪数据的改变:DataContext将始终保存Entity每一行的原始值(Original Value),在SubmitChanges()后将行的状态复位。而每一行,在被执行InsertOnSubmit()之后,被DataContext接管。

处理数据的更新:DataContext.SubmitChanges()执行时,DataContext会将所有的操作封装成一个事务(Transaction)提交给数据库执行。如果发生冲突,则自动Rollback。其中要注意的一点是:DataContext的Rollback操作,只是针对数据库的,即保证数据库的更新是完整和一致的。而如果其中包括有某个Entity对象的InsertOnSubmit()操作,则该Entity对象的状态不会回滚,仍需要自己处理被添加的行。或者换种方式:SubmitChanges()回滚的是事务,而不会对内存中的Entity对象做出更改,需要我们自己手动回滚对Entity的修改。如果想直接丢弃对Entity的修改,则可以构造一个新的DataContext对象,重新开始。具体内容参见该书P521的说明。

P509  DataContext的方法列表

注:内容同样很长、很杂,多数时候要当字典使用。以下只记录常用的部分。

 

人为制造并发冲突的方法

在用DataContext更新数据的同时,利用ADO.NET对象直接访问数据库,造成3个值的冲突:数据库当前值,DataContext Cache值,Entity当前值。在并发冲突处理部分,还将详细说明3个值冲突后的处理办法。

 

P524  多条SQL语句的封装

两条update的SQL语句,用分号分隔后用一个字符串包装,然后传递给ADO.NET的Command对象执行。这也能行?!

string cmd = @"update Customers set ContactTitle = 'Director of Marketing'
                where CustomerID = 'LAZYK';
                update Customers set ContactTitle = 'Director of Sales'
                where CustomerID = 'LONEP'";

 

P528  CreateDatabase()

类似文件另存操作,不过Entity类只有Attribute与XML的映射,因此无法复原存储过程、触发器等信息。

 

P532 ExecuteQuery()

给该方法传递一个SQL语句对应的字符串,即可象使用ADO.NET的Command对象一样执行查询命令,返回的结果是IEnumerable<T>类型的,而不是IQueryable<T>的。

有趣的地方,是它会根据参数的类型,自主决定是否要加上''这样的修饰符。比如下面这2句:

IEnumerable<Customer> custs = db.ExecuteQuery<Customer>(
                            @"select CustomerID, CompanyName, ContactName, ContactTitle
                            from Customers where Region = {0}", "WA");

IEnumerable<Customer> custs = db.ExecuteQuery<Customer>(
                            @"select CustomerID, CompanyName, ContactName, ContactTitle
                            from Customers where Region = 'WA'");

前者应该是采用了类似Parameter的机制,防止SQL注入。

 

P534 Translate()

可以将ADO.NET中DataReader对象作为参数,将其返回的查询结果直接转译为IEnumerable<Entity>序列。

 

P536 ExecuteCommand()

类同前述ExecuteQuery(),返回值是受影响的行数。

 

P544 GetCommand()

将一个IQueryable<T>的LINQ查询或者Entity序列作为参数,返回对应的SQL查询语句。可用作修改某个LINQ查询的SQL查询设置,比如超时值等。

 

P545 GetChangeSet()

原型为 ChangeSet GetChangeSet(); 重要的是这个ChangeSet类,其中包括3个集合属性Inserts、Updates和Deletes,分别对应将提交给数据库的行。


P557 并发冲突的检测与处理

这是数据库应用必须要正视的问题。对同一资源的竞争性使用,将导致并发冲突的产生。

LINQ to SQL采取了开放式的并发控制先调查其他事务是否已经更改了行中的值,再允许提交更改的技术。相比之下,保守式的并发控制用锁定记录来避免并发冲突。之所以称为“开放式”,是因为它将一个事务干扰另一个事务视作不可能(援引自MSDN)。

在LINQ to SQL模型中,同时满足以下条件时,将发生“开放式的并发冲突”:客户端尝试向数据库提交更改;数据库中的一个或多个更新检查值自客户端上次读取它们以来已经得到更新。即前述的3个值之间的不一致,将引发冲突:Entity读取值(Entity Original),Entity当前值(Entity Current),数据库的当前值(Database Current)。

解决的过程包括:查明对象的哪些成员发生冲突,然后选择适当的处理方式。

冲突的检测

并发冲突的解决,是通过重新查询数据库,刷新出现冲突的项,再设法协调差异。刷新对象时,LINQ中DataContext的更改跟踪器会保留以下数据:最初从数据库获取并用于更新检查的值(即Entity的Original值),通过后续查询获得的新的数据库值。

LINQ to SQL通过在Entity中某列的特性中标记IsVersionColumn = true来标记某列是否作为冲突检测的列。如果整个Entity类都没有列被标记,则对所有列使用默认的UpdateCheck.Always来检测冲突。

Always该列始终出现在where子句的判断条件中
WhenChanged该列的值被改变时才出现在where子句的判断条件中
Never无论何种情况都不会出现在where子句的判断条件中,值将被抛弃
冲突的解决

LINQ to SQL提供了并发冲突的3种解决方案。通过在try-catch结构的catch块中,向对应数据库梯次的DataContext.ChangeConflicts.ResoveAll()、对应数据库表或Entity梯次的ObjectChangeConflict.Resolve()或者对应列梯次的MemberChangeConflict.Resolve()提供一个枚举参数RefreshMode,实现不同的策略。

参考MSDN中的说明,将3种方案整理汇总如下:

假设冲突后造成如下表样:

操作ManagerAssistantDepartment
原始数据库被2个User查询时的状态AlfredsMariaSales
User1准备提交更改Alfred Marketing
User2已经提交的更改 MaryService

 

三种方案及其结果(均是从Entity的视角出发):

方案保留数据库值覆盖数据库值合并数据库值
RefreshModeOverwriteCurrentValuesKeepCurrentValuesKeepChanges
处理结果保留在数据库找到的值覆盖数据库的当前值将数据库值与当前客户端成员值合并。数据库值仅在当前变更集也修改该值时才会被覆盖,否则保留数据库当前值
3值优先顺序
(由高至低)
D_Current
E_Original
E_Current
E_Original
E_Current
D_Current
E_Original
数据库呈现
AlfredsMaryService
原始值User2User2
AlfredMariaMarketing
User1原始值User1
AlfredMaryMarketing
User1User2User1

要注意的是,无论何种方案,DataContext都会先通过从数据库中检索更新后的数据,来刷新客户端的记录,以确保下一次更新的尝试也通过相同的并发检查。

P570 中间层和服务器可采取的并发控制方法

这一部分写得不是很出彩,相比而言,MSDN中的“N层应用程序中的数据检索和CUD操作”在并发冲突的检测和解决上更丰满一些。但作者在此处又重申了DataContext的生命周期问题,仍强调在方法域内使用它。

作者还提出了通过构造新的DataContext,然后使用Entity的Attach()方法挂载Entity对象来处理并发冲突的方法。基本步骤如下:

// 构造一个DataContext对象
NorthwindDataContext db = new NorthwindDataContext();
// 构造一个Entity对象
Customers customer = new Customers();
// 设置Entity对象的主键值
customer.CustomerID = "LAZYK";
// 设置Entity对象中准备更新的列原始值
customer.ContactName = "John Steel";
// 填写Entity对象中其他列的值

// 将Entity对象Attach入新构造的DataContext
db.Customers.Attach(customer);
// 修改需要更新的列的值
customer.ContactName = "Vickey Rattz";
// 提交更新,将Entity对象绑定入表中
db.SubmitChanges();

[LINQ] Pro LINQ 之五:LINQ to Entity

转载于:https://www.cnblogs.com/Abbey/archive/2011/07/17/2107917.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值