QuantumHive
QuantumHive

Reputation: 5683

Unable to load the specified metadata resource Release vs Debug build

I've been scratching my head for hours with a peculiar issue in Entity Framework.

The following questions are not duplicates since their answers do not help me:

I have an aspnet core mvc app (v1.1) that references a .NET Framework 4.7 project (the Data Access Layer) with Entity Framework 6.1.3 installed as a NuGet package.

I'm using the designer so I have an .edmx file.
I'm getting the following exception thrown at my face in runtime:

System.Data.Entity.Core.MetadataException: Unable to load the specified metadata resource.
   at System.Data.Entity.Core.Metadata.Edm.MetadataArtifactLoaderCompositeResource.LoadResources(String assemblyName, String resourceName, ICollection`1 uriRegistry, MetadataArtifactAssemblyResolver resolver)
   at System.Data.Entity.Core.Metadata.Edm.MetadataArtifactLoaderCompositeResource.CreateResourceLoader(String path, ExtensionCheck extensionCheck, String validExtension, ICollection`1 uriRegistry, MetadataArtifactAssemblyResolver resolver)
   at System.Data.Entity.Core.Metadata.Edm.MetadataArtifactLoader.Create(String path, ExtensionCheck extensionCheck, String validExtension, ICollection`1 uriRegistry, MetadataArtifactAssemblyResolver resolver)
   at System.Data.Entity.Core.Metadata.Edm.MetadataCache.SplitPaths(String paths)
   at System.Data.Entity.Core.Common.Utils.Memoizer`2.<>c__DisplayClass2.<Evaluate>b__0()
   at System.Data.Entity.Core.Common.Utils.Memoizer`2.Result.GetValue()
   at System.Data.Entity.Core.Common.Utils.Memoizer`2.Evaluate(TArg arg)
   at System.Data.Entity.Core.Metadata.Edm.MetadataCache.GetArtifactLoader(DbConnectionOptions effectiveConnectionOptions)
   at System.Data.Entity.Core.Metadata.Edm.MetadataCache.GetMetadataWorkspace(DbConnectionOptions effectiveConnectionOptions)
   at System.Data.Entity.Core.EntityClient.EntityConnection.GetMetadataWorkspace()
   at System.Data.Entity.Core.Objects.ObjectContext.RetrieveMetadataWorkspaceFromConnection()
   at System.Data.Entity.Core.Objects.ObjectContext..ctor(EntityConnection connection, Boolean isConnectionConstructor, ObjectQueryExecutionPlanFactory objectQueryExecutionPlanFactory, Translator translator, ColumnMapFactory columnMapFactory)
   at System.Data.Entity.Internal.InternalConnection.CreateObjectContextFromConnectionModel()
   at System.Data.Entity.Internal.LazyInternalConnection.CreateObjectContextFromConnectionModel()
   at System.Data.Entity.Internal.LazyInternalContext.InitializeContext()
   at System.Data.Entity.Internal.InternalContext.GetEntitySetAndBaseTypeForType(Type entityType)
   at System.Data.Entity.Internal.Linq.InternalSet`1.Initialize()
   at System.Data.Entity.Internal.Linq.InternalSet`1.GetEnumerator()
   at System.Data.Entity.Infrastructure.DbQuery`1.System.Collections.Generic.IEnumerable<TResult>.GetEnumerator()
   at System.Linq.Enumerable.WhereSelectEnumerableIterator`2.MoveNext()
   at System.Linq.Buffer`1..ctor(IEnumerable`1 source)
   at System.Linq.Enumerable.ToArray[TSource](IEnumerable`1 source)

And I have the following connectionstring:

metadata=res://*/MyContext.csdl|res://*/MyContext.ssdl|res://*/MyContext.msl;provider=System.Data.SqlClient;provider connection string="data source=.;initial catalog=MyDatabase;integrated security=True;multipleactiveresultsets=True;App=EntityFramework"

The thing is, when running in a Debug build, the application runs without any problems. However when running in a Release build, the exception gets thrown. But if I disable optimize code on the project with the .edmx file, the exception is not thrown anymore.

I've even looked in the Entity Framework source code. You can see from the Stacktrace, that this exception gets thrown at line 170, because loaders.Count == 0. I don't understand why the resources can't be loaded from the assembly in a Release build, while it works in a Debug build.

EDIT I just installed a trial version of Reflector to inspect the assembly. So when looking into the .dll file that has been built with the Debug configuration, I can clearly see the 3 resource files embedded. However, in the assembly that has been built with a Release configuration, oddly the resource files are missing!

Upvotes: 4

Views: 1381

Answers (1)

SuperPoney
SuperPoney

Reputation: 660

I've found a workaround for this problem.

I'm targeting a .net core 3.1 linux container with a legacy project using an EDMX. Migration of the whole EDMX in code first will take a lot of time so I had to find a solution to make it work in Azure.

Problem is that a dotnet cli command like dotnet publish does not embed csdl, msl, or ssdl files see issues #8932. So do not embed it in the output assembly. This is partially explained in this thread.

  1. First change the "Metadata artifact processing" property in the "Conceptual Entity Model" of the EDMX from "Embeded in Output Assembly" to "Copy to output directory" this picture show you how.

Note that you need to open the EDMX file (double click), since a single click will show you the property of the file.

  1. copy the csdl, msl, and ssdl files for publishing. I do that with a PostBuild event in the csproj file. Like this :
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
    <Exec Condition="'$(OS)' == 'Unix'" Command="echo *** cp -a $(ProjectDir)$(OutDir)* $(TargetDir)" />
    <Exec Condition="'$(OS)' == 'Unix'" Command="cp -a $(ProjectDir)$(OutDir)* $(TargetDir)" />

    <Exec Condition="'$(OS)' != 'Unix'" Command="echo *** copy /Y $(ProjectDir)$(OutDir)* $(TargetDir)" />
    <Exec Condition="'$(OS)' != 'Unix'" Command="copy /Y $(ProjectDir)$(OutDir)* $(TargetDir)" />
</Target>

Note that you'll have to adapt the copy command paths to match your solution (for example if your EDMX project is located in a sub-project).

  1. Finally change your connection string to a non embeded metadata (see doc here).

From

connectionString="metadata=res://*/Model.csdl|res://*/Model.ssdl|res://*/Model.msl;provider=System.Data.SqlClient;provider connection string=[...]"

To

connectionString="metadata=/app;provider=System.Data.SqlClient;provider connection string=[...]"

Since the folder change upon release/debug/publish I've created this small function in my application to change the connection string :

private string EntityConnectionString(string connectionString)
{
    EntityConnectionStringBuilder csb = new EntityConnectionStringBuilder
    {
        ProviderConnectionString = connectionString,
        Metadata = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location)
    };
    csb.Provider = "System.Data.SqlClient";
    return csb.ConnectionString;
}

Just provide the connection string in the classical non EDMX format to the connectionString parameter of the function. ie :

data source=[...];initial catalog=EntityframeworkTest;integrated security=True;MultipleActiveResultSets=True;App=EntityFramework

Upvotes: 2

Related Questions