DbContext 生成器 T4 模板与 Code First

4
Database First(和Model First)方法有一个很好的DbContext Generator脚手架文件,可以生成上下文和模型; Model.Context.tt + Model.tt
由于它们具有内置的帮助程序方法来检索导航属性,因此将它们用于其他目的(如创建控制器、视图等)非常方便。ASP.NET Scaffolding也可以完成类似的工作,但在那种情况下,需要为每个模型调用Scaffolder,而这些T4文件一次性生成所有文件。
然而,它们只使用"edmx"文件作为输入。是否可能将它们用于Code First方法?
Entity Framework 版本6.1
1个回答

11

以下是一个可用的示例 - 更新于2018年6月5日:
https://github.com/coni2k/DbContextGeneratorWithCodeFirst

  • Visual Studio 2017
  • 目标框架4.6.1
  • Entity Framework 6.2.0

要实现这个目标,需要执行一些步骤。

1. 找到并复制EF6.Utility.CS.ttinclude文件到您的项目中

在T4文件(Model.Context.tt + Model.tt)中,inputFile变量(edmx文件)在两个位置使用;

const string inputFile = @"QAModel.edmx";
//var textTransform ...
//var code ...
//var ef ...
//var typeMapper ...
//var fileManager ...
var itemCollection = new EdmMetadataLoader(textTransform.Host, textTransform.Errors).CreateEdmItemCollection(inputFile);
//var codeStringGenerator ...

if (!typeMapper.VerifyCaseInsensitiveTypeUniqueness(typeMapper.GetAllGlobalItems(itemCollection), inputFile))

VerifyCaseInsensitiveTypeUniqueness 是一种验证方法,inputFile 用作错误的源位置。重要的是 EdmMetadataLoader,它来自于在文件开头定义的 EF6.Utility.CS.ttinclude 文件;

<#@ include file="EF6.Utility.CS.ttinclude"#><#@ 

由于需要修改此文件,请找到该文件并将其复制到您的项目文件夹中。在我的情况下,它位于此文件夹下;

%ProgramFiles(x86)%\Microsoft Visual Studio 12.0\Common7\IDE\Extensions\Microsoft\Entity Framework Tools\Templates\Includes\

如您所见,这是一个可选步骤。我们可以修改原始文件,但使用副本更安全,可以保持原始文件的完整性。

2. 修改ttinclude文件

在包含文件中,有三个方法使用edmx文件。

public IEnumerable<GlobalItem> CreateEdmItemCollection(string sourcePath)

public XElement LoadRootElement(string sourcePath)

private void ProcessErrors(IEnumerable<EdmSchemaError> errors, string sourceFilePath)
重要的方法是LoadRootElement,它用于读取xml文件。我们可以从Code First DbContext创建内存流,让它从这个流中读取数据,而不是传递物理xml文件。
a.在包含文件中添加这两个方法;
public IEnumerable<GlobalItem> CreateEdmItemCollection(DbContext dbContext)
{
    ArgumentNotNull(dbContext, "dbContext");

    var schemaElement = LoadRootElement(dbContext);
    if (schemaElement != null)
    {
        using (var reader = schemaElement.CreateReader())
        {
            IList<EdmSchemaError> errors;
            var itemCollection = EdmItemCollection.Create(new[] { reader }, null, out errors);

            ProcessErrors(errors, dbContext.Database.Connection.ConnectionString);

            return itemCollection ?? new EdmItemCollection();
        }
    }
    return new EdmItemCollection();
}

public XElement LoadRootElement(DbContext dbContext)
{
    ArgumentNotNull(dbContext, "dbContext");

    XElement root;

    using (var stream = new MemoryStream())
    {
        using (var writer = XmlWriter.Create(stream))
        {
            EdmxWriter.WriteEdmx(dbContext, writer);
        }
        stream.Position = 0;

        root = XElement.Load(stream, LoadOptions.SetBaseUri | LoadOptions.SetLineInfo);
        root = root.Elements()
            .Where(e => e.Name.LocalName == "Runtime")
            .Elements()
            .Where(e => e.Name.LocalName == "ConceptualModels")
            .Elements()
            .Where(e => e.Name.LocalName == "Schema")
            .FirstOrDefault()
                ?? root;
    }

    return root;
}

现在我们在包含文件中使用DbContext,这需要包含System.Data.Entity库。

b. 在文件开头添加以下三行代码;

//<#@ assembly name="%VS120COMNTOOLS%..\IDE\Microsoft.Data.Entity.Design.dll" #>
<#@ assembly name="System.Data.Entity" #>
<#@ import namespace="System.Data.Entity" #>
<#@ import namespace="System.Data.Entity.Infrastructure" #>
//<#@ import namespace="System" #>

3. 添加新的构造函数修改DbContext类

由于T4文件有自己的域,所以无法使用项目的web / app.config文件中的连接字符串来初始化DbContext。最简单的解决方法是使用显式连接字符串来初始化它。

通过添加一个带有连接字符串参数的新构造函数来修改您的DBContext类。

public QAContext(string nameOrConnectionString)
    : base(nameOrConnectionString)
{
}

4. 修改T4文件

现在T4准备好通过Code First DbContext实例传递。根据需要更新该文件。

a. 为了能够访问和实例化它,将您的DbContext类库作为程序集引用添加到T4文件开头;

//<#@ template language="C#" ...
<#@ assembly name="$(SolutionDir)DbContextGeneratorWithCodeFirst\bin\Debug\DbContextGeneratorWithCodeFirst.dll"#>
//<#@ include file="EF6 ...

b. 将inputFile变量替换为connectionString

const string connectionString = @"Server=(LocalDb)\v11.0;Database=QADb;Integrated Security=True;MultipleActiveResultSets=True";
//var textTransform ...

c. 更新 CreateEdmItemCollection 模块

//var   fileManager ...
IEnumerable<GlobalItem> itemCollection;
using (var dbContext = new DbContextGeneratorWithCodeFirst.QAContext(connectionString))
{
    itemCollection = new EdmMetadataLoader(textTransform.Host, textTransform.Errors).CreateEdmItemCollection(dbContext);

    if (itemCollection == null)
    {
        return string.Empty;
    }
}
//var codeStringGenerator ...

d. 使用connectionString参数更新VerifyCaseInsensitiveTypeUniqueness方法。

if (!typeMapper.VerifyCaseInsensitiveTypeUniqueness(typeMapper.GetAllGlobalItems(itemCollection), connectionString))
现在它已经可以使用了。您可以根据需要修改文件的其余部分,基于您的Code First模型创建任何文件,例如html、javascript、razor等。
这是一些工作,当然还有改进的空间。例如,包含文件可以将DbContext类型作为参数,实例化它,确定它是Code First还是Database First,然后继续处理。或者定位web/app.config文件并从那里读取连接字符串。不过,这应该足以入门了。

非常感谢!回答非常详细 - 我几乎没有遇到任何问题就成功了。 - Michał Drozdowicz
@MichałDrozdowicz 很高兴能够帮到你。如果有需要改进的地方,请告诉我。 - coni2k
这些问题与我们如何构建DbContext有关 - 它是基于程序集中发现的实体类型动态生成的,因此我遇到了一些来自类型加载器的奇怪异常。 - Michał Drozdowicz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接