I have a table in an MSSQL database with ~300 columns and I want to extract only one row and get all the values of the columns. I've used this code but I have problems with GetValue(,) method. This is my code:
private PropertyInfo[] GetValuesDB()
{
......
var result = from val in datacTx.TableA
where val.A == "AA" + "-" + "11" &&
val.B == "CC
select val;
return result.First().GetType().GetProperties();
}
...
public void MethodA()
{
var res = GetValuesDB();
foreach (var propertyInfo in res)
{
var rez = propertyInfo.GetValue(res,null);
}
}
I always obtain this kind of exception:
Object does not match target type.
GetValue expects the type of the first argument to be the type declaring the property (or a subtype).
So your result.First() call returns a reference to an object - and you want to get the properties on that object... so that should be the reference you pass to GetValue. You'll need to change your code to return that reference:
// Ideally, change this to a more appropriate return type...
private object GetValuesDB()
{
......
var result = from val in datacTx.TableA
where val.A == "AA" + "-" + "11" &&
val.B == "CC"
select val;
return result.First();
}
...
public void MethodA()
{
var res = GetValuesDB();
foreach (var propertyInfo in res.GetType().GetProperties())
{
var rez = propertyInfo.GetValue(res, null);
}
}
So GetValuesDB now returns a reference to the relevant entity - and then you fetch the property descriptors, and ask each descriptor for the value of that property on that object.
Related
I understand how to use Reflection to get the property name and value of a given class. But what if that property is an array containing 1 or more of another class?
var person = new
{
Name = "John",
Characteristic = new[]
{
new Characteristic {Name = "Age", Value = "31"},
new Characteristic {Name = "Height", Value = "6'1""},
}
};
var properties = person.GetType().GetProperties();
foreach (var property in properties)
{
Console.WriteLine(property.Name);
Console.WriteLine(property.GetValue(person));
if (property.PropertyType.IsArray)
{
// Need to get info here! (See below)
}
}
Basically, I need to get that the particular property is an array of Characteristic and then the properties Name and Value and then the subsequent values of those properties. Thanks very much!
**edit: this is what I tried and I couldn't get the values I needed...this code was in place of the comment above
foreach (var prop in property.GetType().GetProperties())
{
Console.WriteLine(prop.Name);
Console.WriteLine(prop.GetValue(property));
}
You still utilize GetValue() but for array properties it returns an array cast to object so you need to cast it back to Array explicitly
if (property.PropertyType.IsArray)
{
foreach (var item in (Array)property.GetValue(person))
Console.WriteLine(item);
}
If you need to access the item's properties as well, repeat the same property scammin routine to the item objects:
if (property.PropertyType.IsArray)
{
foreach (var item in (Array)property.GetValue(person))
{
var subProperties = item.GetType().GetProperties();
foreach (var subProperty in subProperties)
{
Console.WriteLine(subProperty.Name);
Console.WriteLine(subProperty.GetValue(item));
}
}
}
And more generic version processing arbitrary levels of nested array properties:
TraverseProps(person);
// ..................................................
static void TraverseProps(object obj, int level = 0)
{
string indent = new string(' ', level * 2);
if (obj == null)
{
Console.WriteLine(indent + "<NULL>");
return;
}
var properties = obj.GetType().GetProperties();
foreach (var property in properties)
{
var value = property.GetValue(obj);
Console.Write(indent + property.Name + ": ");
if (property.PropertyType.IsArray)
{
Console.WriteLine(indent + "[");
int i = 0;
foreach (var item in (Array)value)
{
Console.WriteLine(indent + "item " + i + ":");
TraverseProps(item, level + 1);
i++;
}
Console.WriteLine(indent + "]");
}
else
{
Console.WriteLine(value);
}
}
}
N.B. This function is prone to infinite recursion in case of reference loops in the object tree.
After you retrieve the array instance (using GetValue as normal), you can use LINQ's Cast() method to convert it to an IEnumerable of the appropriate type, like this:
var characteristics = person.GetType().GetProperty("Characteristic", BindingFlags.Instance | BindingFlags.Public).GetValue(person, null) as System.Array;
foreach (var c in characteristics.Cast<Characteristic>())
{
Console.WriteLine("{0} = {1}", c.Name, c.Value);
}
If you don't have early-bound access to the Characteristic class, you can also use pure reflection:
var characteristics = person.GetType().GetProperty("Characteristic", BindingFlags.Instance | BindingFlags.Public).GetValue(person, null) as System.Array;
foreach (var o in characteristics)
{
var name = o.GetType().GetProperty("Name", BindingFlags.Public | BindingFlags.Instance).GetValue(o, null);
var val = o.GetType().GetProperty("Value", BindingFlags.Public | BindingFlags.Instance).GetValue(o, null);
Console.WriteLine("{0} = {1}", name, val);
}
The output in both cases is:
Age = 31
Height = 6'1"
Link to DotNetFiddle demo
Here is what i came up with that works. This would have been in the if statement above:
if (property.PropertyType.IsArray)
{
foreach (var item in (Array) property.GetValue(person))
{
var props = item.GetType().GetProperties();
foreach (var prop in props)
{
Console.WriteLine(prop.Name);
Console.WriteLine(prop.GetValue(item));
}
}
}
I have the following Method (I exemplify what I need in the comments of the method):
public static Dictionary<int, int> Foo(bool os, bool rad, bool aci, bool outr, string distrito = null)
{
if (os == false && rad == false && aci == false && outr == false)
{
return new Dictionary<int, int>();
}
var parameters = MethodBase.GetCurrentMethod().GetParameters();
foreach (ParameterInfo parameter in parameters)
{
// I would love if parameter.Value existed, because:
// if (parameter.Value==true) {
// x++
// if (x == 1) string s = "true" + parameter.Name;
// if (x > 1) s += "Additional true" + parameter.Name;
// }
// s += "End";
}
return null;
}
I have to know if one or more values of the bool parameters are true. Since they are four, imagine the combination of if I would have to do to check if only one is true, or if more than one, which are true.
So, how can I cycle the current value of the incoming Method parameters without using the parameter variable itself?
If you only want to know how many are true, you can turn them into integers and add their values:
var values = new[] {os, rad, aci, outr};
var sum = values.Select(v => Convert.ToInt32(v)).Sum();
If you need the name of the parameters, then you can create an anonymous object and read its properties:
public static Dictionary<int, int> Foo(bool os, bool rad, bool aci, bool outr, string distrito = null)
{
var obj = new { os, rad, aci, outr};
foreach (PropertyInfo pInfo in obj.GetType().GetProperties())
{
var value = (bool)pInfo.GetValue(obj);
if (value)
{
//Do whatever you want here.
Console.WriteLine("{0}: {1}", pInfo.Name, value);
}
}
}
You can try some LINQ extensions, I think composition of Where and Select may be a solution, with a string.Join method on top:
// get all parameters
var parameters = MethodBase.GetCurrentMethod().GetParameters();
// for each true parameter we add this prefix
var truePrefix = "true";
// we put this string between true parameters, if more than one are present
var separator = "Additional";
// Join IEnumerable of strings with a given separator
var total = string.Join(separator, parameters
// filter parameters to check only boolean ones, and only true ones
.Where(p => p.ParameterType == typeof(bool) && (bool)p.Value)
// select a name with prefix
.Select(p => truePrefix + p.Name)))
I have a PolygonRenderer class containing a Vertices property, which is a List, holding the points of the polygon the class renders.
When I try to change a specific point in this list by reflection, I get a System.Reflection.TargetParameterCountException on the last line of my function :
public override void ApplyValue(string property, object value, int? index)
{
List<PropertyInfo> properties = Data.GetType().GetProperties().ToList();
PropertyInfo pi = properties.FirstOrDefault(p => p.Name == property);
pi.SetValue(Data, value,
index.HasValue ? new object[] { index.Value } : null);
}
When I debug, I get index.Value = 3, Data is the PolygonRenderer instance and pi reflects the Vertices property, which count = 4.
Since my index is supposed to be the last item of my list, how is it possible that I get a count exception on that property ?
Thanks
I have a PolygonRenderer class containing a Vertices property, which is a List...
So you need to execute something like this
Data.Vertices[index] = value
and what your code is trying to do is
Data[index] = value
You can use something like this instead
public override void ApplyValue(string property, object value, int? index)
{
object target = Data;
var pi = target.GetType().GetProperty(property);
if (index.HasValue && pi.GetIndexParameters().Length != 1)
{
target = pi.GetValue(target, null);
pi = target.GetType().GetProperties()
.First(p => p.GetIndexParameters().Length == 1
&& p.GetIndexParameters()[0].ParameterType == typeof(int));
}
pi.SetValue(target, value, index.HasValue ? new object[] { index.Value } : null);
}
Below we have a list for a mock database call and a little program that takes that list and converts it do a DataTable. In this example I'm using a variable for the column name to access that column's values and get the average. However, I made a call to the Field method and gave it the type of int. It doesn't appear to be possible to pass a variable to the generic Field method. Is there another way to access a DataTable's column values and return something like an average without knowing the type of the column until runtime?
public class Icd
{
public int ConditionCode { get; set; }
public string ConditionName { get; set; }
public static List<Icd> GetIcdList()
{
return new List<Icd>()
{
new Icd() { ConditionCode = 111, ConditionName = "Condition 1" },
new Icd() { ConditionCode = 222, ConditionName = "Condition 2" },
};
}
}
var icdList = Icd.GetIcdList();
var columnName = "ConditionCode";
DataTable dt = new DataTable();
dt = icdList.ToList().ListToDataTable();
var avg = dt.AsEnumerable().Where(x => x[columnName] != DBNull.Value)
//possible to pass a variable to the field method?
.Average(x => x.Field<int>(columnName));
Console.WriteLine(avg); //<-- correct answer
Update: I tried to add:
Type t = typeof(int)
and do
x => x.Field<t>(columnName)
but that gives me the error:
The type or namespace 't' could not be found
ListToDataTable helper method:
public static DataTable ListToDataTable<T>(this IList<T> data)
{
DataTable dt = new DataTable();
PropertyDescriptorCollection props = TypeDescriptor.GetProperties(typeof(T));
for (int i = 0; i < props.Count; i++)
{
PropertyDescriptor prop = props[i];
dt.Columns.Add(prop.Name, prop.PropertyType);
}
object[] values = new object[props.Count];
foreach (T t in data)
{
for (int i = 0; i < values.Length; i++)
{
values[i] = props[i].GetValue(t);
}
dt.Rows.Add(values);
}
return dt;
}
I think you may use the dynamic type here.
For example:
var avg = dt.AsEnumerable().Where(x => x[columnName] != DBNull.Value)
//possible to pass a variable to the field method?
.Average(x => x.Field<dynamic>(columnName));
I have done minimal testing, and it seems to work. Others are welcome to comment on this.
Cheers
Generic types must be known at compile time. If you want to make that variable you need to use reflection:
// Get the method information
MethodInfo method = typeof(T).GetMethod("Field");
// here hardcoded for int, but you could use any type
var types = new Type[] {typeof(int)};
// Create a generic instance of that method
MethodInfo genericMethod = method.MakeGenericMethod(types);
var avg = dt.AsEnumerable().Where(x => x[columnName] != DBNull.Value)
// Use the generic method with target x and parameter columnName
.Average(x => genericMethod.Invoke(x, columnName);
I have a couple of classes implementing an ISortable interface:
public interface ISortable
{
int Id { get; set; }
int? Idx { get; set; }
}
In my DbContext class I have an update method that should do some extra stuff for entities that implements the ISortable:
public void UpdateSingle<T>(T item) where T : class
{
// If entity is Sortable, update the indexes of the records between the new and the old index of the updated entity
var sortable = item as ISortable;
if (sortable != null)
{
Detach(item); // need to detach the entity from the context in order to retrieve the old values from DB
var oldItem = Find<T>(sortable.Id) as ISortable;
if (oldItem != null && sortable.Idx != oldItem.Idx)
{
var entities = FindAll<T>().ToList().Cast<ISortable>();
var oldIdx = oldItem.Idx;
var newIdx = sortable.Idx;
if (newIdx > oldIdx)
{
var toUpdate = entities.Where(a => a.Idx <= newIdx && a.Idx > oldIdx).Select(a => a);
foreach (var toUpdateEntity in toUpdate)
{
toUpdateEntity.Idx = toUpdateEntity.Idx - 1;
}
}
else
{
var toUpdate = entities.Where(a => a.Idx >= newIdx && a.Idx < oldIdx).Select(a => a);
foreach (var toUpdateEntity in toUpdate)
{
toUpdateEntity.Idx = toUpdateEntity.Idx + 1;
}
}
}
Detach(oldItem);
Attach(item); // re-attach to enable saving
}
Entry(item).State = EntityState.Modified;
Commit();
}
What I'm wondering about is this line:
var entities = FindAll<T>().ToList().Cast<ISortable>();
I have to convert the LINQ to SQL expression to a list in order to cast the entities to ISortable. And I need to cast it to ISortable in order to perform this Where:
var toUpdate = entities.Where(a => a.Idx <= newIdx && a.Idx > oldIdx).Select(a => a);
The Idx attribute is exposed by the interface.
The problem is that calling ToList() on FindAll() loads the whole table into memory.
Is there a way of performing the Where without first loading the whole table, and without losing the generic implementation?
The idea here is that I want to perform some common action on update for all entities that are "sortable". For this to work the update method needs to be generic in order to handle various classes, but then I need the interface to expose the necessary fields... If there's a better way of doing this (there probably is), please let me know. :-)
The problem is that calling ToList() on FindAll() loads the whole table into memory.
Use AsEnumerable instead of ToList; it just changes the compile-time type to IEnumerable<T> instead of IQueryable<T>, so the subsequent operations are executed in memory rather than in the database, but only one item at a time is processed (items are fetched from the DB one by one as needed by the subsequent operations).
Trying again, this time with expressions. I think this should work:
public void UpdateSingle<T>(T item) where T : class
{
// If entity is Sortable, update the indexes of the records between the new and the old index of the updated entity
var sortable = item as ISortable;
if (sortable != null)
{
Detach(item); // need to detach the entity from the context in order to retrieve the old values from DB
var oldItem = Find<T>(sortable.Id);
if (oldItem != null && sortable.Idx != oldItem.Idx)
{
UpdateSingleSortable(oldItem, sortable);
}
Detach(oldItem);
Attach(item); // re-attach to enable saving
}
Entry(item).State = EntityState.Modified;
Commit();
}
public void UpdateSingleSortable<T>(T oldItem, ISortable sortable)
where T : class
{
var entities = FindAll<T>();
var oldIdx = oldItem.Idx;
var newIdx = sortable.Idx;
if (newIdx > oldIdx)
{
var expression = GenerateExpressionA(oldItem, newIdx, oldIdx);
var typedExpression = expression as Expression<Func<T, bool>>;
var toUpdate = entities.Where(typedExpression).Select(a => a);
foreach (var toUpdateEntity in toUpdate)
{
toUpdateEntity.Idx = toUpdateEntity.Idx - 1;
}
}
else
{
var expression = GenerateExpressionB(oldItem, newIdx, oldIdx);
var typedExpression = expression as Expression<Func<T, bool>>;
var toUpdate = entities.Where(typedExpression).Select(a => a);
foreach (var toUpdateEntity in toUpdate)
{
toUpdateEntity.Idx = toUpdateEntity.Idx + 1;
}
}
}
Expression GenerateExpressionB<T>(T t, int? newIdx, int? oldIdx)
{
// a => a.Idx >= newIdx && a.Idx < oldIdx
var underlyingType = t.GetType();
var idxGetter = underlyingType.GetProperty("Idx");
Type genericFunc = typeof(Func<,>);
Type[] typeArgs = { underlyingType, typeof(bool) };
Type returnType = genericFunc.MakeGenericType(typeArgs);
var param = Expression.Parameter(underlyingType);
var toReturn = Expression.Lambda(
returnType,
Expression.And
(
Expression.GreaterThanOrEqual(
Expression.MakeMemberAccess(param, idxGetter),
Expression.Constant(newIdx, typeof(int?))
),
Expression.LessThan(
Expression.MakeMemberAccess(param, idxGetter),
Expression.Constant(oldIdx, typeof(int?))
)
),
param);
return toReturn;
}
Expression GenerateExpressionA<T>(T t, int? newIdx, int? oldIdx)
{
// a => a.Idx <= newIdx && a.Idx > oldIdx
var underlyingType = t.GetType();
var idxGetter = underlyingType.GetProperty("Idx");
Type genericFunc = typeof(Func<,>);
Type[] typeArgs = { underlyingType, typeof(bool) };
Type returnType = genericFunc.MakeGenericType(typeArgs);
var param = Expression.Parameter(underlyingType);
var toReturn = Expression.Lambda(
returnType,
Expression.And
(
Expression.LessThanOrEqual(
Expression.MakeMemberAccess(param, idxGetter),
Expression.Constant(newIdx, typeof(int?))
),
Expression.GreaterThan(
Expression.MakeMemberAccess(param, idxGetter),
Expression.Constant(oldIdx, typeof(int?))
)
),
param);
toReturn.Dump();
return toReturn;
}
Just change the signature of the method to the following:
public void UpdateSingle<T>(T item)
where T : class, ISortable
Then not only can you perform the query on the DB end (you won't need to pull the collection into memory to get the items meeting the given condition) you also won't be doing the check at runtime; you'll be checking to make sure T implements ISortable at compile time.