I use reflection to update objects which have had updates made to them and saved to mongodb
private void updateSelf(MongoDoc newDoc)
{
Type type = this.GetType();
foreach (var i in type.GetProperties())
{
if (i.GetCustomAttributes(false).Any(x => x is MongoDB.Bson.Serialization.Attributes.BsonIgnoreAttribute)) continue;
Object oldValue = i.GetValue(this, null);
Object newValue = i.GetValue(newDoc, null);
if (!Object.Equals(oldValue, newValue) && !((oldValue == null) && (newValue == null)))
{
i.SetValue(this, newValue, null);
}
}
}
this is working for the most part but the i.SetValue(this, newValue, null); throws an exception when trying to update this property:
public uint Revision { get; private set; }
this is trying to update an object of type Product which is a derived type of MongoDoc which contains the property public uint Revision { get; private set; } which is causing the exception Property set Method not found I'm not sure what is causing this because it works on all my other properties, just this one throws and exception. Any help much appreciated
UPDATE:
I have tried the answer below:
i.SetValue(this, newValue, System.Reflection.BindingFlags.SetProperty | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic, null, null, null);
but unfortunately the exact same result, it still throws the exception on the Revision property.
UPDATE:
Exception:
System.ArgumentException was unhandled
Message=Property set method not found.
Source=mscorlib
StackTrace:
at System.Reflection.RuntimePropertyInfo.SetValue(Object obj, Object value, BindingFlags invokeAttr, Binder binder, Object[] index, CultureInfo culture)
at System.Reflection.RuntimePropertyInfo.SetValue(Object obj, Object value, Object[] index)
at Flo.Client.Docs.MongoDoc.updateSelf(MongoDoc newDoc) in F:\Flo\Flo.Client\Docs\MongoDoc.cs:line 162
at Flo.Client.Docs.MongoDoc.UpdateToMongo(MongoDoc newDoc) in F:\Flo\Flo.Client\Docs\MongoDoc.cs:line 120
at Flo.Client.Docs.Product.EditProduct(String Name, Nullable`1 State) in F:\Flo\Flo.Client\Docs\Product.cs:line 89
at Flo.Client.Program.Main() in F:\Flo\Flo.Client\Program.cs:line 26
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.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()
InnerException:
I fixed it with this, thanks to Dylan Meador for pointing me to another question which gave me enough to get the solution:
private void updateSelf(MongoDoc newDoc, Type type)
{
foreach (var i in type.GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public))
{
if (i.GetCustomAttributes(false).Any(x => x is MongoDB.Bson.Serialization.Attributes.BsonIgnoreAttribute)) continue;
Object oldValue = i.GetValue(this, null);
Object newValue = i.GetValue(newDoc, null);
if (!Object.Equals(oldValue, newValue) && !((oldValue == null) && (newValue == null)))
{
i.SetValue(this, newValue, null);
}
}
Type baseType = type.BaseType;
if (baseType != null)
{
this.updateSelf(newDoc, baseType);
}
}
It looks like the Type needed to be explicitly set to the base class type in order to use the set accessor for that particular property.
Try using the overload of SetValue that has an System.Reflection.BindingFlags parameter and pass it a value of BindingFlags.SetProperty | BindingFlags.Public | BindingFlags.NonPublic.
The problem is that from the point of view of the derived type there is no setter for the property.
You can work around this walking the inheritance hierarchy and getting the properties declared on each type (using the DeclaredOnly BindingFlag, together with Public and Instance), or checking whether the property is declared by the reflection type and if not get it again from the property info's DeclaringType.
For the former you'd you could have a nested loop, while the latter would look something like this:
foreach (var property in p.GetType().GetProperties())
{
var actualProperty = property.DeclaringType != property.ReflectedType ? property.DeclaringType.GetProperty(property.Name) : property;
actualProperty.SetValue(p, newValue, null);
}
Probably the issue can be in next:
You are using auto property
public uint Revision { get; private set; }
Which perform boxing/unboxing operations through default type int.
So, here you should explicitly cast to uint type.
You can find out similar problem with next snippet:
byte b1 = 4;
byte b2 = 5;
byte sum = b1 + b2;
This will raise an exception since no overload are declared for "+" operator.
This snippet actually demonstrate an issue with boxing/unboxing operations through default type int.
Related
How can i get around casting to FieldInfo? With the code below it throws InvalidCastException when derived class has i.e. bool variable.
The problem is that it also returns null as a value of field. (everything happens in last 5 lines, but i paste more for context)
{
string paramsData = ".";
if (param == null)
{
return paramsData;
}
BindingFlags bindingFlags = BindingFlags.Public |
BindingFlags.NonPublic |
BindingFlags.Instance |
BindingFlags.Static;
FieldInfo[] paramFields = param.GetType().GetFields(bindingFlags);
foreach (FieldInfo field in paramFields)
{
paramsData += field.Name;
Param child = (Param)field.GetValue(param);
paramsData += GetParamDataInChildren(child);
}
return paramsData;
}
It is difficult to know what you are trying to achieve, but it looks like you should be checking the type of your field before calling GetValue. For example:
if (field.FieldType == typeof(Param))
{
Param child = (Param)field.GetValue(param);
paramsData += GetParamDataInChildren(child);
}
Note that child could still be null in this case, and you should check for that if the subsequent function doesn't handle it.
[EDIT]
If you are trying to recursively get all the fields, regardless of type, you should make your recursive function take Object rather than Param and then you won't need to cast to Param as GetValue returns Object.
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.
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.
This works fine:
_collection.AsQueryable<object>();
while this:
_collection.AsQueryable<object>().Where(t => t.GetType() == type);
throws the exception:
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).
Also this does work:
_collection.FindAs<object>(Query.EQ("_t", type.Name)).AsQueryable();
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.
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);
}
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);
}
Result after inserting the dynamic object into MongoDB (transactionType = "Cash" and version = 1):
You can always combine mongo queries with LINQ using Inject and have them run in MongoDB and not the client:
var queryable = _collection.AsQueryable().Where(_ => Query.EQ("_t", type.Name).Inject()).Where(...);
Inject is a pseudo-method that is used to inject a lower level MongoDB query into a LINQ query.
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.
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.
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];
}
I have the following code. However, it has a runtime exception on SetValue. What may cause the error?
var _filter = new Filter(....); // Filter implemented IFilter
ApplyFilter(_view.Name, x => x.Name);
private void ApplyFilter<T>(T curr, Expression<Func<IFilter, T>> prev)
{
var expr = (MemberExpression)prev.Body;
var prop = (PropertyInfo)expr.Member;
if (!EqualityComparer<T>.Default.Equals(curr, (T)_filter[prop.Name]))
{
prop.SetValue(_filter, curr, null); // Error
..... // do something on _filter
The exception is:
System.ArgumentException was unhandled
Message=Property set method not found.
Source=mscorlib
StackTrace:
at System.Reflection.RuntimePropertyInfo.SetValue(Object obj, Object value, BindingFlags invokeAttr, Binder binder, Object[] index, CultureInfo culture)
at System.Reflection.RuntimePropertyInfo.SetValue(Object obj, Object value, Object[] index)
at MyApp.ErrorLogPresenter.ApplyFilter[T](T curr, Expression`1 prev) in d:\....cs:line 50
Message=Property set method not found.
This usually simply means that the property you are using does not define a setter. Either ensure that a suitable setter exists, or use a different approach to assign values.
In WinRT when I call the count method in Queryable class for the IOrderedEnumerable instance it will throw the exception.
DoWork(emp); //Working fine
DoWork(emp.OrderBy(objects => objects.EmployeeId)); //throw exception..
public void DoWork(IEnumerable<object> collection)
{
var queryable = collection.AsQueryable();
int count = 0;
if (queryable != null)
count = queryable.Count();
else
throw new InvalidOperationException("Not able to get count");
//Some other operations using queryable...
}
Exception
System.InvalidOperationException was unhandled by user code
HResult=-2146233079
Message=The API 'System.Linq.OrderedEnumerable`2[[SfDataGrid.BusinessObjects, SfDataGrid, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null],[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]' cannot be used on the current platform. See http://go.microsoft.com/fwlink/?LinkId=248273 for more information.
Source=mscorlib
StackTrace:
at System.Reflection.Emit.DynamicILGenerator.GetTokenFor(RuntimeType rtType)
at System.Reflection.Emit.DynamicILGenerator.Emit(OpCode opcode, Type type)
at System.Linq.Expressions.Compiler.BoundConstants.EmitConstantFromArray(LambdaCompiler lc, Object value, Type type)
at System.Linq.Expressions.Compiler.BoundConstants.EmitConstant(LambdaCompiler lc, Object value, Type type)
at System.Linq.Expressions.Compiler.LambdaCompiler.EmitConstant(Object value, Type type)
at System.Linq.Expressions.Compiler.LambdaCompiler.EmitConstantExpression(Expression expr)
at System.Linq.Expressions.Compiler.LambdaCompiler.EmitExpression(Expression node, CompilationFlags flags)
at System.Linq.Expressions.Compiler.LambdaCompiler.EmitArguments(MethodBase method, IArgumentProvider args, Int32 skipParameters)
at System.Linq.Expressions.Compiler.LambdaCompiler.EmitMethodCall(MethodInfo mi, IArgumentProvider args, Type objectType, CompilationFlags flags)
at System.Linq.Expressions.Compiler.LambdaCompiler.EmitMethodCall(Expression obj, MethodInfo method, IArgumentProvider methodCallExpr, CompilationFlags flags)
at System.Linq.Expressions.Compiler.LambdaCompiler.EmitMethodCallExpression(Expression expr, CompilationFlags flags)
at System.Linq.Expressions.Compiler.LambdaCompiler.EmitExpression(Expression node, CompilationFlags flags)
at System.Linq.Expressions.Compiler.LambdaCompiler.EmitLambdaBody(CompilerScope parent, Boolean inlined, CompilationFlags flags)
at System.Linq.Expressions.Compiler.LambdaCompiler.EmitLambdaBody()
at System.Linq.Expressions.Compiler.LambdaCompiler.Compile(LambdaExpression lambda, DebugInfoGenerator debugInfoGenerator)
at System.Linq.EnumerableExecutor`1.Execute()
at System.Linq.EnumerableQuery`1.System.Linq.IQueryProvider.Execute[S](Expression expression)
at System.Linq.Queryable.Count[TSource](IQueryable`1 source)
at SfDataGrid.ViewModel.GetCount(IEnumerable`1 collection) in e:\DiskD\Support\I108101\SfDataGrid1022101733\SfDataGrid\SfDataGrid\ViewModel\ViewModel.cs:line 39
at SfDataGrid.ViewModel..ctor() in e:\DiskD\Support\I108101\SfDataGrid1022101733\SfDataGrid\SfDataGrid\ViewModel\ViewModel.cs:line 27
at SfDataGrid.SfDataGrid_XamlTypeInfo.XamlTypeInfoProvider.Activate_0_ViewModel() in e:\DiskD\Support\I108101\SfDataGrid1022101733\SfDataGrid\SfDataGrid\obj\Debug\XamlTypeInfo.g.cs:line 123
at SfDataGrid.SfDataGrid_XamlTypeInfo.XamlUserType.ActivateInstance() in e:\DiskD\Support\I108101\SfDataGrid1022101733\SfDataGrid\SfDataGrid\obj\Debug\XamlTypeInfo.g.cs:line 3679
It seems that IOrderedEnumerable<T>.AsQueryable() is implemented using dynamic code generation which is not available in WinRT. You could work around that by materializing it in a list before calling AsQueryable():
public void DoWork(IEnumerable<object> collection)
{
var queryable = collection.ToList().AsQueryable();
int count = 0;
if (queryable != null)
count = queryable.Count();
else
throw new InvalidOperationException("Not able to get count");
//Some other operations using queryable...
}
Unless you're working with very large collections performance shouldn't be much worse.
You must use IOrderable<object> instead of IEnumerable<object> as param for DoWork Method
IOrderable:
http://msdn.microsoft.com/en-us/library/microsoft.visualstudio.utilities.iorderable.aspx
IEnumerable:
http://msdn.microsoft.com/en-us/library/system.collections.ienumerable(v=vs.100).aspx
I'm having some issues with invoking an overloaded static method with an out parameter via reflection and would appreciate some pointers.
I'm looking to dynamically create a type like System.Int32 or System.Decimal, and then invoke the static TryParse(string, out x) method on it.
The below code has two issues:
t.GetMethod("TryParse", new Type[] { typeof(string), t } ) fails to return the MethodInfo I expect
mi.Invoke(null, new object[] { value.ToString(), concreteInstance }) appears to succeed but doesn't set the out param concreteInstance to the parsed value
Interwoven into this function you can see some temporary code demonstrating what should happen if the type parameter was set to System.Decimal.
public static object Cast(object value, string type)
{
Type t = Type.GetType(type);
if (t != null)
{
object concreteInstance = Activator.CreateInstance(t);
decimal tempInstance = 0;
List<MethodInfo> l = new List<MethodInfo>(t.GetMethods(BindingFlags.Static | BindingFlags.Public));
MethodInfo mi;
mi = t.GetMethod("TryParse", new Type[] { typeof(string), t } ); //this FAILS to get the method, returns null
mi = l.FirstOrDefault(x => x.Name == "TryParse" && x.GetParameters().Length == 2); //ugly hack required because the previous line failed
if (mi != null)
{
try
{
bool retVal = decimal.TryParse(value.ToString(), out tempInstance);
Console.WriteLine(retVal.ToString()); //retVal is true, tempInstance is correctly set
object z = mi.Invoke(null, new object[] { value.ToString(), concreteInstance });
Console.WriteLine(z.ToString()); //z is true, but concreteInstance is NOT set
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
}
return concreteInstance;
}
return value;
}
What do I need to do to ensure that my t.GetMethod() call returns the correct MethodInfo? What do I need to do to have concreteInstance correctly set in my mi.Invoke() call?
I know there are a bunch of questions on this topic, but most of them involve static generic methods or static methods that are not overloaded. This question is similar but not a duplicate.
You need to use the right BindingFlags and use Type.MakeByRefType for out and ref parameters. One second, and I'll have a code sample for you.
For example,
MethodInfo methodInfo = typeof(int).GetMethod(
"TryParse",
BindingFlags.Public | BindingFlags.Static,
Type.DefaultBinder,
new[] { typeof(string), typeof(int).MakeByRefType() },
null
);
I should point out that invoking this is a little tricky too. Here's how you do it.
string s = "123";
var inputParameters = new object[] { "123", null };
methodInfo.Invoke(null, inputParameters);
Console.WriteLine((int)inputParameters[1]);
The first null is because we are invoking a static method (there is no object "receiving" this invocation). The null in inputParameters will be "filled" for us by TryParse with the result of the parse (it's the out parameter).