Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ResolveEventArgs.RequestingAssembly is null when AppDomain.CurrentDomain.AssemblyResolve is called

As part of our continuous integration efforts we've created a custom deployment application for handling Entity Framework 6.0 Code First database migrations. We take our target DLLs and compare those against the currently deployed DLLs to determine the migration path whether that path be upwards or downwards. In order to ensure we're loading the correct version of the data context's DLL we're listening on the AppDomain.CurrentDomain.AssemblyResolve (see: Loading Dependent Assemblies Manually).

using System;
using System.Collections.Concurrent;
using System.IO;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;

public enum MigrationsSource
{
    Target = 1,
    Deployed= 2
}

public class AssemblyLoader
{
    private readonly ConcurrentDictionary<string, MigrationsSource> m_Sources = new ConcurrentDictionary<string, MigrationsSource>();
    private string m_DeployedPath;
    private string m_TargetPath;

    public AssemblyLoader()
    {
        AppDomain.CurrentDomain.AssemblyResolve += ResolveDependentAssembly;
    }

    ~AssemblyLoader()
    {
        AppDomain.CurrentDomain.AssemblyResolve -= ResolveDependentAssembly;
    }

    private Assembly ResolveDependentAssembly(object sender, ResolveEventArgs args)
    {
        MigrationsSource source;

        if (m_Sources.TryGetValue(BuildAssemblyId(args.RequestingAssembly), out source))
        {
            var assemblyName = new AssemblyName(args.Name);

            string targetPath = Path.Combine(
                source == MigrationsSource.Deployed ? m_DeployedPath : m_TargetPath, string.Format("{0}.dll", assemblyName.Name));

            assemblyName.CodeBase = targetPath;

            //We have to use LoadFile here, otherwise we won't load a differing
            //version, regardless of the codebase because only LoadFile
            //will actually load a *new* assembly if it's at a different path
            //See: http://msdn.microsoft.com/en-us/library/b61s44e8(v=vs.110).aspx
            var dependentAssembly = Assembly.LoadFile(assemblyName.CodeBase);
            m_Sources.TryAdd(BuildAssemblyId(dependentAssembly), source);

            return dependentAssembly;
        }

        return null;
    }

    private string BuildAssemblyId(Assembly assembly)
    {
        return
            Convert.ToBase64String(
            HashAlgorithm.Create("SHA1")
                .ComputeHash(UTF8Encoding.UTF8.GetBytes(
                    string.Format("{0}|{1}", assembly.FullName, assembly.Location))));
    }
}

This works fine when deploying a simple data context (a single table seeded with a single row) to a server running SQL Server 2012 (running Windows Server 2012), but the following error occurs when deploying the same simple data context to a server running SQL Server 2008 R2 (running Windows Server 2008 Standard):

Unhandled Exception: System.NullReferenceException: Object reference not set to an instance of an object.
   at EntityFramework.AssemblyLoader.BuildAssemblyId(Assembly assembly) in c:\EntityFramework\AssemblyLoader.cs:line 103
   at EntityFramework.AssemblyLoader.ResolveDependentAssembly(Object sender, ResolveEventArgs args) in c:\EntityFramework\AssemblyLoader.cs:line 42
   at System.AppDomain.OnAssemblyResolveEvent(RuntimeAssembly assembly, String assemblyFullName)
   at System.RuntimeTypeHandle.GetTypeByName(String name, Boolean throwOnError, Boolean ignoreCase, Boolean reflectionOnly, StackCrawlMarkHandle stackMark, IntPtr pPrivHostBinder, Boolean loadTypeFromPartialName, ObjectHandleOnStack type)
   at System.RuntimeTypeHandle.GetTypeByName(String name, Boolean throwOnError, Boolean ignoreCase, Boolean reflectionOnly, StackCrawlMark& stackMark, IntPtr pPrivHostBinder, Boolean loadTypeFromPartialName)
   at System.RuntimeType.GetType(String typeName, Boolean throwOnError, Boolean ignoreCase, Boolean reflectionOnly, StackCrawlMark& stackMark)
   at System.Type.GetType(String typeName, Boolean throwOnError)
   at System.Data.Entity.Infrastructure.DependencyResolution.ClrTypeAnnotationSerializer.Deserialize(String name, String value)
   at System.Data.Entity.Core.SchemaObjectModel.SchemaElement.CreateMetadataPropertyFromXmlAttribute(String xmlNamespaceUri, String attributeName, String value)
   at System.Data.Entity.Core.SchemaObjectModel.SchemaElement.AddOtherContent(XmlReader reader)
   at System.Data.Entity.Core.SchemaObjectModel.SchemaElement.ParseAttribute(XmlReader reader)
   at System.Data.Entity.Core.SchemaObjectModel.SchemaElement.Parse(XmlReader reader)
   at System.Data.Entity.Core.SchemaObjectModel.Schema.HandleEntityTypeElement(XmlReader reader)
   at System.Data.Entity.Core.SchemaObjectModel.Schema.HandleElement(XmlReader reader)
   at System.Data.Entity.Core.SchemaObjectModel.SchemaElement.ParseElement(XmlReader reader)
   at System.Data.Entity.Core.SchemaObjectModel.SchemaElement.Parse(XmlReader reader)
   at System.Data.Entity.Core.SchemaObjectModel.Schema.HandleTopLevelSchemaElement(XmlReader reader)
   at System.Data.Entity.Core.SchemaObjectModel.Schema.InternalParse(XmlReader sourceReader, String sourceLocation)
   at System.Data.Entity.Core.SchemaObjectModel.Schema.Parse(XmlReader sourceReader, String sourceLocation)
   at System.Data.Entity.Core.SchemaObjectModel.SchemaManager.ParseAndValidate(IEnumerable`1 xmlReaders, IEnumerable`1 sourceFilePaths, SchemaDataModelOption dataModel, AttributeValueNotification providerNotification, AttributeValueNotification providerManifestTokenNotification, ProviderManifestNeeded providerManifestNeeded, IList`1& schemaCollection)
   at System.Data.Entity.Core.SchemaObjectModel.SchemaManager.ParseAndValidate(IEnumerable`1 xmlReaders, IEnumerable`1 sourceFilePaths, SchemaDataModelOption dataModel, DbProviderManifest providerManifest, IList`1& schemaCollection)
   at System.Data.Entity.Core.Metadata.Edm.EdmItemCollection.LoadItems(IEnumerable`1 xmlReaders, IEnumerable`1 sourceFilePaths, SchemaDataModelOption dataModelOption, DbProviderManifest providerManifest, ItemCollection itemCollection, Boolean throwOnError)
   at System.Data.Entity.Core.Metadata.Edm.EdmItemCollection.Init(IEnumerable`1 xmlReaders, IEnumerable`1 filePaths, Boolean throwOnError)
   at System.Data.Entity.Core.Metadata.Edm.EdmItemCollection..ctor(IEnumerable`1 xmlReaders)
   at System.Data.Entity.Utilities.XDocumentExtensions.GetStorageMappingItemCollection(XDocument model, DbProviderInfo&providerInfo)
   at System.Data.Entity.Migrations.Infrastructure.EdmModelDiffer.Diff(XDocument sourceModel, XDocument targetModel, Lazy`1 modificationCommandTreeGenerator, MigrationSqlGenerator migrationSqlGenerator, String sourceModelVersion, String targetModelVersion)
   at System.Data.Entity.Migrations.DbMigrator.IsModelOutOfDate(XDocument model, DbMigration lastMigration)
   at System.Data.Entity.Migrations.DbMigrator.Upgrade(IEnumerable`1 pendingMigrations, String targetMigrationId, String lastMigrationId)
   at System.Data.Entity.Migrations.DbMigrator.UpdateInternal(String targetMigration)
   at System.Data.Entity.Migrations.DbMigrator.<>c__DisplayClassc.<Update>b__b()
   at System.Data.Entity.Migrations.DbMigrator.EnsureDatabaseExists(Action mustSucceedToKeepDatabase)
   at System.Data.Entity.Migrations.DbMigrator.Update(String targetMigration)
   at System.Data.Entity.Internal.DatabaseCreator.CreateDatabase(InternalContext internalContext, Func`3 createMigrator, ObjectContext objectContext)
   at System.Data.Entity.Internal.InternalContext.CreateDatabase(ObjectContext objectContext, DatabaseExistenceState existenceState)
   at System.Data.Entity.Database.Create(DatabaseExistenceState existenceState)
   at System.Data.Entity.CreateDatabaseIfNotExists`1.InitializeDatabase(TContext context)
   at System.Data.Entity.Internal.InternalContext.<>c__DisplayClassf`1.<CreateInitializationAction>b__e()
   at System.Data.Entity.Internal.InternalContext.PerformInitializationAction(Action action)
   at System.Data.Entity.Internal.InternalContext.PerformDatabaseInitialization()
   at System.Data.Entity.Internal.LazyInternalContext.<InitializeDatabase>b__4(InternalContext c)
   at System.Data.Entity.Internal.RetryAction`1.PerformAction(TInput input)
   at System.Data.Entity.Internal.LazyInternalContext.InitializeDatabaseAction(Action`1 action)
   at System.Data.Entity.Internal.LazyInternalContext.InitializeDatabase()
   at EntityFramework.DbDeploymentManager.HandleDatabaseInitialization(DatabaseEndpoint endpoint) in c:\EntityFramework\DbDeploymentManager.cs:line 185
   at EntityFramework.DbDeploymentManager.Deploy() in c:\EntityFramework\DbDeploymentManager.cs:line 67
   at EntityFramework.Deployer.Program.Main(String[] args) in c:\EntityFramework.Deployer\Program.cs:line 23

Some logging output revealed that BuildAssemblyId is throwing the NullReferenceException because the args.RequestingAssembly that's being passed in is null. The value in args.Name is the name of the DLL that contains our data context. This gets thrown between the creation of the context and the data seeding as the table is created, but empty.

The application was initial running on each machine independently. We've ruled out .NET Framework discrepancy by update each machine to .NET 4.5.1. Also, we ran the deployment application on the same machine; we used the machine that had SQL Server 2012 installed. Finally, both the deployment application and simple data context are referencing the same version of EntityFramework.

EDIT

It was suggested that the attribute on the table object could be the root of the problem.

[Table("dbo.tblCustomer")]
public class Customer
{
    public Guid Id { get; set; }
    public string Name { get; set; }
}

We removed the TableAttribute and while it still failed on SQL Server 2008 R2 the error became reproducible on SQL Server 2012.

like image 787
Doug G Avatar asked Sep 09 '14 23:09

Doug G


1 Answers

The adhesive bandage we've applied is to unregister our ResolveDependentAssembly event after the determination of target and deployed DLLs and before the database is initialized. This will work fine in our current situation because we're only ever deploying a data context to one environment.

Our long term fix is going to be creating separate app domains for the target and each deployment.

like image 192
Doug G Avatar answered Sep 20 '22 17:09

Doug G