65.9K
CodeProject 正在变化。 阅读更多。
Home

在 LINQ for SQL 中创建多对多关系

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.46/5 (7投票s)

2006 年 9 月 1 日

5分钟阅读

viewsIcon

120245

downloadIcon

1634

在 LINQ for SQL 中创建多对多关系。

Sample Image

引言

Microsoft 正在大力开发代号为“Orcas”的 Visual Studio,并最近发布了 LINQ CTP(五月版)和 ADO.NET vNext。本文基于 LINQ 2006 年 5 月 CTP。

必备组件

要运行示例并理解本文,您必须拥有

  • Visual Studio 2005
  • MS SQL Server 2005
  • LINQ 五月 CTP

开始新项目

安装 LINQ 后,VS2005 中将出现与 LINQ 相关的模板。因此,请按以下步骤创建新项目:

  1. 单击“开始”|“程序”|“Microsoft Visual Studio 2005”|“Microsoft Visual Studio 2005”菜单命令。
  2. 单击“工具”|“选项”菜单命令。
  3. 在 Microsoft Visual Studio 中,单击“文件”|“新建”|“项目…”菜单命令。
  4. 在“新建项目”对话框中,在“项目类型”下,单击“Visual C#”|“LINQ Preview”。
  5. 在“模板”中,单击“LINQ Console Application”。
  6. 在“名称”字段中输入 LINQRelations 作为新项目的名称。
  7. 点击“确定”。
  8. 在警告对话框中,单击“确定”。

VS 2005 LINQ template

好了……现在我们有了一个带有空窗体的新的 Windows Forms 应用程序。让我们为数据库对象生成代码。

使用代码生成来创建对象模型

  1. 将数据库文件(来自LINQ_Databases.zip)附加到 SQL Server,名称为MovieCollection
  2. 生成数据库表关系可能很繁琐且容易出错。在 Visual Studio 支持 LINQ 之前,您可以手动运行代码生成工具 SQLMetal。单击“开始”|“程序”|“Microsoft Visual Studio 2005”|“Visual Studio Tools”|“Visual Studio 2005 Command Prompt”菜单项。
  3. 执行以下命令更改为项目位置的目录(请将路径更改为您项目的实际路径):cd "D:\Projects\NET\LINQRelations\LINQRelations"
  4. 通过输入以下命令生成完整的 MovieCollection 类层次结构,并带有主键和外键注解:"C:\Program Files\LINQ Preview\Bin\SqlMetal.exe" /server:.\SQLExpress /database:" MovieCollection " /pluralize /code:MovieCollection.cs
  5. 在 Microsoft Visual Studio 的“解决方案资源管理器”中,单击“LINQRelations”|“添加”|“现有项”菜单命令。
  6. 找到新的MovieCollection.cs文件,然后单击“添加”。
  7. 在“解决方案资源管理器”中,双击MovieCollection.cs
  8. 编译项目,以便在接下来的步骤中显示这些对象。

现在,我们已经生成了实体,并且它们已准备好进行查询。

使用 LINQ 列出实体

  1. 在 Visual Studio 2005(已打开 LINQRelations)中,打开“数据源”窗口(窗体菜单“数据”|“显示数据源”)。
  2. 右键单击“数据源”窗口中的任意位置,然后选择“添加新数据源…”
  3. 从向导中,选择“对象”作为数据源类型,然后单击“下一步”。
  4. 选择Movie,如下图所示,然后单击“下一步”|“完成”关闭向导。

    Add new data source

  5. 打开 Form1。
  6. 将 Movie 从“数据源”窗口拖放到窗体上。
  7. 对于窗体的 Load 事件,我们需要编写一些代码来从服务器加载数据。
    private void Form1_Load(object sender, EventArgs e)
    {
        MovieCollection colllection = 
          new MovieCollection(@"Data Source=.\SQLEXPRESS;Database" + 
                              @" = MovieCollection;Integrated Security=True;");
        Table<Movie>  movies = colllection.Movies;
    
        movieBindingSource.DataSource = movies;
    }

执行项目以确认结果。(我添加了一部电影,这样表格就不会是空的)。到目前为止一切顺利。Microsoft 已确认 SQL Metal 工具(我们用来生成 MovieCollection.cs 中代码的工具)支持一对一和一对多关系。如果我们迫不及待想支持它怎么办?

添加多对多关系

在此数据库中,我们有两个实体表(Movie 和 Category)和一个连接表 MovieCategory,用于支持多对多关系。

database design

我们需要编写一些代码来支持它,但这值得付出努力。

首先,我们需要访问 DataContext 实例

我们必须从一个实例获取所有对象。这样,数据一致性就得到了保证。(更多信息,请参阅 LINQ CTP 的 C#/VB.NET 的 DLinq 实操实验室)。在Program.cs文件中,编写

private static MovieCollection dataContext = null;
internal static MovieCollection DataContext
{
    get
    {
        if (null == dataContext)
        {
            dataContext = 
              new MovieCollection(@"Data Source=.\SQLEXPRESS;" + 
                                  @"Database = MovieCollection;" + 
                                  @"Integrated Security=True;");
        }

        return dataContext;
    }
}

我们将使用此 Singleton 实现从一个点访问数据库。为确保这一点,我们必须将 Form_Load 事件更改为

private void Form1_Load(object sender, EventArgs e)
{
    movieBindingSource.DataSource = Program.DataContext.Movies;
}

扩展 Movie 类

接下来要做的是扩展 Movie 类以返回所有 Category。我更倾向于在 .NET 2.0 中使用部分类,这样如果我们必须重新生成代码,就可以区分工具生成的代码。在 Visual Studio 2005 中,添加一个名为MovieCollectionExtend.cs的新文件。不要忘记将MovieCollection.cs中的类放在 LINQRelations 命名空间中。现在,让我们利用部分类的魔力。用以下内容填充MovieCollectionExtend.cs

public partial class Movie
{
    protected EntitySet<Category> categories;
    /// <summary>
    /// Return assigned categories to current movie
    /// </summary>
    public EntitySet<Category>  Categories
    {
        get
        {
            categories = new EntitySet<Category>(onAddCategory, onRemoveCategory);

            var cats = from c in Program.DataContext.Categories
                       join mc in Program.DataContext.MovieCategories 
                               on c.CategoryID equals mc.CategoryID 
                       where mc.MovieID == this.MovieID
                       select c;

            //add to result 
            foreach (var c in cats)
                categories.Add(c);

            return categories;
        }
    }

    protected void onAddCategory(Category arg)
    {
        //add entry to MovieCategories table

            MovieCategory mc = new MovieCategory();
            mc.MovieID = this.MovieID;
            mc.CategoryID = arg.CategoryID;

            Program.DataContext.MovieCategories.Add(mc);
    }
    protected void onRemoveCategory(Category arg)
    {
    }
}

信不信由你,这就是我们从特定电影中提取所有分类所需的全部。

我们刚才做了什么?

让我们一步一步地解释代码。我们为 Movie 类创建了一个名为 Categories 的新属性。在 setter 内部,我们创建了一个新的 Categories 集合实例,该实例将被填充并在请求时返回。在构造函数中定义了两个委托:一个在向集合添加新元素时执行,另一个在删除元素时执行。

categories = new EntitySet Category (onAddCategory, onRemoveCategory);

下一条语句对对象执行 LINQ 查询。不幸的是,那时我们必须能够访问 DataContext 实例。

var cats = from c in Program.DataContext.Categories
           join mc in Program.DataContext.MovieCategories 
                   on c.CategoryID equals mc.CategoryID 
           where mc.MovieID == this.MovieID
           select c;

此查询将被翻译成 SQL:

SELECT [t0].[CategoryID], [t0].[CategoryName]
FROM [Category] AS [t0], [MovieCategory] AS [t1]
WHERE ([t1].[MovieID] = 1) AND ([t0].[CategoryID] = [t1].[CategoryID])

接下来的两行很简单:填充私有集合并将其作为结果返回。

//add to result 
foreach (var c in cats)
    categories.Add(c);

return categories;

委托 onAddCategory 负责在将新分类分配给电影时向连接表 MovieCategory 添加新行,而 onRemoveCategory 负责删除一行。就是这样。

你确定就这样了吗?

让我们创建另一个 DataGridView 来确保我们达成了目标。

  1. 在窗体上添加一个新的 DataGridView 控件,并将其命名为 categoriesDataGridView
  2. Form_load 事件中,添加一行以自动生成列。
    categoriesDataGridView.AutoGenerateColumns = true;
  3. movieBindingSource 创建一个新的事件处理程序,用于 CurrentChanged 事件。
  4. 为它编写以下代码:
    private void movieBindingSource_CurrentChanged(object sender, EventArgs e)
    {
        categoriesDataGridView.DataSource = 
          (movieBindingSource.Current as Movie).Categories;
    }

历史

  • 版本 1 - 已发布。
© . All rights reserved.