I am writing a test library that needs to traverse the Entity Framework MetadataWorkspace
for a given DbContext
type. However, as this is a test library I would rather not have a connection to the database - it introduces dependencies that may not be available from the test environment.
When I try to get a reference to the MetadataWorkspace
like so:
var metadata = ((IObjectContextAdapter)context).ObjectContext.MetadataWorkspace;
I get a SqlException
:
An exception of type 'System.Data.SqlClient.SqlException' occurred in System.Data.dll but was not handled in user code
Additional information: A network-related or instance-specific error occurred while establishing a connection to SQL Server. The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server is configured to allow remote connections. (provider: SQL Network Interfaces, error: 26 - Error Locating Server/Instance Specified)
Is it possible to do what I want without a connection string?
Yes you can do this by feeding context a dummy connection string. Note that usually when you call parameterless constructor of DbContext, it will look for connection string with the name of your context class in app.config file of main application. If that is the case and you cannot change this behavior (like you don't own the source code of context in question) - you will have to update app.config with that dummy conneciton string (can be done in runtime too). If you can call DbContext constructor with connection string, then:
var cs = String.Format("metadata=res://*/{0}.csdl|res://*/{0}.ssdl|res://*/{0}.msl;provider=System.Data.SqlClient;provider connection string=\"\"", "TestModel");
using (var ctx = new TestDBEntities(cs)) {
var metadata = ((IObjectContextAdapter)ctx).ObjectContext.MetadataWorkspace;
// no throw here
Console.WriteLine(metadata);
}
So you providing only parameters important to obtain metadata workspace, and providing empty connection string.
UPDATE: after more thought, you don't need to use such hacks and instantiate context at all.
public static MetadataWorkspace GetMetadataWorkspaceOf<T>(string modelName) where T:DbContext {
return new MetadataWorkspace(new[] { $"res://*/{modelName}.csdl", $"res://*/{modelName}.ssdl", $"res://*/{modelName}.msl" }, new[] {typeof(T).Assembly});
}
Here you just use constructor of MetadataWorkspace class directly, passing it paths to target metadata elements and also assembly to inspect. Note that this method makes some assumptions: that metadata artifacts are embedded into resources (usually they are, but can be external, or embedded under another paths) and that everything needed is in the same assembly as Context class itself (you might in theory have context in one assembly and entity classes in another, or something). But I hope you get the idea.
UPDATE2: to get metadata workspace of code-first model is somewhat more complicated, because edmx file for that model is generated at runtime. Where and how it is generated is implementation detail. However, you can still get metadata workspace with some efforts:
public static MetadataWorkspace GetMetadataWorkspaceOfCodeFirst<T>() where T : DbContext {
// require constructor which accepts connection string
var constructor = typeof (T).GetConstructor(new[] {typeof (string)});
if (constructor == null)
throw new Exception("Constructor with one string argument is required.");
// pass dummy connection string to it. You cannot pass empty one, so use some parameters there
var ctx = (DbContext) constructor.Invoke(new object[] {"App=EntityFramework"});
try {
var ms = new MemoryStream();
var writer = new XmlTextWriter(ms, Encoding.UTF8);
// here is first catch - generate edmx file yourself and save to xml document
EdmxWriter.WriteEdmx(ctx, writer);
ms.Seek(0, SeekOrigin.Begin);
var rawEdmx = XDocument.Load(ms);
// now we are crude-parsing edmx to get to the elements we need
var runtime = rawEdmx.Root.Elements().First(c => c.Name.LocalName == "Runtime");
var cModel = runtime.Elements().First(c => c.Name.LocalName == "ConceptualModels").Elements().First();
var sModel = runtime.Elements().First(c => c.Name.LocalName == "StorageModels").Elements().First();
var mModel = runtime.Elements().First(c => c.Name.LocalName == "Mappings").Elements().First();
// now we build a list of stuff needed for constructor of MetadataWorkspace
var cItems = new EdmItemCollection(new[] {XmlReader.Create(new StringReader(cModel.ToString()))});
var sItems = new StoreItemCollection(new[] {XmlReader.Create(new StringReader(sModel.ToString()))});
var mItems = new StorageMappingItemCollection(cItems, sItems, new[] {XmlReader.Create(new StringReader(mModel.ToString()))});
// and done
return new MetadataWorkspace(() => cItems, () => sItems, () => mItems);
}
finally {
ctx.Dispose();
}
}
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