I'm using Roslyn to compile, emit and run C# source code. However, I've run into a limitation when faced with projects that use EntityFramework.
It seems that simply emitting the compilation isn't enough, as there is an EntityDeploy
build task that manipulates the DLLs after they've been emitted. (I believe it is embedding Metadata Artifacts in the DLLs after they're emitted).
In the .csproj
file I'm processing, I see the following entity deploy task:
<EntityDeploy Include="Models\Northwind.edmx">
<Generator>EntityModelCodeGenerator</Generator>
<LastGenOutput>Northwind.Designer.cs</LastGenOutput>
</EntityDeploy>
Is it possible to invoke this build task this directly and manipulate the DLLs I've emitted?
Note: I don't want to simply call msbuild.exe
or run MSBuild
on everything in the .csproj
file. The projects I'm building exist in memory, but not on disk, so that won't work in my case.
I'm trying to learn how to use the Microsoft.Build.Evaluation
stuff. I can find the EntityDeploy task, but I'm at a loss for how to invoke it (and for what parameters I should be providing).
var project = new Project(@"C:\Users\JoshVarty\Documents\Visual Studio 2015\Projects\WebApplication1\WebApplication1\WebApplication1.csproj");
//Get the entity deploy target? I'm not sure if this is a task or target.
var entityDeploy = project.Targets.Where(n => n.Key == "EntityDeploy").Single();
var projectTargetInstance = entityDeploy.Value;
I've also tried looking at the EntityDeploy
build task as it exists on disk.
var entityDeployTask = new Microsoft.Data.Entity.Build.Tasks.EntityDeploy();
entityDeployTask.Sources = //I'm not sure where I can get the ITaskItem[] I need
entityDeployTask.EntityDataModelEmbeddedResources = //I'm not sure where I can get the ITaskItem[]
entityDeployTask.Execute();
I'm simultaneously brand new to MSBuild
, EntityFramework
and EntityDeploy
, so please correct me if I've misused terms or come at this the wrong way altogether.
I'm not familiar with EntityDeploy
, but I'll give some information that I've gathered that might help you.
If you look at the targets file you can see that there's an EntityDeploy
target which relies on EntityDeploy
, EntityDeploySplit
, EntityDeploySetLogicalNames
and EntityClean
tasks.
When the EntityDeploy
target is called with list of .edmx
files the following happens:
.edmx
files and determines whether the results from processing each one should be embedded in the target assembly or placed alongside.NonEmbeddingItems
from 1., it splits the .edmx
files and places the result in OutputPath
EmbeddingItems
from 1., it splits the .edmx
files and places the result in EntityDeployIntermediateResourcePath
EntityDeployIntermediateResourcePath
to set the metadata LogicalName
on each file to the logical name to be used for embedding (It's just the relative path to the file from EntityDeployIntermediateResourcePath
with slashes replaced by dots)I haven't tried this, but it should be possible to call these in sequence to get the expected behavior using the Engine class:
// Instantiate a new Engine object
Engine engine = new Engine();
// Point to the path that contains the .NET Framework 2.0 CLR and tools
engine.BinPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.System)
+ @"\..\Microsoft.NET\Framework\v2.0.50727";
var entityDeploySplitTask = new EntityDeploySplit();
entityDeploySplitTask.Sources = new ITaskItem[]
{
new TaskItem("Model.edmx")// path to .edmx file from .csproj
};
entityDeploySplitTask.BuildEngine = engine;
entityDeploySplitTask.Execute();
var entityDeployTask = new EntityDeploy();
entityDeployTask.Sources = entityDeploySplitTask.NonEmbeddingItems
entityDeployTask.OutputPath = "."; // path to the assembly output folder
entityDeployTask.BuildEngine = engine;
entityDeployTask.Execute();
var entityDeployTask2 = new EntityDeploy();
entityDeployTask2.Sources = entityDeploySplitTask.EmbeddingItems
entityDeployTask2.OutputPath = "C:\Temp"; // path to an intermediate folder
entityDeployTask2.BuildEngine = engine;
entityDeployTask2.Execute();
var entityDeploySetLogicalTask = new EntityDeploySetLogicalNames();
entityDeploySetLogicalTask.Sources = Directory.EnumerateFiles("C:\Temp", "*.*", SearchOption.AllDirectories)
.Select(f => new TaskItem(f)).ToArray();
entityDeploySetLogicalTask.ResourceOutputPath = "C:\Temp"; // path to the intermediate folder
entityDeploySetLogicalTask.BuildEngine = engine;
entityDeploySetLogicalTask.Execute();
foreach(var resourceFile in entityDeploySetLogicalTask.ResourcesToEmbed)
{
var fileName = resourceFile.GetMetadata("Identity");
var logicalName = resourceFile.GetMetadata("LogicalName");
//TODO: Embed filename using logicalName in the output assembly
//You can embed them as normal resources by passing /resource to csc.exe
//eg. /resource:obj\Debug\edmxResourcesToEmbed\Models\SampleEF.csdl,Models.SampleEF.csdl
}
//TODO: call EntityClean or just remove all files from the intermediate directory
Try the following:
var mockObject = new Mock<IBuildEngine>();
IBuildEngine engine = mockObject.Object;
var entityDeployTask = new EntityDeploy();
entityDeployTask.Sources = new ITaskItem[]
{
new TaskItem(@"path to edmx\Model1.edmx")
};
entityDeployTask.OutputPath = @"C:\";
entityDeployTask.BuildEngine = engine;
entityDeployTask.Execute();
The output path doesn't seem to be picked up, but if it's empty then an error is logged. You can see this if you implement your own IBuildEngine
and log the errors. The result of the process will be three files next to the edmx: "Model1.ssdl", "Model1.csdl", "Model1.msdl". These files need to be passed to CSC as embedded resources, at least that's what the original targets file seems to do.
Hope it helps, and at least gets you started.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With