Linq to Sql 學習系列之八 繼承與關係

論壇表結構

 

       爲了演示繼承與關係,我們創建一個論壇數據庫,在數據庫中創建三個表:

1、  論壇版塊分類表 dbo.Categories

字段名

字段類型

可空

備註

CategoryID

int

not null

identity/ 主鍵

CategoryName

varchar(50)

not null

 

2、  論壇版塊表 dbo.Boards

字段名

字段類型

可空

備註

BoardID

int

not null

identity/ 主鍵

BoardName

varchar(50)

not null

 

BoardCategory

int

not null

對應論壇版塊分類表的 CategoryID

3、  論壇主題表 dbo.Topics

字段名

字段類型

可空

備註

TopicID

int

not null

identity/ 主鍵

TopicTitle

varchar(50)

not null

 

TopicContent

varchar(max)

not null

 

ParentTopic

int

null

如果帖子是主題貼這個字段爲 null ,否則就是所屬主題 id

TopicType

tinyint

not null

0 – 主題貼

1 – 回覆帖

 

實體繼承的定義

 

       Linq to sql 支持實體的單表繼承,也就是基類和派生類都存儲在一個表中。對於論壇來說,帖子有兩種,一種是主題貼,一種是回覆帖。那麼,我們就先定義帖子基類:

[Table (Name = "Topics" )]

public class Topic

{

    [Column (Name = "TopicID" , DbType = "int identity" , IsPrimaryKey = true , IsDbGenerated = true , CanBeNull = false )]

    public int TopicID { get ; set ; }

 

    [Column (Name = "TopicType" , DbType = "tinyint" , CanBeNull = false )]

    public int TopicType { get ; set ; }

 

    [Column (Name = "TopicTitle" , DbType = "varchar(50)" , CanBeNull = false )]

     public string TopicTitle { get ; set ; }

 

    [Column (Name = "TopicContent" , DbType = "varchar(max)" , CanBeNull = false )]

    public string TopicContent { get ; set ; }

}

       這些實體的定義大家應該很熟悉了。下面,我們再來定義兩個實體繼承帖子基類,分別是主題貼和回覆貼:

public class NewTopic : Topic

{

    public NewTopic()

    {

        base .TopicType = 0;

    }

}

 

public class Reply : Topic

{

    public Reply()

    {

        base .TopicType = 1;

    }

 

    [Column (Name = "ParentTopic" , DbType = "int" , CanBeNull = false )]

    public int ParentTopic { get ; set ; }

}

       對於主題貼,在數據庫中的 TopicType 就保存爲 0 ,而對於回覆貼就保存爲 1 。回覆貼還有一個相關字段就是回覆所屬主題貼的 TopicID 。那麼,我們怎麼告知 Linq to sql TopicType 0 的時候識別爲 NewTopic ,而 1 則識別爲 Reply 那?只需稍微修改一下前面的 Topic 實體定義:

[Table (Name = "Topics" )]

[InheritanceMapping (Code = 0, Type = typeof (NewTopic ), IsDefault = true )]

[InheritanceMapping (Code = 1, Type = typeof (Reply ))]

public class Topic

{

    [Column (Name = "TopicID" , DbType = "int identity" , IsPrimaryKey = true , IsDbGenerated = true , CanBeNull = false )]

    public int TopicID { get ; set ; }

 

    [Column (Name = "TopicType" , DbType = "tinyint" , CanBeNull = false , IsDiscriminator = true )]

    public int TopicType { get ; set ; }

 

    [Column (Name = "TopicTitle" , DbType = "varchar(50)" , CanBeNull = false )]

    public string TopicTitle { get ; set ; }

 

    [Column (Name = "TopicContent" , DbType = "varchar(max)" , CanBeNull = false )]

    public string TopicContent { get ; set ; }

}

       爲類加了 InheritanceMapping 特性定義, 0 的時候類型就是 NewTopic 1 的時候就是 Reply 。並且爲 TopicType 字段上的特性中加了 IsDiscriminator = true ,告知 Linq to sql 這個字段就是用於分類的字段。

 

實體繼承的使用

 

       定義好繼承的實體之後,我們就可以使用了。先是自定義一個 DataContext 吧:

public partial class BBSContext : DataContext

{

    public Table <BoardCategory > BoardCategories;

    public Table <Board > Boards;

    public Table <Topic > Topics;

    public BBSContext(string connection) : base (connection) { }

}

       然後,我們來測試一下 Linq to sql 是否能根據 TopicType 識別派生類:

        BBSContext ctx = new BBSContext ("server=xxx;database=BBS;uid=xxx;pwd=xxx" );

        var query = from t in ctx.Topics select t;

        foreach (Topic topic in query)

        {

            if (topic is NewTopic )

            {

                NewTopic newtopic = topic as NewTopic ;

                Response.Write(" 標題: " + newtopic.TopicTitle + " 類型: " + newtopic.TopicType + "<br/>" );

            }

            else if (topic is Reply )

            {

                Reply reply = topic as Reply ;

                Response.Write(" 標題: " + reply.TopicTitle + " 類型: " + reply.TopicType + " 隸屬主題: " + reply.ParentTopic + "<br/>" );

            }

        }

       然後我們往 Topics 表中加一些數據,如下圖:

 

       啓動程序得到如下測試結果:

 

       當然,你也可以在查詢句法中直接查詢派生實體:

        IEnumerable newtopiclist = (from t in ctx.Topics.OfType<NewTopic >() select t).ToList();

        newtopics.DataSource = newtopiclist;

        IEnumerable replylist = (from t in ctx.Topics.OfType<Reply >() select t).ToList();

        replies.DataSource = replylist;

        Page.DataBind();

       newtopic replies 是兩個 GridView 控件,執行效果如下圖:

 

       再來看看如何進行增刪操作:

        NewTopic nt = new NewTopic () { TopicTitle = " 還是新主題 " , TopicContent = " 還是新主題 " };

        Reply rpl = new Reply () { TopicTitle = " 還是新回覆 " , TopicContent = " 還是新回覆 " , ParentTopic = 4 };

        ctx.Topics.Add(nt);

        ctx.Topics.Add(rpl);

        ctx.SubmitChanges();

        rpl = ctx.Topics.OfType<Reply >().Single(reply => reply.TopicID == 8);

        ctx.Topics.Remove(rpl);

        ctx.SubmitChanges();

 

實體關係的定義

 

       比如我們的論壇分類表和論壇版塊表之間就有關係,這種關係是 1 對多的關係。也就是說一個論壇分類可能有多個論壇版塊,這是很常見的。定義實體關係的優勢在於,我們無須顯式作連接操作就能處理關係表的條件。

       首先來看看分類表的定義:

 

[Table (Name = "Categories" )]

public class BoardCategory

{

    [Column (Name = "CategoryID" , DbType = "int identity" , IsPrimaryKey = true , IsDbGenerated = true , CanBeNull = false )]

    public int CategoryID { get ; set ; }

 

    [Column (Name = "CategoryName" , DbType = "varchar(50)" , CanBeNull = false )]

    public string CategoryName { get ; set ; }

 

    private EntitySet <Board > _Boards;

 

    [Association (OtherKey = "BoardCategory" , Storage = "_Boards" )]

    public EntitySet <Board > Boards

    {

        get { return this ._Boards; }

        set { this ._Boards.Assign(value ); }

    }

 

    public BoardCategory()

    {

        this ._Boards = new EntitySet <Board >();

    }

}

       CategoryID CategoryName 的映射沒有什麼不同,只是我們還增加了一個 Boards 屬性,它返回的是 Board 實體集。通過特性,我們定義了關係外鍵爲 BoardCategory Board 表的一個字段)。然後來看看 1 對多,多端版塊表的實體:

 

[Table (Name = "Boards" )]

public class Board

{

    [Column (Name = "BoardID" , DbType = "int identity" , IsPrimaryKey = true , IsDbGenerated = true , CanBeNull = false )]

    public int BoardID { get ; set ; }

 

    [Column (Name = "BoardName" , DbType = "varchar(50)" , CanBeNull = false )]

    public string BoardName { get ; set ; }

 

    [Column (Name = "BoardCategory" , DbType = "int" , CanBeNull = false )]

    public int BoardCategory { get ; set ; }

 

    private EntityRef <BoardCategory > _Category;

 

    [Association (ThisKey = "BoardCategory" , Storage = "_Category" )]

    public BoardCategory Category

    {

        get { return this ._Category.Entity; }

        set

        {

            this ._Category.Entity = value ;

            value .Boards.Add(this );

        }

}

}

       在這裏我們需要關聯分類,設置了 Category 屬性使用 BoardCategory 字段和分類表關聯。 


實體關係的使用

   
    好了,現在我們就可以在查詢句法中直接關聯表了(數據庫中不一定要設置表的外鍵關係):

        Response.Write("------------- 查詢分類爲 1 的版塊 -------------<br/>" );

        var query1 = from b in ctx.Boards where b.Category.CategoryID == 1 select b;

        foreach (Board b in query1)

            Response.Write(b.BoardID + " " + b.BoardName + "<br/>" );

        Response.Write("------------- 查詢版塊大於 2 個的分類 -------------<br/>" );

        var query2 = from c in ctx.BoardCategories where c.Boards.Count > 2 select c;

        foreach (BoardCategory c in query2)

            Response.Write(c.CategoryID + " " + c.CategoryName + " " + c.Boards.Count + "<br/>" );

       在數據庫中加一些測試數據,如下圖:

 

       運行程序後得到下圖的結果:

 

       我想定義實體關係的方便我不需要再用語言形容了吧。執行上述的程序會導致下面 SQL 的執行:

SELECT [t0].[BoardID], [t0].[BoardName], [t0].[BoardCategory]

FROM [Boards] AS [t0]

INNER JOIN [Categories] AS [t1] ON [t1].[CategoryID] = [t0].[BoardCategory]

WHERE [t1].[CategoryID] = @p0

-- @p0: Input Int32 (Size = 0; Prec = 0; Scale = 0) [1]

 

SELECT [t0].[CategoryID], [t0].[CategoryName]

FROM [Categories] AS [t0]

WHERE ((

    SELECT COUNT(*)

    FROM [Boards] AS [t1]

    WHERE [t1].[BoardCategory] = [t0].[CategoryID]

    )) > @p0

-- @p0: Input Int32 (Size = 0; Prec = 0; Scale = 0) [2]

 

SELECT [t0].[BoardID], [t0].[BoardName], [t0].[BoardCategory]

FROM [Boards] AS [t0]

WHERE [t0].[BoardCategory] = @p0

-- @p0: Input Int32 (Size = 0; Prec = 0; Scale = 0) [1]

        可以看到,第二個查詢並沒有做外連接,還記得 DataLoadOptions 嗎?我們可以要求 Linq to sql 在讀取版塊分類信息的時候也把版塊信息一起加載:

 

DataLoadOptions options = new DataLoadOptions ();

        options.LoadWith<BoardCategory >(c => c.Boards);

        ctx.LoadOptions = options;

        Response.Write("------------- 查詢版塊大於 2 個的分類 -------------<br/>" );

        var query2 = from c in ctx.BoardCategories where c.Boards.Count > 2 select c;

        foreach (BoardCategory c in query2)

            Response.Write(c.CategoryID + " " + c.CategoryName + " " + c.Boards.Count + "<br/>" );

查詢經過改造後會得到下面的 SQL

 

SELECT [t0].[CategoryID], [t0].[CategoryName], [t1].[BoardID], [t1].[BoardName], [t1].[BoardCategory], (

    SELECT COUNT(*)

    FROM [Boards] AS [t3]

    WHERE [t3].[BoardCategory] = [t0].[CategoryID]

    ) AS [count]

FROM [Categories] AS [t0]

LEFT OUTER JOIN [Boards] AS [t1] ON [t1].[BoardCategory] = [t0].[CategoryID]

WHERE ((

    SELECT COUNT(*)

    FROM [Boards] AS [t2]

    WHERE [t2].[BoardCategory] = [t0].[CategoryID]

    )) > @p0

ORDER BY [t0].[CategoryID], [t1].[BoardID]

-- @p0: Input Int32 (Size = 0; Prec = 0; Scale = 0) [2]

         在添加分類的時候,如果這個分類下還有新的版塊,那麼提交新增分類的時候版塊也會新增:

 

        BoardCategory dbcat = new BoardCategory () { CategoryName = "Database" };

        Board oracle = new Board () { BoardName = "Oracle" , Category = dbcat};

        ctx.BoardCategories.Add(dbcat);

        ctx.SubmitChanges();

       上述代碼導致下面的 SQL 被執行:

 

INSERT INTO [Categories]([CategoryName]) VALUES (@p0)

 

SELECT [t0].[CategoryID]

FROM [Categories] AS [t0]

WHERE [t0].[CategoryID] = (SCOPE_IDENTITY())

 

-- @p0: Input AnsiString (Size = 8; Prec = 0; Scale = 0) [Database]

 

INSERT INTO [Boards]([BoardName], [BoardCategory]) VALUES (@p0, @p1)

 

SELECT [t0].[BoardID]

FROM [Boards] AS [t0]

WHERE [t0].[BoardID] = (SCOPE_IDENTITY())

 

-- @p0: Input AnsiString (Size = 6; Prec = 0; Scale = 0) [Oracle]

-- @p1: Input Int32 (Size = 0; Prec = 0; Scale = 0) [23]

    今天就講到這裏。大家可以自己嘗試爲帖子表也定義實體的關係,因爲,是不是可以直接通過帖子獲取帖子下的回覆,或者直接通過回覆得到所屬帖子那?

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章