代码改变世界

LINQ TO SQL 笔记 — 存储过程、并发与事务

2008-04-17 22:57  Animax!  阅读(1508)  评论(1编辑  收藏  举报

前期设定:

 

先定义有两个数据表:

CREATE TABLE [dbo].[Table_A](

         [AID] [bigint] IDENTITY(1,1) NOT NULL,

         [name] [text] COLLATE Chinese_PRC_CI_AS NULL,

         [name2] [text] COLLATE Chinese_PRC_CI_AS NULL,

         [name3] [text] COLLATE Chinese_PRC_CI_AS NULL,

 CONSTRAINT [PK_Table_A] PRIMARY KEY CLUSTERED

(

         [AID] ASC

)WITH (PAD_INDEX = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY]

) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

CREATE TABLE [dbo].[Table_B](

         [BID] [bigint] IDENTITY(1,1) NOT NULL,

         [AID] [bigint] NULL,

         [text] [text] COLLATE Chinese_PRC_CI_AS NULL,

         [text2] [text] COLLATE Chinese_PRC_CI_AS NULL,

 CONSTRAINT [PK_Table_B] PRIMARY KEY CLUSTERED

(

         [BID] ASC

)WITH (PAD_INDEX = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY]

) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

GO

ALTER TABLE [dbo].[Table_B] WITH CHECK ADD CONSTRAINT [FK_Table_A_Table_B] FOREIGN KEY([AID])

REFERENCES [dbo].[Table_A] ([AID])

GO

ALTER TABLE [dbo].[Table_B] CHECK CONSTRAINT [FK_Table_A_Table_B]

三个存储过程:

ALTER PROCEDURE [dbo].[GetTableA_And_B] 单结果集返回

         @TableAID bigint

AS

BEGIN

         SET NOCOUNT ON;

SELECT                Table_B.BID, Table_B.text, Table_B.text2,

                            Table_A.AID, Table_A.name, Table_A.name3, Table_A.name2

FROM         Table_B INNER JOIN Table_A ON Table_B.AID = Table_A.AID

WHERE               Table_A.AID = @TableAID

        

END

ALTER PROCEDURE [dbo].[GetTableA_ID_by_TableB_ID] -–传入传出值

         @BID bigint,

         @AID bigint output

AS

BEGIN

         SET NOCOUNT ON;

         select @AID = AID from Table_B

         where BID = @BID

        

END

ALTER PROCEDURE [dbo].[GetTables] -–双结果集返回

         @TableA_ID        bigint

AS

BEGIN

 SET NOCOUNT ON;

         SELECT     AID, name, name2, name3

         FROM         Table_A

         where AID = @TableA_ID

        

         SELECT     BID, AID, text, text2

         FROM         Table_B

         where AID = @TableA_ID

END

并且设置工程添加了一个名为MyLINQDataLINQ TO SQL类,在这类中加入了需要的表和存储过程。

LINQ TO SQL调用存储过程:

有返回值的存储过程(GetTableA_ID_by_TableB_ID):

            MyLINQDataDataContext data = new MyLINQDataDataContext();

            long? TableAID = 0;

            data.GetTableA_ID_by_TableB_ID(3, ref TableAID);

            Console.WriteLine(string.Format("TableA.ID = {0}", TableAID));

返回单结果集的存储过程(GetTableA_And_B):

            MyLINQDataDataContext data = new MyLINQDataDataContext();

            var revalue = data.GetTableA_And_B(1);

            foreach (var val in revalue)

            {

                Console.WriteLine(string.Format(

                    "AID:{0}||BID:{1}||name:{2}||name1:{3}||name2:{4}||text:{5}||text2:{6}",

                    val.AID, val.BID, val.name, val.name2, val.name3, val.text, val.text2));

            }

返回多结果集的存储过程(GetTables):

使用返回多结果集的存储过程比较麻烦,在LINQ TO SQL类中只识别到一个(第一个)结果集,所以需要手动修改生成的DataContext类,

原本的GetTables方法:

        [Function(Name = "dbo.GetTables")]

        public ISingleResult<GetTablesResult> GetTables([Parameter(Name = "TableA_ID", DbType = "BigInt")] System.Nullable<long> tableA_ID)

        {

            IExecuteResult result = this.ExecuteMethodCall(this, ((MethodInfo)(MethodInfo.GetCurrentMethod())), tableA_ID);

            return ((ISingleResult<GetTablesResult>)(result.ReturnValue));

        }

修改后的方法:

        [Function(Name = "dbo.GetTables")]

        [ResultType(typeof(Table_A))]         //这里是第一个结果集,返回的是Table_A的内容

        [ResultType(typeof(Table_B))] //这里是第二个结果集

        public IMultipleResults GetTables([Parameter(Name = "TableA_ID", DbType = "BigInt")] System.Nullable<long> tableA_ID)

        {

            IExecuteResult result = this.ExecuteMethodCall(this,((MethodInfo)(MethodInfo.GetCurrentMethod())), tableA_ID);

            return ((IMultipleResults)(result.ReturnValue));

        }

多结果集的调用方式

            MyLINQDataDataContext data = new MyLINQDataDataContext();

            var revalue = data.GetTables(1);

            var TableA = revalue.GetResult<Table_A>();

            var TableB = revalue.GetResult<Table_B>();

            foreach (var TA in TableA)

            {

                Console.WriteLine(string.Format(

                    "AID:{0}||name:{1}||name2:{2}||name3:{3}",

                    TA.AID, TA.name, TA.name2, TA.name3));

            }

            Console.WriteLine();

            foreach (var TB in TableB)

            {

                Console.WriteLine(string.Format(

                    "AID:{0}||BID:{1}||text:{2}||text2:{3}",

                    TB.AID, TB.BID, TB.text, TB.text2

                    ));

            }

LINQ TO SQL更新冲突:

需要 using System.Data.Linq;

更新冲突是指在赋值后和提交修改前数据库中设计修改的值发生了变化:

            MyLINQDataDataContext data = new MyLINQDataDataContext();

            var TableA =

                from Table in data.Table_A

                select Table;

            foreach (var value in TableA)

            {

                value.name = "updateName";

                value.name2 = "updateName2";

                value.name3 = "updaeName3";

            }

                      // 这时数据被改变了….

            data.SubmitChanges(System.Data.Linq.ConflictMode.FailOnFirstConflict);

这里要说明一下System.Data.Linq.ConflictMode这个枚举类型的值:

FailOnFirstConflict : 当发现数据库数据已经改变,对发生了更改的数据不进行更新(未发生更改的数据继续进行更新)。

ContinueOnConflict : 先对数据进行更改,然后提交冲突报告。

在使用ContinueOnConflict模式的数据更新中,可以使用ChangeConflictException 来捕获冲突异常。

当捕获到并发冲突异常之后,可以使用数据库实体(Context)的ContinueOnConflict来获取发生冲突的对象;并可以使用Resolve方法来解决数据冲突的问题。

Resolve方法可以接受3种解决方式(RefreshMode):

KeepCurrentValues使用数据库中的值覆盖当前值。(以现在的更新为准)

KeepChanges:使用从数据库检索的值替换原始值。(原来的更新有效,冲突字段以当前的更新为准)

OverwriteCurrentValues保留已更改的当前值,但将其他值更新为数据库值。(更新以数据库更新为准)

            try

            {

                data.SubmitChanges(System.Data.Linq.ConflictMode.ContinueOnConflict);

            }

            catch (ChangeConflictException) //捕获到冲突

            {

                foreach (ObjectChangeConflict OC in data.ChangeConflicts)

                {

                    OC.Resolve(RefreshMode.OverwriteCurrentValues);

                }

            }

LINQ TO SQL事务:

需要 using System.Data.Common;

            MyLINQDataDataContext data = new MyLINQDataDataContext();

            if (data.Connection != null) //打开连接

            {

                data.Connection.Open();

            }

            // BeginTransaction 可以传入事务的隔离类型。

            DbTransaction tran = data.Connection.BeginTransaction(); // 隔离类型为:Unspecified

            data.Transaction = tran;

            try

            {

                var TableA =

                    from Table in data.Table_A

                    select Table;

                foreach (var value in TableA)

                {

                    value.name = "test";

                    value.name2 = "test";

                    value.name3 = "test";

                }

                data.SubmitChanges(); //更新数据

                tran.Commit(); // 提交事务

            }

            catch

            {

                tran.Rollback();

            }

在事务CommitRollback 之前数据是被锁着的,锁的类型视乎于事务的隔离等级,在默认情况下是(Unspecified)。

隔离等级System.Data.IsolationLevel的枚举值:

Unspecified正在使用与指定隔离级别不同的隔离级别,但是无法确定该级别。

Chaos无法覆盖隔离级别更高的事务中的挂起的更改。

ReadUncommitted可以进行脏读,意思是说,不发布共享锁,也不接受独占锁。

ReadCommitted在正在读取数据时保持共享锁,以避免脏读,但是在事务结束之前可以更改数据,从而导致不可重复的读取或幻像数据。

RepeatableRead在查询中使用的所有数据上放置锁,以防止其他用户更新这些数据。防止不可重复的读取,但是仍可以有幻像行。

Serializable DataSet 上放置范围锁,以防止在事务完成之前由其他用户更新行或向数据集中插入行。

Snapshot通过在一个应用程序正在修改数据时存储另一个应用程序可以读取的相同数据版本来减少阻止。表示您无法从一个事务中看到在其他事务中进行的更改,即便重新查询也是如此。