Выполнение кода, сгенерированного Roslyn
Пишу проект, в котором использую рослин для кодогенерации и столкнулся с проблемой, когда нужно использовать сгенерированный код. Сначала опишу последовательность дейсвтий. В приложении есть кнопка при нажатии на которую создается таблица, в которую можно накидывать колонки. При создании таблицы для нее генерируется класс, имеющий следующий вид:
namespace GSCrm.Models.Custom.TableModels
{
using System;
using GSCrm.Models.Default.MainEntities;
using GSCrm.Models.Default.TableModels;
using System.Text.Json.Serialization;
public class TestTable : MainTable
{
}
}
Затем этот класс записывается в файл, а дерево класса передается в метод, занимающийся компиляцией:
// Запись в файл
CodeGenUtils.WriteNode(compilationUnit.SyntaxTree, entityName, entityType);
// Компиляция
CodeGenUtils.CreateCompilation(compilationUnit.SyntaxTree);
Код метода компиляции:
public static CSharpCompilation CreateCompilation(SyntaxTree syntaxTree)
{
Compilation = Compilation == null ? CSharpCompilation.Create(
"GSCrm",
new[] { syntaxTree }, new[]
{
MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
MetadataReference.CreateFromFile(typeof(JsonSerializer).Assembly.Location),
MetadataReference.CreateFromFile(typeof(Program).Assembly.Location),
MetadataReference.CreateFromFile(typeof(Database).Assembly.Location),
MetadataReference.CreateFromFile(typeof(Migration).Assembly.Location),
MetadataReference.CreateFromFile(typeof(Expression).Assembly.Location),
MetadataReference.CreateFromFile(dllFolderPath + "System.Runtime.dll"),
MetadataReference.CreateFromFile(dllFolderPath + "netstandard.dll")
},
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary))
: Compilation.AddSyntaxTrees(syntaxTree);
return Compilation;
}
После этого пользователь нажимает на кнопку "ApplyTable" и сущность добавляется в контекст приложения, по сути в класс контекста просто добавляется новая строка:
public DbSet<TestTable> TestTable { get; set; }
И снова происходит запись и компиляция:
// Запись в файл
CodeGenUtils.WriteNode(tree, "ApplicationContext", typeof(ApplicationContext));
// Компиляция
CodeGenUtils.CreateCompilation(tree);
Затем, необходимо произвести миграцию бд, для этого генерируется класс следующего вида:
namespace GSCrm.GenerateMigrations
{
using System;
using GSCrm.Data;
using GSCrm.Models.Custom.TableModels;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
[DbContext(typeof(ApplicationContext))]
[Migration("TestTable_2020410_221241")]
public partial class TestTable_2020410_221241 : Migration
{
private readonly ApplicationContext context;
public TestTable_2020410_221241() { }
public TestTable_2020410_221241(ApplicationContext context)
{
this.context = context;
}
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "TestTable",
columns: table => new
{
CreatedBy = table.Column<string>(nullable: false),
Id = table.Column<Guid>(nullable: false),
UpdatedBy = table.Column<string>(nullable: false),
Updated = table.Column<DateTime>(nullable: false),
Created = table.Column<DateTime>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_TestTable", x => x.Id);
});
}
public void Migrate()
{
context.Set<TestTable>();
context.Database.Migrate();
}
}
}
Для перевода бд на новую схему, надо вызвать метод Migrate. Для его получения я использую код:
using (MemoryStream stream = new MemoryStream())
{
EmitResult emit = CodeGenUtils.Compilation.Emit(stream);
stream.Seek(0, SeekOrigin.Begin);
// Здесь ошибка
if (emit.Success)
{
}
}
EmitResult содержит ошибку: Не удалось найти тип или имя пространства имен "TestTable" (возможно, отсутствует директива using или ссылка на сборку). При этом если выполнить emit до добавления сущности в контекст приложения и до миграции, такой ошибки не будет. Может быть кто-нибудь знает, в чем причина
Ответы (1 шт):
Если вдруг у кого-нибудь будет такая проблема, то одна из возможных проблем - неправильно сгенерированное дерево. В моем случае ошибка заключачлась в том, как я генерирую код для класса таблицы.
Там для генерации namespace-а я использовал такой код:
NamespaceDeclaration(IdentifierName("GSCrm.Models.Custom.TableModels").WithTrailingTrivia(LineFeed))
А нужно было такой:
NamespaceDeclaration(
QualifiedName(
QualifiedName(
QualifiedName(
IdentifierName("GSCrm"),
Token(DotToken),
IdentifierName("Models")),
Token(DotToken),
IdentifierName("Custom")),
Token(DotToken),
IdentifierName("TableModels")))
.WithNamespaceKeyword(Token(NamespaceKeyword).WithTrailingTrivia(Whitespace(" ")))