Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Invoke EntityDeploy build task programmatically

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.

What I've tried:

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.

like image 311
JoshVarty Avatar asked Nov 27 '15 08:11

JoshVarty


2 Answers

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:

  1. EntityDeploySplit is invoked, it reads the .edmx files and determines whether the results from processing each one should be embedded in the target assembly or placed alongside.
  2. EntityDeploy is called on NonEmbeddingItems from 1., it splits the .edmx files and places the result in OutputPath
  3. EntityDeploy is called on EmbeddingItems from 1., it splits the .edmx files and places the result in EntityDeployIntermediateResourcePath
  4. EntityDeploySetLogicalNames is called on the files in 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)
  5. EntityClean is called to remove the intermediate files

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
like image 97
Andriy Svyryd Avatar answered Oct 18 '22 22:10

Andriy Svyryd


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.

like image 1
Tamas Avatar answered Oct 18 '22 22:10

Tamas