是否可以在没有数据库连接的情况下检索MetadataWorkspace?

Ale*_*ill 10 c# entity-framework

我正在编写一个测试库,需要遍历MetadataWorkspace给定DbContext类型的Entity Framework .但是,由于这是一个测试库,我宁愿不与数据库建立连接 - 它引入了测试环境可能无法提供的依赖关系.

当我试图获得类似的引用时MetadataWorkspace:

var metadata = ((IObjectContextAdapter)context).ObjectContext.MetadataWorkspace;
Run Code Online (Sandbox Code Playgroud)

我得到一个SqlException:

System.Data.dll中出现"System.Data.SqlClient.SqlException"类型的异常,但未在用户代码中处理

附加信息:建立与SQL Server的连接时发生与网络相关或特定于实例的错误.服务器未找到或无法访问.验证实例名称是否正确,以及SQL Server是否配置为允许远程连接.(提供程序:SQL网络接口,错误:26 - 查找指定的服务器/实例时出错)

没有连接字符串可以做我想做的事吗?

Evk*_*Evk 10

是的,您可以通过向上下文提供虚拟连接字符串来实现此目 请注意,通常在调用DbContext的无参数构造函数时,它将在主应用程序的app.config文件中查找具有上下文类名称的连接字符串.如果是这种情况并且您无法更改此行为(就像您没有相关上下文的源代码那样) - 您将不得不使用该虚拟连接字符串更新app.config(也可以在运行时完成).如果可以使用连接字符串调用DbContext构造函数,则:

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

因此,您只提供重要的参数来获取元数据工作空间,并提供空连接字符串.

更新:经过深思熟虑后,您根本不需要使用此类黑客并实例化上下文.

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

在这里,您只需直接使用MetadataWorkspace类的构造函数,将路径传递给目标元数据元素,并将程序集传递给inspect.请注意,此方法做出了一些假设:元数据工件嵌入到资源中(通常它们是,但可以是外部的,或嵌入在另一个路径下),并且所需的一切都与Context类本身在同一个程序集中(理论上你可能有)一个程序集中的上下文和另一个程序集中的实体类.但我希望你明白这个主意.

UPDATE2:获取代码优先模型的元数据工作空间有点复杂,因为该模型的edmx文件是在运行时生成的.它的生成位置和方式是实现细节.但是,您仍然可以通过一些努力获得元数据工作区:

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