当'myType'在运行时动态生成时,模糊鉴别器'myType'

时间:2014-08-07 16:19:59

标签: c# .net linq mongodb mongodb-.net-driver

我有一个应用程序,它在运行时动态创建新类型,创建该类型的对象并将它们插入类型为object的MongoDB数据库集合中。使用shell我可以看到对象被正确插入,_t值是动态创建的类的正确名称。

我正在尝试使用AsQueryable从我的集合中检索对象,同时应用LINQ查询将结果过滤为仅特定类型的对象。

这很好用:

_collection.AsQueryable<object>();

虽然:

_collection.AsQueryable<object>().Where(t => t.GetType() == type);

抛出异常:

Ambiguous discriminator 'myType'
   at MongoDB.Bson.Serialization.BsonSerializer.LookupActualType(Type nominalType, BsonValue discriminator)
   at MongoDB.Bson.Serialization.Conventions.StandardDiscriminatorConvention.GetActualType(BsonReader bsonReader, Type nominalType)
   at MongoDB.Bson.Serialization.Serializers.ObjectSerializer.Deserialize(BsonReader bsonReader, Type nominalType, IBsonSerializationOptions options)
   at MongoDB.Driver.Internal.MongoReplyMessage`1.ReadBodyFrom(BsonBuffer buffer)
   at MongoDB.Driver.Internal.MongoReplyMessage`1.ReadFrom(BsonBuffer buffer)
   at MongoDB.Driver.Internal.MongoConnection.ReceiveMessage[TDocument](BsonBinaryReaderSettings readerSettings, IBsonSerializer serializer, IBsonSerializationOptions serializationOptions)
   at MongoDB.Driver.Operations.QueryOperation`1.GetFirstBatch(IConnectionProvider connectionProvider)
   at MongoDB.Driver.Operations.QueryOperation`1.Execute(IConnectionProvider connectionProvider)
   at MongoDB.Driver.MongoCursor`1.GetEnumerator()
   at MongoDB.Driver.Linq.IdentityProjector`1.GetEnumerator()
   at System.Linq.Enumerable.First[TSource](IEnumerable`1 source)
   at MongoDB.Driver.Linq.SelectQuery.<TranslateFirstOrSingle>b__a(IEnumerable source)
   at MongoDB.Driver.Linq.SelectQuery.Execute()
   at MongoDB.Driver.Linq.MongoQueryProvider.Execute(Expression expression)
   at MongoDB.Driver.Linq.MongoQueryProvider.Execute[TResult](Expression expression)
   at System.Linq.Queryable.First[TSource](IQueryable`1 source)
   at MongoDBTest.Program.RetreiveTransaction(String transactionType, Int32 version) in c:\projects\mrp\trunk\Source\POC\MongoDBTest\MongoDBTest\Program.cs:line 192
   at MongoDBTest.Program.DynamicDBExample() in c:\projects\mrp\trunk\Source\POC\MongoDBTest\MongoDBTest\Program.cs:line 163
   at MongoDBTest.Program.Main(String[] args) in c:\projects\mrp\trunk\Source\POC\MongoDBTest\MongoDBTest\Program.cs:line 28
   at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
   at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
   at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
   at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading.ThreadHelper.ThreadStart()

当类型是我的动态生成类型时(它适用于其他类型)。

这也有效:

_collection.FindAs<object>(Query.EQ("_t", type.Name)).AsQueryable();

但不幸的是,它从数据库中返回该类型的所有文档,然后任何LINQ查询都在本地执行而不是在数据库中执行,这不是我想要的。


以下是我在运行时用于创建类型的代码:

public static Type CompileResultType(string className, Dictionary<string, string> fields)
{
    TypeBuilder tb = GetTypeBuilder(className);
    ConstructorBuilder constructor = tb.DefineDefaultConstructor(MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName);

    foreach (var field in fields)
    {
        CreateProperty(tb, field.Key, Type.GetType(field.Value));
    }

    Type objectType = tb.CreateType();
    return objectType;
}

private static TypeBuilder GetTypeBuilder(string className)
{
    var an = new AssemblyName("DynamicClassAssembly");
    AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run);
    ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("MainModule");
    TypeBuilder tb = moduleBuilder.DefineType("DynamicClassNamespace."+className
                        , TypeAttributes.Public |
                        TypeAttributes.Class |
                        TypeAttributes.AutoClass |
                        TypeAttributes.AnsiClass |
                        TypeAttributes.BeforeFieldInit |
                        TypeAttributes.AutoLayout
                        , null);
    return tb;
}

private static void CreateProperty(TypeBuilder tb, string propertyName, Type propertyType)
{
    FieldBuilder fieldBuilder = tb.DefineField("_" + propertyName, propertyType, FieldAttributes.Private);

    PropertyBuilder propertyBuilder = tb.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, null);
    MethodBuilder getPropMthdBldr = tb.DefineMethod("get_" + propertyName, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, propertyType, Type.EmptyTypes);
    ILGenerator getIl = getPropMthdBldr.GetILGenerator();

    getIl.Emit(OpCodes.Ldarg_0);
    getIl.Emit(OpCodes.Ldfld, fieldBuilder);
    getIl.Emit(OpCodes.Ret);

    MethodBuilder setPropMthdBldr =
        tb.DefineMethod("set_" + propertyName,
            MethodAttributes.Public |
            MethodAttributes.SpecialName |
            MethodAttributes.HideBySig,
            null, new[] { propertyType });

    ILGenerator setIl = setPropMthdBldr.GetILGenerator();
    Label modifyProperty = setIl.DefineLabel();
    Label exitSet = setIl.DefineLabel();

    setIl.MarkLabel(modifyProperty);
    setIl.Emit(OpCodes.Ldarg_0);
    setIl.Emit(OpCodes.Ldarg_1);
    setIl.Emit(OpCodes.Stfld, fieldBuilder);

    setIl.Emit(OpCodes.Nop);
    setIl.MarkLabel(exitSet);
    setIl.Emit(OpCodes.Ret);

    propertyBuilder.SetGetMethod(getPropMthdBldr);
    propertyBuilder.SetSetMethod(setPropMthdBldr);
}

表现出此行为的完整代码:

static void ProcessTransaction(IncomingTransaction input, string transactionType, int version)
{
    var configuration = GetConfiguration(transactionType, version);

    //configuration.Fields is just a Dictionary of field names -> types
    Type dynamicType = DynamicClassHelper.CompileResultType(transactionType + version, configuration.Fields);

    object transaction = Activator.CreateInstance(dynamicType);

    //AutoMapper, just populates the data on transaction object
    Mapper.DynamicMap(input, transaction, typeof(IncomingTransaction), transaction.GetType());

    //Just a wrapper around MongoDB, creates a MongoCollection<object>
    var db = new MongoTransactionDB<object>(connectionString, databaseName, "transactions", new ConsoleLogger());

    //just calls Insert() on the collection
    db.AddTransaction(transaction);
}

static void RetreiveTransaction(string transactionType, int version)
{
    var db = new MongoTransactionDB<object>(connectionString, databaseName, "transactions", new ConsoleLogger());

    var config = GetConfiguration(transactionType, version);

    Type dynamicType = DynamicClassHelper.CompileResultType(transactionType + version, config.Fields);

    //!!! This is where the exception is thrown !!!
    var result = db.GetAllTransactionsOfType(dynamicType).First();
}

//From MongoTransactionDB class...
public IQueryable<TTransactionBase> GetAllTransactionsOfType(Type type)
{
    return _collection.AsQueryable().Where(t => t.GetType() == type);
}

将动态对象插入MongoDB(transactionType = "Cash"version = 1)后的结果:

enter image description here

2 个答案:

答案 0 :(得分:2)

您始终可以使用Inject将mongo查询与LINQ结合使用,并让它们在MongoDB中运行,而不是在客户端中运行:

var queryable = _collection.AsQueryable().Where(_ => Query.EQ("_t", type.Name).Inject()).Where(...);
  

Inject是一种伪方法,用于将较低级别的MongoDB查询注入LINQ查询。

答案 1 :(得分:2)

通过一些实验,我找到了这个错误的原因。

如果我只创建一次动态类型并使用相同的Type对象进行插入和检索,那么它可以正常工作。即使我每次插入或检索它时都创建Type对象,只要在应用程序的不同执行期间发生插入和检索,它就可以正常工作。

这表明问题与MongoDB如何自动创建类注册有关。我想现在发生的事情是,当插入对象时,MongoDB为Type对象的该实例创建一个类映射,然后在检索MongoDB时使用新的Type实例自动创建另一个类映射,存在歧义,因为现在MongoDB已经注册了2个不同的类映射,它们都具有相同的名称。

我能够通过使用Type个对象的缓存来解决这个问题,这样在运行时期间每个动态创建的类型只会有1个实例:

static Type GetTransactionType(string transactionType, int version)
{
    string key = transactionType + version;

    if (!typeCache.ContainsKey(key))
    {
        var configuration = GetConfiguration(transactionType, version);

        Type dynamicType = DynamicClassHelper.CompileResultType(transactionType + version, configuration.Fields);

        typeCache.Add(key, dynamicType);
    }

    return typeCache[key];
}