LINQtoSQL:自定义设计器生成的代码
自定义 LINQtoSQL 设计器生成的代码。
引言
LINQ to SQL 用于操作 SQL Server 数据库中的数据。Visual Studio 附带的 LINQtoSQL 设计器是一个非常强大的工具,用于生成 LINQ。它快速、易于使用,并且在许多方面都非常出色。
但是,它有一个缺点。您无法自定义生成的代码。与所有其他设计器一样,它能正常工作,很容易启动一个演示,但一旦遇到实际情况,您总是想要更多一点。额外的这部分总是取决于问题。因此,我不会尝试创建更好的 LINQ to SQL 代码。我只会向您展示如何在几分钟内自定义和丰富生成的代码。这种自定义对标准 LINQtoSQL 设计器是完全透明的,您仍然将使用它来建模您的类和数据访问。
简要说明
我们可以根据所需生成代码与 LINQtoSQL 生成代码的不同程度,将问题分为三种场景。下面将更详细地讨论每种场景,并将基于一个名为 Reegenerator 的免费 Visual Studio 工具实现。
使用此工具,您可以在解决方案中定义一个单独的项目,该项目包含不属于构建的代码生成器。然后,您将 LINQtoSQL 项目项(.dbml)附加到在该项目中定义的代码生成类。当您使用 LINQtoSQL 设计器并保存文件时,代码生成将自动调用,以创建依赖于 DBML 文件的 .Designer.cs 文件。
Using the Code
从链接下载 zip 文件。您需要安装 Visual Studio 2008 和 Reegenerator。解压后,将解压文件夹中的解决方案文件加载到 Visual Studio 中。该解决方案包含一个名为 CustomizeDesigners 的项目,其中包含代码生成器,以及一个名为 Business 的项目,其中包含 DBML 文件和用作 LINQtoSQL 设计器源的数据库。在 Business 和 RenderersLibrary 项目中,都有三个 Scenario 文件夹,它们托管 DBML 和代码生成器,如下所述,针对每种场景。
要触发代码生成,您需要编辑并保存一个 DBML 文件,或者右键单击它并调用“运行自定义工具”。要进行调试,您只需在 RenderersLibrary 项目中的代码生成器代码中设置一个断点并启动调试会话。该会话将调用 Visual Studio 的第二个实例。在第二个实例中加载相同的解决方案,并通过保存 DBML 文件来触发代码生成。生成的代码将保存为依赖于 DBML 文件的项目项(通常是“.Designer.cs”)。您需要单击 DBML 项目项左侧的加号以展开其子项。
要检查 DBML 项目项附加到什么,右键单击它并单击“附加渲染器...”。
下图显示了您的工作环境将如何
场景 1 - 小幅更改
在这种情况下,我们最可能只想简单地调用 LINQtoSQL 默认代码生成器,将结果代码作为字符串获取,以我们想要的方式对其进行操作,然后将修改后的代码发送回 Visual Studio。
业务项目 Scenario1 文件夹中的 DBML 文件与 CustomizeDesigners\Scenario1\CustSmallChangesToDefaultLINQtoSQL
类链接。实现所需修改的代码非常简单
public class SmallChangesToDefaultLINQtoSQL : Generators.CodeRenderer
{
/// <summary>
/// Default custom tool for dbml files.
/// </summary>
public const string CustomToolName = "MSLinqToSQLGenerator";
public override Generators.RenderResults Render()
{
// execute the default custom tool.
byte[] resultsAsBytes = base.RunOtherCustomTool(CustomToolName);
// get Microsoft's generated code as string.
string results = System.Text.Encoding.Default.GetString(resultsAsBytes);
// transform the string.
string modifiedResults = AddSomething(results); // AddAttribute(results);
// return the string to be saved into the .Designer.cs file.
return new Generators.RenderResults(modifiedResults);
}
private string AddSomething(string results)
{
results = string.Format(@"// -------------------------------------------------------
// Automatically generated.
// Generation date: {0}
// Generated by: {1}
// -------------------------------------------------------
{2}",
System.DateTime.Now.ToString("yyyy-MM-dd hh:mm"),
System.Security.Principal.WindowsIdentity.GetCurrent().Name,
results);
return results;
}
}
如上面的代码所示,我们调用标准代码生成器来检索 Microsoft 设计器生成的代码。一旦获得它,您可以通过向其中添加代码或使用字符串操作例程或正则表达式替换其某些部分来对其进行操作。
要测试,请编辑 Business 项目中的 Business\Scenario1\CustomersLINQ.dbml,保存它,展开其子项,然后打开 Business\Scenario1\CustomersLINQ.Designer.cs。您可以看到该文件包含添加到 Microsoft 代码中的额外代码。
要进行调试,请在 Render
方法中设置一个断点,启动调试会话,在第二个 Visual Studio 实例中加载相同的解决方案,然后执行与测试时相同的步骤。
示例代码生成器还有另一个方法 AddAttribute
,它演示了如何进行稍微更复杂的操作。如果所需的更改更复杂,您可以使用正则表达式。
场景 2 - 显著添加
在这种场景中,您对 Microsoft 生成的内容感到满意,但您想要额外的代码。额外代码的示例可以是为从表中生成的每个类添加一个方法,或为数据库对象创建 SQL 脚本。
实现此目的最简单的方法是将 DBML 文件链接到两个代码生成器。第一个调用 Microsoft 的代码生成器,您可能需要像场景 1 中那样稍微更改结果。第二个生成额外的代码。
要正确构建 DBML 文件的代码生成器,您需要能够将其反序列化为可序列化类实例。这是通过在代码生成器项目中拥有一个架构来实现的,该架构从该架构创建可序列化类。第二个代码生成器将 DBML 文件反序列化为此类,然后使用它生成所需的内容。
DBML 文件架构可以在 C:\Program Files\Microsoft Visual Studio 9.0\Xml\Schemas\DbmlSchema.xsd 中找到,假设您将 Visual Studio 安装在默认位置。此架构已添加到 CustomizeDesigners 项目的 Dbml\DbmlSchema.xsd 中。它与 CustomizeDesigners\XsdRenderers.rgt 代码生成器链接,该生成器调用与 xsd.exe 相同的机制,从架构创建可序列化类。展开 DbmlSchema.xsd 的子项目项以查看可序列化类。
Business\Scenario2\CustomersLINQ.dbml 链接到两个代码生成器(右键单击它并单击“附加渲染器...”进行检查),当您保存它时,会创建/更新两个 .cs 文件。Business\Scenario2\CustomersLINQ.Designer.cs 文件由 CustomizeDesigners\Scenario2\SmallChangesToDefaultLINQtoSQL.cs 生成,与 Scenario1 完全相同。
Business\Scenario2\CustomersLINQ.Hello.cs 文件由 CustomizeDesigners\Scenario2\AddHelloMethodToTables.rgt 生成。这是一个代码生成器模板文件,看起来很像一个 ASPX 页面。它还有一个代码隐藏文件,CustomizeDesigners\Scenario2\AddHelloMethodToTables.rgt.cs,其中包含手动编写的代码。CustomizeDesigners\Scenario2\AddHelloMethodToTables.Designer.cs 是模板的代码表示。它与代码隐藏文件一起形成一个类,当 DBML 文件保存时将被调用。
CustomizeDesigners\Scenario2\AddHelloMethodToTables.rgt 代码生成器模板如下所示
<%@ Template Language="C#" ClassName="AddHelloMethodToTables" %>
<%@ Import Namespace="System" %>
<%@ Import Namespace="CustomizeDesigners.Dbml" %>
// -------------------------------------------------------
// Automatically generated with Kodeo's Reegenerator
// Generator: CustomizeDesigners.Scenario2.AddMethodToTables
// Generation date: <%= System.DateTime.Now.ToString("yyyy-MM-dd hh:mm") %>
// Generated by: <%= System.Security.Principal.WindowsIdentity.GetCurrent().Name %>
// -------------------------------------------------------
namespace <%= base.ProjectItem.CodeNamespace %>
{
<% RenderTables(); %>
}
<%@ Method Name="RenderTable" %>
<%@ Parameter Name="table" Type="Table" %>
partial class <%= table.Type.Name %>
{
public string Hello()
{
return "Hello from table <%= table.Type.Name %>";
}
}
<%/ Method %>
与 ASPX 不同,您可以定义渲染方法来帮助您将代码生成工作分解为更易于管理的单元。另请注意命名空间下方的 <% RenderTables(); %>
调用。这调用了在代码隐藏中手动编写的函数。
Scenario2\AddHelloMethodToTables.rgt.cs 模板代码隐藏文件如下所示
public partial class AddHelloMethodToTables
{
/// <summary>
/// The database element as deserialized from the dbml file.
/// </summary>
Database _database = null;
/// <summary>
/// Takes place before the render. Deserializes the dbml file into _database.
/// </summary>
public override void PreRender()
{
base.PreRender();
// deserializes the dbml file into the private property.
this._database = DbmlSchemaFactory.Create<Database>(base.ProjectItem);
}
/// <summary>
/// Renders all the tables.
/// </summary>
private void RenderTables()
{
if (this._database.Table == null)
return;
foreach (Table table in this._database.Table)
{
RenderTable(table);
}
}
}
在代码生成之前,DBML 文件被反序列化到 _database
属性中。请注意 RenderTables
的实现,它从模板文件中调用。它又调用了模板中定义的 RenderTable(Table table)
。
要测试它,请编辑 Business\Scenario2\CustomersLINQ.dbml,保存它,展开其子项,并检查两个子 cs 文件的内容。
要进行调试,请在 PreRender
方法中设置一个断点,启动调试会话,在第二个 Visual Studio 实例中加载相同的解决方案,然后执行与测试时相同的步骤。
场景 3 - 完全重写
当您对设计器为您生成的内容完全不满意时,或者您只是想将设计器用于其他目的(例如,创建纯业务对象,这些对象对 LINQtoSQL 一无所知)时,此场景适用。
CustomizeDesigners\Scenario3\DbmlRenderer.rgt 在复制 Microsoft 的代码生成器方面做得足够好。就其本身而言,它几乎没有价值,因为如果 Microsoft 的代码对您来说足够好,您的问题将属于场景 1 或 2。
它的目的是作为一个示例,并提供熟悉度和结构,以便开始构建您的高度定制代码生成器,利用一个已有的设计器。
Business\Scenario3\CustomersLINQ.dbml 与 CustomizeDesigners\Scenario3\DbmlRenderer.rgt 链接,您可以像之前的场景一样进行测试和调试。
最后说明
您现在可以使用 LINQtoSQL 设计器做的事情是无限的。以下是一些示例
- 您可以使用设计器创建自己的 LINQtoSQL 自定义,这些自定义会神奇地生成。
- 您可以生成不同的东西,例如表的 SQL 脚本和 DBML 文件使用的存储过程。
- 只要设计器能很好地完成您想要实现的目标,您就可以将设计器用于与 LINQtoSQL 完全不同且不相关的事情。