2
votes

When I try to retrieve a document containing a list of entities whose types are found in a dynamically loaded DLL, I get a JSONSerialisation exception, which states that it cannot find the type in the dynamically loaded DLL: Could not load assembly 'Sandbox_One'

I am using RavenDB (build 2681) to store entities that are created using Roslyn, although I don't think that is an issue here. Each of the entities inherit a non-dynamic interface IEntity. The Raven Session is being created either side of a .NET MVC 4 Action (as per the Raven example) and I am able to save the dynamic entity into a list of IEntity.

When I try to load the list of entities back out of the document, I get the serialisation error. At the moment, I only have a single instance (Blue Eyes) of a single entity type (Customer).

Observations

  • In the database, the entity looks exactly as I would expect:
{
  "Entities": [
    {
      "$type": "Prometheus.Dynamic.Sandbox_One.Customer, Sandbox_One",
      "Name": "Customer",
      "Eye_colour": "Blue",
      "Age": 9.0,
      "Id": "b0937393-b1bf-4bcb-97d7-1aea7a96e881"
    }
  ]
}
  • At the point of Loading the entity, I check the AppDomain.CurrentDomain for the loaded assemblies and Prometheus.Dynamic.Sandbox_One with the Customer type is there.

  • The Realm object has a reference to the dynamically loaded Assembly and I am able to reflect types from it like so:

    var customer = Activator.CreateInstance(realm.Assembly.GetTypes().First());

  • The Assembly is loaded inside the MVC action, after the RavenDB session was started. Would that make a difference? I imagine not as I expect the JSON.net serializer is looking in the AppDomain.


Code and Exception

Full stack trace of the Exception:

[JsonSerializationException: Could not load assembly 'Sandbox_One'.]
   Raven.Imports.Newtonsoft.Json.Serialization.DefaultSerializationBinder.GetTypeFromTypeNameKey(TypeNameKey typeNameKey) in c:\Builds\RavenDB-Stable\Imports\Newtonsoft.Json\Src\Newtonsoft.Json\Serialization\DefaultSerializationBinder.cs:69
   Raven.Imports.Newtonsoft.Json.Utilities.ThreadSafeStore`2.AddValue(TKey key) in c:\Builds\RavenDB-Stable\Imports\Newtonsoft.Json\Src\Newtonsoft.Json\Utilities\ThreadSafeStore.cs:62
   Raven.Imports.Newtonsoft.Json.Serialization.DefaultSerializationBinder.BindToType(String assemblyName, String typeName) in c:\Builds\RavenDB-Stable\Imports\Newtonsoft.Json\Src\Newtonsoft.Json\Serialization\DefaultSerializationBinder.cs:119
   Raven.Imports.Newtonsoft.Json.Serialization.JsonSerializerInternalReader.ReadSpecialProperties(JsonReader reader, Type& objectType, JsonContract& contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue, Object& newValue, String& id) in c:\Builds\RavenDB-Stable\Imports\Newtonsoft.Json\Src\Newtonsoft.Json\Serialization\JsonSerializerInternalReader.cs:473

[JsonSerializationException: Error resolving type specified in JSON 'Prometheus.Dynamic.Sandbox_One.Customer, Sandbox_One'. Path 'Entities[0].$type'.]
   Raven.Imports.Newtonsoft.Json.Serialization.JsonSerializerInternalReader.ReadSpecialProperties(JsonReader reader, Type& objectType, JsonContract& contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue, Object& newValue, String& id) in c:\Builds\RavenDB-Stable\Imports\Newtonsoft.Json\Src\Newtonsoft.Json\Serialization\JsonSerializerInternalReader.cs:526
   Raven.Imports.Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue) in c:\Builds\RavenDB-Stable\Imports\Newtonsoft.Json\Src\Newtonsoft.Json\Serialization\JsonSerializerInternalReader.cs:344
   Raven.Imports.Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue) in c:\Builds\RavenDB-Stable\Imports\Newtonsoft.Json\Src\Newtonsoft.Json\Serialization\JsonSerializerInternalReader.cs:238
   Raven.Imports.Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateList(IWrappedCollection wrappedList, JsonReader reader, JsonArrayContract contract, JsonProperty containerProperty, String id) in c:\Builds\RavenDB-Stable\Imports\Newtonsoft.Json\Src\Newtonsoft.Json\Serialization\JsonSerializerInternalReader.cs:1132
   Raven.Imports.Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateList(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, Object existingValue, String id) in c:\Builds\RavenDB-Stable\Imports\Newtonsoft.Json\Src\Newtonsoft.Json\Serialization\JsonSerializerInternalReader.cs:572
   Raven.Imports.Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue) in c:\Builds\RavenDB-Stable\Imports\Newtonsoft.Json\Src\Newtonsoft.Json\Serialization\JsonSerializerInternalReader.cs:240
   Raven.Imports.Newtonsoft.Json.Serialization.JsonSerializerInternalReader.SetPropertyValue(JsonProperty property, JsonConverter propertyConverter, JsonContainerContract containerContract, JsonProperty containerProperty, JsonReader reader, Object target) in c:\Builds\RavenDB-Stable\Imports\Newtonsoft.Json\Src\Newtonsoft.Json\Serialization\JsonSerializerInternalReader.cs:692
   Raven.Imports.Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateObject(Object newObject, JsonReader reader, JsonObjectContract contract, JsonProperty member, String id) in c:\Builds\RavenDB-Stable\Imports\Newtonsoft.Json\Src\Newtonsoft.Json\Serialization\JsonSerializerInternalReader.cs:1593

[JsonSerializationException: Could not read value for property: Entities]
   Raven.Imports.Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateObject(Object newObject, JsonReader reader, JsonObjectContract contract, JsonProperty member, String id) in c:\Builds\RavenDB-Stable\Imports\Newtonsoft.Json\Src\Newtonsoft.Json\Serialization\JsonSerializerInternalReader.cs:1602
   Raven.Imports.Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue) in c:\Builds\RavenDB-Stable\Imports\Newtonsoft.Json\Src\Newtonsoft.Json\Serialization\JsonSerializerInternalReader.cs:368
   Raven.Imports.Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue) in c:\Builds\RavenDB-Stable\Imports\Newtonsoft.Json\Src\Newtonsoft.Json\Serialization\JsonSerializerInternalReader.cs:238
   Raven.Imports.Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize(JsonReader reader, Type objectType, Boolean checkAdditionalContent) in c:\Builds\RavenDB-Stable\Imports\Newtonsoft.Json\Src\Newtonsoft.Json\Serialization\JsonSerializerInternalReader.cs:164
   Raven.Imports.Newtonsoft.Json.JsonSerializer.DeserializeInternal(JsonReader reader, Type objectType) in c:\Builds\RavenDB-Stable\Imports\Newtonsoft.Json\Src\Newtonsoft.Json\JsonSerializer.cs:565
   Raven.Client.Document.InMemoryDocumentSessionOperations.ConvertToEntity(Type entityType, String id, RavenJObject documentFound, RavenJObject metadata) in c:\Builds\RavenDB-Stable\Raven.Client.Lightweight\Document\InMemoryDocumentSessionOperations.cs:457
   Raven.Client.Document.InMemoryDocumentSessionOperations.TrackEntity(Type entityType, String key, RavenJObject document, RavenJObject metadata, Boolean noTracking) in c:\Builds\RavenDB-Stable\Raven.Client.Lightweight\Document\InMemoryDocumentSessionOperations.cs:404
   Raven.Client.Document.InMemoryDocumentSessionOperations.TrackEntity(Type entityType, JsonDocument documentFound) in c:\Builds\RavenDB-Stable\Raven.Client.Lightweight\Document\InMemoryDocumentSessionOperations.cs:388
   Raven.Client.Document.InMemoryDocumentSessionOperations.TrackEntity(JsonDocument documentFound) in c:\Builds\RavenDB-Stable\Raven.Client.Lightweight\Document\InMemoryDocumentSessionOperations.cs:343
   Raven.Client.Document.SessionOperations.LoadOperation.Complete() in c:\Builds\RavenDB-Stable\Raven.Client.Lightweight\Document\SessionOperations\LoadOperation.cs:61
   Raven.Client.Document.DocumentSession.Load(String id) in c:\Builds\RavenDB-Stable\Raven.Client.Lightweight\Document\DocumentSession.cs:230
   Prometheus.Core.DomainServices.DataManager.GetDataForRealm(IDocumentSession session, Realm realm) in c:\TeamProjectsCloud\Prometheus\Prometheus.Core\DomainServices\DataManager.cs:48
   Prometheus.Portal.Controllers.DataController.Index(String entityName) in c:\TeamProjectsCloud\Prometheus\Prometheus.Portal\Controllers\DataController.cs:23
   lambda_method(Closure , ControllerBase , Object[] ) +192
   System.Web.Mvc.ReflectedActionDescriptor.Execute(ControllerContext controllerContext, IDictionary`2 parameters) +274
   System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary`2 parameters) +39
   System.Web.Mvc.Async.c__DisplayClass39.b__33() +120
   System.Web.Mvc.Async.c__DisplayClass4f.b__49() +452
   System.Web.Mvc.Async.c__DisplayClass37.b__36(IAsyncResult asyncResult) +15
   System.Web.Mvc.Async.c__DisplayClass2a.b__20() +33
   System.Web.Mvc.Async.c__DisplayClass25.b__22(IAsyncResult asyncResult) +240
   System.Web.Mvc.c__DisplayClass1d.b__18(IAsyncResult asyncResult) +28
   System.Web.Mvc.Async.c__DisplayClass4.b__3(IAsyncResult ar) +15
   System.Web.Mvc.Controller.EndExecuteCore(IAsyncResult asyncResult) +53
   System.Web.Mvc.Async.c__DisplayClass4.b__3(IAsyncResult ar) +15
   System.Web.Mvc.c__DisplayClass8.b__3(IAsyncResult asyncResult) +42
   System.Web.Mvc.Async.c__DisplayClass4.b__3(IAsyncResult ar) +15
   System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +606
   System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +288

The controller (I've bolded the call where the Entities are loaded): NB RavenSession is inherited from a base controller exactly as in the MVC site example

public ActionResult Index(string entityName)
{
     var viewModel = new DataViewModel();
     var realm = realmManager.GetRealm(RavenSession, viewModel.SelectedRealm);
     viewModel.Entities = realmManager.GetEntities(realm);
     viewModel.CurrentEntity = viewModel.Entities.SingleOrDefault(x => x.Name == entityName);
     viewModel.Data = dataManager.GetDataForRealm(RavenSession, realm); // Call to loading method

     return View(viewModel);
}

The Load call that is throwing the exception:

    public List<IEntity> GetDataForRealm(IDocumentSession session, Realm realm)
    {
        var realmData = session.Load<RealmData>(realm.RealmDataId); // Exception thrown
        return realmData.Entities;
    }

The Realm Data class:

public class RealmData
{
    public RealmData()
    {
        Entities = new List<IEntity>();
    }
    public List<IEntity> Entities { get; set; }
}

Thank you in advance for your help!

1

1 Answers

2
votes

My solution was to implement an AssemblyResolve event handler on the App Domain.

The GetDataForRealm method becomes:

public List<IEntity> GetDataForRealm(IDocumentSession session, Realm realm)
    {
        AppDomain.CurrentDomain.AssemblyResolve += (s, a) => MyResolveEventHandler(s, a, realm.Assembly);

        var realmData = session.Load<RealmData>(realm.RealmDataId);
        return realmData.Entities;
    }

The event handler looks like this:

private Assembly MyResolveEventHandler(object sender, ResolveEventArgs args, Assembly assembly)
    {
        if (assembly.GetName().Name == args.Name)
        {
            return assembly;
        }
        return null;
    }

What I believe was happening:

When the JSON.Net deserializer was reflecting out the dynamic type, it could not find the Assembly in the AppDomain.CurrentDomain. The handler is called when the AppDomain cannot find the assembly by name. My assembly was in the AppDomain (as I used Assembly.Load) but it could not be found.

Note: I extended MyResolveEventHandler with an extra parameter because I already had the Assembly I needed. If you don't have the Assembly in memory (but know where it is) then you can load it inside MyResovleEventHandler.

Update

You may find that you have problems assigning data to these resultant objects later. This is because passing the Entity through the AppDomain does not mean that it changes the Domain of the object. Instead, it just hands a reference. Instead, I found that it was easier to get RavenDB to create the objects in the correct (temporary) Domain in the first place using a Custom JSON Converter that has the dynamic assembly in a member. See that solution here.