Entity Framework 6: 无法加载指定的元数据资源

9

首先,这与SO上的另一个问题有关:

我已经阅读并调试了以下SO文章和博客:

MetadataException:无法加载指定的元数据资源

以及

http://blogs.teamb.com/craigstuntz/2010/08/13/38628/

但是...除了这个“修复”之外,我还有其他问题。

我有一个WebAPI(2.1),我的WebAPI中的连接字符串如下:

    <connectionStrings>
<add name="ProjectEntities" connectionString="
     metadata=res://*/ProjectModel.csdl|
     res://*/ProjectModel.ssdl|
     res://*/ProjectModel.msl;          
     provider=System.Data.SqlClient;          
     provider connection string=&quot;          
     data source=192.168.0.1;          
     initial catalog=Project;          
     persist security info=True;          
     user id=***;          
     password=***;          
     multipleactiveresultsets=True;          
     App=EntityFramework&quot;" 
     providerName="System.Data.EntityClient" />

当我在WebAPI中调用DbSetToList()方法时(伪代码):

DbContext _DbContext = new ProjectEntities();
DbSet<TEntity> _dbSet = _DbContext.Set<TEntity>();
_dbSet.ToList();

它非常好用!

当我从一个WINDOWS SERVICE中调用相同的内容时,我会得到以下错误: Error

连接字符串的app.config条目与web.config完全相同:

<connectionStrings>
<add name="ProjectEntities" connectionString="
     metadata=res://*/ProjectModel.csdl|
     res://*/ProjectModel.ssdl|
     res://*/ProjectModel.msl;          
     provider=System.Data.SqlClient;          
     provider connection string=&quot;          
     data source=192.168.0.1;          
     initial catalog=Project;          
     persist security info=True;          
     user id=***;          
     password=***;          
     multipleactiveresultsets=True;          
     App=EntityFramework&quot;" 
     providerName="System.Data.EntityClient" />

现在,博客显示手动引用dll的方法如下:
<connectionStrings>
    <add name="ProjectEntities" connectionString="
         metadata=res://Project.Data.dll/ProjectModel.csdl|
         res://Project.Data.dll/ProjectModel.ssdl|
         res://Project.Data.dll/ProjectModel.msl;          
         provider=System.Data.SqlClient;          
         provider connection string=&quot;          
         data source=192.168.0.1;          
         initial catalog=Project;          
         persist security info=True;          
         user id=***;          
         password=***;          
         multipleactiveresultsets=True;          
         App=EntityFramework&quot;" 
         providerName="System.Data.EntityClient" />
  </connectionStrings>

这种方法无法解决问题。

我能够解决它的唯一方式是使用完全限定名称:

<connectionStrings>
    <add name="ProjectEntities" connectionString="
         metadata=res://Project.Data, Version=1.6.0.0, Culture=neutral, PublicKeyToken=null/ProjectModel.csdl|
         res://Project.Data, Version=1.6.0.0, Culture=neutral, PublicKeyToken=null/ProjectModel.ssdl|
         res://Project.Data, Version=1.6.0.0, Culture=neutral, PublicKeyToken=null/ProjectModel.msl;          
         provider=System.Data.SqlClient;          
         provider connection string=&quot;          
         data source=192.168.250.125\sqlexpress;          
         initial catalog=Project;          
         persist security info=True;          
         user id=***;          
         password=***;          
         multipleactiveresultsets=True;          
         App=EntityFramework&quot;" 
         providerName="System.Data.EntityClient" />
  </connectionStrings>

为什么会有这种情况?为什么在Web项目中可以工作,但在Windows服务项目中不能?我最近从EF5更改到EF6,然后这个错误弹出来了——在升级EF之前,所有的代码都能正常工作。是否有人了解为什么以及如何/是否可以在我的连接字符串中使用*作为dll名称?
我以为是服务.exe所在的位置有问题,没有将文件复制到本地,但不,Project.Data.dll在那里,并且是正确的版本。
我使用FusionLog尝试找到错误,但没有运气。我很困惑。

你的Windows服务是否在不同的凭据下运行?它们是否具有连接到SQL服务器所需的适当权限? - cadsjo
尝试将连接字符串嵌入代码中,以查看是文件本身的问题还是连接本身的问题。 - jamesSampica
4个回答

5

为什么会出现这种情况?

您遇到的问题,只是额外安全措施的结果,以防止在将应用程序作为Windows服务运行时发生二进制种植或DLL劫持攻击(了解更多)。

我为什么要在意?

正如您所知,每个引用的DLL文件被查找的顺序都有一个特定的、经过充分记录的顺序。通常它从当前应用程序目录开始搜索DLL,然后转移到更“公共”的位置,例如PATH文件夹、GAC等。

二进制种植的主要思想是在比合法DLL的文件夹更早的文件夹中植入恶意DLL文件。加载这样的恶意DLL将允许攻击者控制系统。

通常,Windows服务以提升的账户(例如本地系统、本地服务、网络服务等)运行,因此Windows服务是二进制种植攻击的好目标。

我能做什么?

微软已经采取了额外的预防措施来减少安全风险,这是有充分理由的。但您可以尝试解决您的问题。

1) 当前目录不是您期望的目录

Windows服务在系统文件夹中启动(通常类似于C:\Windows\System32)

好消息是解决方法非常简单。您只需要在服务启动时更改当前目录即可。

System.IO.Directory.SetCurrentDirectory(System.AppDomain.CurrentDomain.BaseDirectory);

请参考Phil Haack的博客文章

2)仔细阅读文档

根据EF文档,通配符有特殊含义,并且它限制了运行时查找DLL文件的位置:

如果在assemblyFullName中指定通配符(*),则Entity Framework运行时将按以下顺序搜索资源:

1)调用程序集。

2)引用的程序集。

3)应用程序bin目录中的程序集。

由于您的工作文件夹设置为系统文件夹,您的引用可能不在那里,因此EF可能会在错误的位置查找并未加载包含资源的程序集。

3)使用完全限定的程序集名称保持安全

虽然我不完全确定这一点并且没有测试过,但微软可能已经禁止Windows服务加载DLL而不提供完全限定的程序集名称,以降低注入恶意DLL文件的风险;

关于保护Windows服务的好文章在这里(特别是第5章)。

4)调试它!

EF6恰好是开源项目。这意味着您可以获得它的完整源代码并对其进行调试。您可以在CodePlex上找到该项目。


嗯,我很快就要测试这个,但它似乎正确地回答了问题。虽然我不知道为什么赏金点数却授予给别人了!!! - Steve Stokes
@SteveStokes 我收到了赏金分数,因为我的回答在赏金宽限期结束后是最高投票的。 "如果您在7天(加上宽限期)内未授予您的赏金,则将授予在赏金开始后得分至少为2的最高投票答案一半的赏金金额。" http://stackoverflow.com/help/bounty - Shaun Luttin
我现在明白了,但不幸的是这并不是最好的答案。 - Steve Stokes
使用Phil Haack的修复方法对我的aspnet core mvc应用程序作为Windows服务托管起了作用。 - QuantumHive

2

将包含ProjectEntities的dll复制到不同的路径,然后在服务项目中引用相同的dll。


你能再解释得更清楚一些吗?我愿意尝试任何事情。 - Steve Stokes

1
很抱歉,我无法重现您收到的错误,或回答为什么您需要更改metadata。但是,我了解到,对于EF连接字符串,Windows服务需要与WebApi不同的provider connection string
以下是重现您的错误的步骤。唯一的区别是我使用的是localdb而不是SQLExpress。
我的步骤产生的代码在线上GitHub上可以查看:https://github.com/bigfont/EntityFrameworkWindowsServiceWebApi
以下是这些步骤:

创建Web API项目

  1. 创建 ASP.NET Web API 2 空项目 (MyWebApi)
  2. 使用 NuGet, Install-Package EntityFramework -ProjectName MyWebApi
  3. 添加一个名为 MyProjectModel 的新 ADO.NET 实体数据模型。
  4. 添加一个名为 Entity1 的实体。
  5. 从模型生成数据库,将其命名为 MyProject,并使用 localdb。
  6. 在 (localdb)\v11.0 上运行 db 创建脚本
  7. 添加一个名为 ValuesController 的新 WebApi 控制器,其中包含一个查询数据库的 Get 方法。
  8. 通过在 Visual Studio 中运行并转到 localhost:123456/api/get 进行测试。

参见: https://msdn.microsoft.com/zh-cn/data/jj205424.aspx

创建 Windows 服务项目

  1. 创建Windows服务(MyWindowsService)。
  2. 使用NuGet,Install-Package EntityFramework -ProjectName MyWindowsService
  3. 添加一个名为MyProjectModel的新ADO.NET实体数据模型。
  4. 添加一个名为Entity1的实体。
  5. 使用localdb从模型生成数据库,将其命名为MyService。
  6. 在(localdb)\v11.0上运行数据库创建脚本。
  7. 在OnStart方法中添加一些查询数据库的代码。
  8. NT AUTHORITY\SYSTEM添加为localdb登录和MyService db用户。
  9. 通过安装、启动和写入文件进行测试:

PowerShell安装、启动和卸载

Release> installutil .\MyWindowsService.exe
Release> Start-Service MyService
Release> installutil .\MyWindowsService.exe /u

Windows服务中的localdb连接字符串

在Windows服务的连接字符串中,我无法使用(localdb)\v11.0。相反,我需要使用命名管道。 我通过以下命令行找到了该命名管道:

> SqlLocalDB.exe info v11.0

Name:               v11.0
Version:            11.0.2100.60
Shared name:
Owner:              MY_COMPUTER\Shaun.Luttin
Auto-create:        Yes
State:              Running
Last start time:    2015-04-09 5:54:34 PM
Instance pipe name: np:\\.\pipe\LOCALDB#1010101\tsql\query

使用实例管道名称的连接字符串如下所示。
  <connectionStrings>
    <add name="MyProjectModelContainer" 
         connectionString="
  metadata=
  res://*/MyProjectModel.csdl|
  res://*/MyProjectModel.ssdl|
  res://*/MyProjectModel.msl;
  provider=System.Data.SqlClient;
  provider connection string=&quot;
    data source=np:\\.\pipe\LOCALDB#4BCE6D95\tsql\query;
    initial catalog=MyService;
    Integrated Security=True;
    MultipleActiveResultSets=True;
    App=EntityFramework&quot;" 
         providerName="System.Data.EntityClient" />
  </connectionStrings>

而 WebApi 的连接字符串看起来像这样:

    <add name="MyProjectModelContainer" 
         connectionString="
  metadata=
  res://*/MyProjectModel.csdl|
  res://*/MyProjectModel.ssdl|
  res://*/MyProjectModel.msl;
  provider=System.Data.SqlClient;
  provider connection string=&quot;
    data source=(localdb)\v11.0;
    initial catalog=MyProject;
    integrated security=True;
    MultipleActiveResultSets=True;
    App=EntityFramework&quot;" 
         providerName="System.Data.EntityClient" />
  </connectionStrings>

另请参阅: http://www.connectionstrings.com/sql-server-2012/

需要在Windows服务中使用不同的连接字符串与WebApi项目相比是一个类似的问题。从Sql Server Management Studio、Visual Studio和WebApi中,我们可以通过调用数据源(localdb)\v.11来连接,然而从Web服务中,我们需要通过它的实例命名管道来调用。

这里有一个怀疑:可能计算机上有多个本地数据库(localdb)的实例,并且我们需要绝对指定要使用哪一个。不幸的是,这并不能回答为什么需要更改metadata的问题。

这是一个类似但不同的问题,因为您需要更改Entity Framework metadata,而我需要更改provider connection string。巧合吗?


当然 - 这不是正确的解决方案,也不应该被赏金奖励。我投了反对票,以便将正确答案置于首位 - Windows服务需要在连接字符串中包含DLL版本以获取元资源。 - Steve Stokes

-1
请按照以下步骤操作:
1.右键单击edmx文件,然后选择相关实体的“打开方式”。
2.选择xml编辑器并打开。
3.从上到下滚动.edmx xml文件,并查找任何错误标记。
4.如果您注意到错误,请修复它们。
5.重新构建解决方案,如果没有发现错误,则恭喜你 :)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接