我有一个应用程序,它在运行时动态创建新类型,创建该类型的对象并将它们插入类型为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
)后的结果:
答案 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];
}