【问题标题】:Is it possible to retrieve a MetadataWorkspace without having a connection to a database?是否可以在不连接数据库的情况下检索 MetadataWorkspace?
【发布时间】:2015-02-24 18:25:28
【问题描述】:

我正在编写一个测试库,它需要针对给定的DbContext 类型遍历实体框架MetadataWorkspace。但是,由于这是一个测试库,我宁愿没有与数据库的连接 - 它引入了测试环境中可能不可用的依赖项。

当我尝试像这样获取MetadataWorkspace 的引用时:

var metadata = ((IObjectContextAdapter)context).ObjectContext.MetadataWorkspace;

我收到SqlException

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

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

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

【问题讨论】:

  • 如何实例化context
  • @GertArnold 我只是在使用无参数构造函数

标签: c# entity-framework


【解决方案1】:

是的,您可以通过为上下文提供一个虚拟连接字符串来做到这一点。请注意,通常当您调用 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);                
}

所以你只提供获取元数据工作空间的重要参数,并提供空连接字符串。

更新:经过深思熟虑,您根本不需要使用这些技巧和实例化上下文。

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});
}

在这里,您只需直接使用 MetadataWorkspace 类的构造函数,将路径传递给目标元数据元素以及要检查的程序集。请注意,此方法做了一些假设:元数据工件被嵌入到资源中(通常它们是,但可以是外部的,或嵌入在其他路径下)并且所需的一切都与 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();
        }
    }

【讨论】:

  • 这看起来很有希望,但我收到 MetadataException:“无法加载指定的元数据资源。” - 完整的堆栈跟踪here。有什么想法吗?
  • 使用上述哪种方法?
  • 对不起 - 使用任何一种方法。 EF6 数据库优先,如果有帮助的话
  • 是的,我在 EF6 Database First 模型上对此进行了测试,所以应该可以。您确定您设置了正确的型号名称吗?这通常是您的 edmx 文件的名称(我的意思是第二种方法,它似乎更可靠)。
  • 无处可去,基本上是通过探索 MetadataWorkspace 构造函数接受的内容 :) 但在许多情况下,反编译器有助于检查库作者如何做到这一点。我使用 JetBrains 反编译器,它是免费的,可以集成到 Visual Studio 中。因此,当您执行 Go To Definition 时 - 它会即时反编译代码,从而可以轻松检查外部源。
【解决方案2】:

Evk 提出的解决方案对我不起作用,因为 EdmxWriter.WriteEdmx 最终会连接到数据库。

这是我解决这个问题的方法:

var dbContextType = typeof(MyDbContext);
var dbContextInfo = new DbContextInfo(dbContextType, new DbProviderInfo(providerInvariantName: "System.Data.SqlClient", providerManifestToken: "2008"));
using (var context = dbContextInfo.CreateInstance() ?? throw new Exception($"Failed to create an instance of {dbContextType}. Does it have a default constructor?"))
{
    var workspace = ((IObjectContextAdapter)context).ObjectContext.MetadataWorkspace;
    // ... use the workspace ...
}

通过这种方式创建上下文,Entity Framework 在访问ObjectContext 属性时不会尝试连接到数据库。

请注意,您的 DbContext 类必须具有默认构造函数才能使此解决方案正常工作。

【讨论】:

  • 这很有帮助,但在我的情况下没有帮助,因为我们的模板生成的上下文确实包含一个默认构造函数,它使用连接字符串名称调用 base。但是,这有助于我了解如何轻松修改模板以支持这种方法。
  • 我没有像你说的那样检查它是否创建了连接,但对我来说,在调试或测试模式下调用 EdmxWriter.WriteEdmx 需要 30(!) 整秒。使用您的解决方案,完成测试需要 2 秒。进步很大,谢谢。不过,我将不得不做更多的测试,因为对于外键查找来说,它似乎仍然太昂贵了。另外,我也不得不弄乱我们上下文的默认构造函数并生成像@dhochee这样的模板
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-03-16
  • 1970-01-01
  • 1970-01-01
  • 2015-07-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多