暧昧鉴“的myType'当'的myType'是在运行时动态生成是在、暧昧、动态、myType

2023-09-06 10:56:22 作者:思念抹不去的记忆

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

I have an application which creates new types dynamically at run time, creates objects of that type and inserts them into a MongoDB database collection of type object. Using the shell I can see that the object is inserted correctly and the _t value is the correct name of the dynamically created class.

我试图检索使用从我的收藏对象 AsQueryable已同时将LINQ查询过滤结果为特定类型的对象。

I am trying to retrieve objects from my collection using AsQueryable while applying a LINQ query to filter the results to only objects of a specific type.

这工作得很好:

_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()

当类型为我的动态生成的类型(它正常工作的其他类型)。

when the type is my dynamically generated type (it works fine for other types).

另外这样做的工作:

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

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

but unfortunately it returns all the documents of that type from the database and then any LINQ queries are performed locally instead of at the database, which is not what I want.

下面是code我使用创建类型在运行时:

Here is the code I am using to create types at run time:

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);
}

全部code表现出这种行为:

Full code that exhibits this behavior:

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 =现金版本= 1 ):

推荐答案

从一些实验,我能够发现这个错误的原因。

From some experimentation I was able to find the cause of this error.

如果我创建动态类型只有一次,并使用相同的键入对象插入和检索,然后它工作正常。另外即使我创建键入在应用程序的不同执行的对象我每次插入或检索它工作正常,只要插入和检索发生。

If I create the dynamic type just once and use that same Type object for inserting and retrieving then it works fine. Also even if I create the Type object every time I insert or retrieve it works fine as long as the insert and retrieval occur during different executions of the application.

这表示该问题涉及到如何MongoDB中自动创建类注册。我想,到底是怎么回事的是,插入对象时MongoDB中创建一个类映射为键入对象的实例,然后当检索MongoDB是自动使用创建另一个类图新键入实例,并且存在不明确性,因为现在的MongoDB注册了2个不同的类映射这两者具有相同的名称。

This indicates that the problem is related to how MongoDB automatically creates class registrations. I think what is going on is that when inserting the object MongoDB creates a class map for that instance of the Type object, and then when retrieving MongoDB is creating another class map automatically using the new Type instance and there is ambiguity because now MongoDB has 2 different class maps registered which both have the same name.

我可以用的缓存类型对象来解决这个问题,这样只会每个动态创建的类型在运行时间1个实例:

I was able to fix this by using a cache of Type objects so that there will only by 1 instance of each dynamically created type during run time:

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];
}