具有代码优先的DbContext Generator T4模板

con*_*i2k 3 t4 entity-framework ef-code-first

Database First(和Model First)方法有这个不错的DbContext Generator脚手架文件来生成上下文和模型; Model.Context.tt + Model.tt

由于它们具有内置辅助方法来检索导航属性,因此将它们用于其他目的也非常方便,例如创建控制器,视图等.ASP.NET Scaffolding也做类似的工作但在这种情况下,脚手架需要相反,这些T4文件会同时生成所有文件.

但是,它们仅使用"edmx"文件作为输入.是否可以使用/转换它们用于Code First方法?

实体框架版本6.1

con*_*i2k 11

这是一个工作样本 - 2018年6月5日更新:https:
//github.com/coni2k/DbContextGeneratorWithCodeFirst

  • Visual Studio 2017
  • 目标框架4.6.1
  • 实体框架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))
Run Code Online (Sandbox Code Playgroud)

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

<#@ include file="EF6.Utility.CS.ttinclude"#><#@ 
Run Code Online (Sandbox Code Playgroud)

由于需要修改此文件,请找到该文件并复制到项目文件夹.就我而言,它在这个文件夹下;

%ProgramFiles(x86)%\Microsoft Visual Studio 12.0\Common7\IDE\Extensions\Microsoft\Entity Framework Tools\Templates\Includes\
Run Code Online (Sandbox Code Playgroud)

如您所见,这是一个可选步骤.我们可以修改原始文件,但是使用副本更安全并保留原始文件.

2.修改ttinclude文件

在include文件中,有三种方法使用edmx文件.

public IEnumerable<GlobalItem> CreateEdmItemCollection(string sourcePath)

public XElement LoadRootElement(string sourcePath)

private void ProcessErrors(IEnumerable<EdmSchemaError> errors, string sourceFilePath)
Run Code Online (Sandbox Code Playgroud)

重要的是LoadRootElement读取xml文件的方法.我们可以从Code First DbContext创建一个内存流,让它从这个流中读取数据,而不是传递物理xml.

一个.将这两个方法添加到包含文件中;

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;
}
Run Code Online (Sandbox Code Playgroud)

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

湾 在文件的开头添加这三行;

//<#@ 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" #>
Run Code Online (Sandbox Code Playgroud)

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

由于T4文件有自己的域,DbContext因此无法使用项目的web/app.config文件中的连接字符串进行初始化.最简单的解决方法是使用显式连接字符串对其进行初始化.

通过添加以连接字符串作为参数的新构造函数来修改DBContext类.

public QAContext(string nameOrConnectionString)
    : base(nameOrConnectionString)
{
}
Run Code Online (Sandbox Code Playgroud)

4.修改T4文件

现在T4已准备好传递Code First DbContext实例.相应地更新文件.

一个.为了能够访问和实例化它,将DbContext类库添加为T4文件开头的汇编引用;

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

湾 用inputFile变量替换变量connectionString

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

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 ...
Run Code Online (Sandbox Code Playgroud)

d.VerifyCaseInsensitiveTypeUniquenessconnectionString参数的更新方法

if (!typeMapper.VerifyCaseInsensitiveTypeUniqueness(typeMapper.GetAllGlobalItems(itemCollection), connectionString))
Run Code Online (Sandbox Code Playgroud)

现在它已经可以使用了.您可以根据需要修改文件的其余部分,根据您的Code First模型创建任何您想要的文件,如html,javascript,razor等.

这是一项工作,绝对可以改进.例如,include文件可以将DbContext类型作为参数,实例化它,确定它是Code First还是Database First,然后继续处理.或者找到web/app.config文件并从那里读取连接字符串.但是应该足够一个开始.