How to distinguish Navigation Properties from regular ones when using GetProperties? - c#

Is there a way to distinguish between a regular collection property and a navigation property on an Entity-Framework class (Database-First)?
I'm currently checking if the object is ICollection and IsVirtual but I feel this could possibly trigger on a regular property that someone has declared as a virtual collection.
Question: Are there any other ways to distinguish Navigation Properties from others?
Context: I'm using this to compare values of any object, but I want it to ignore navigation properties (to ignore circular-references among other things).
foreach (var item in (IEnumerable)obj)
{
list2.MoveNext();
var item2 = list2.Current;
foreach (PropertyInfo propInfo in item.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
Object v1 = propInfo.GetValue(item);
Object v2 = propInfo.GetValue(item2);
Primitive = (v1 == null && v2 == null) || IsPrimitive(v1.GetType());
if (Primitive)
{
Assert.AreEqual(v1, v2);
}
else
{
// Ignore Navigation Properties
// Currently assuming Virtual properties to be Navigation...
if (propInfo.GetGetMethod().IsVirtual) continue;
CompareObjects(v1, v2);
}
}
}

Well, if you want to know the names of the navigation properties and the scalar properties related to an Entity, I suggest you use this code:
using (var db = new YourContext())
{
var workspace = ((IObjectContextAdapter)db).ObjectContext.MetadataWorkspace;
var itemCollection = (ObjectItemCollection)(workspace.GetItemCollection(DataSpace.OSpace));
var entityType = itemCollection.OfType<EntityType>().Single(e => itemCollection.GetClrType(e) == typeof(YourEntity));
foreach (var navigationProperty in entityType.NavigationProperties)
{
Console.WriteLine(navigationProperty.Name);
}
foreach (var property in entityType.Properties)
{
Console.WriteLine(property.Name);
}
}

One solution when using GetProperties() is to create an IEntity interface and apply it to all entities. Then you can skip over single entity navigation properties by checking if they implement IEntity and multiple entity navigation
if they are of type ICollection.
So in your foreach,
if (property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition() == typeof(System.Collections.Generic.ICollection<>)) continue;
if (property.PropertyType.GetInterfaces().Contains(typeof(IEntity))) continue;
Here is a simple method to return only updateable properties using this logic:
private IEnumerable<PropertyInfo> GetUpdateableProperties<T>(T entity) where T : IEntity
{
return entity.GetType().GetProperties(BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance | BindingFlags.SetProperty)
.Where(property =>
property.CanWrite &&
!property.PropertyType.GetInterfaces().Contains(typeof(IEntity)) &&
!(property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition() == typeof(ICollection<>))
);
}

Related

Cannot cast back to ICollection<T>

I have an Interface ISomeThing:
public interface ISomeThing {}
and 2 types implementing it:
public class SomeThing1 : ISomeThing {}
public class SomeThing2 : ISomeThing {}
and then I have a type using those types:
public class FooBar
{
public ICollection<SomeThing1> Foo { get; set; }
public ICollection<SomeThing2> Bar { get; set; }
}
I then have to use reflection to get access to the properties of FooBar:
var properties = typeof(FooBar).GetProperties()
.Where(p => typeof(ICollection<>).IsAssignableFrom(p.PropertyType));
Console.WriteLine(properties.Count());
The output will be 0. Even if I change the code to look for ICollection<ISomeThing> it won't work:
var properties = typeof(FooBar).GetProperties()
.Where(p => typeof(ICollection<ISomeThing>).IsAssignableFrom(p.PropertyType));
Console.WriteLine(properties.Count());
I want to get access to the property directly because ICollection brings in Remove etc.. So I need the cast to ICollection<T>.
Edit
I changed my sample to use ISomeThing instead of DateTime. The point is that I don't know the exact generic type at runtime but I need to yield a ICollection<ISomeThing> as the result.
** Edit 2 **
Now I finally come up with my example. This example here shows why I need the cast so badly.
var properties = instance.GetType().GetProperties();
foreach (var property in properties){
var value = property.GetValue(instance);
if (value is ISomeThing someThingValue && someThingValue.IsSomeCondition)
{
// Do a ISomeThing-specific thing here
}
else if (property.PropertyType.GetInterfaces().Concat(new []{property.PropertyType})
.Any(i => i.IsGenericType
&& i.GetGenericTypeDefinition() == typeof(ICollection<>)
&& typeof(ISomeThing).IsAssignableFrom(i.GetGenericArguments().Single())))
{
var someThingValues = value as ICollection<ISomeThing>; // <-- this results in null
foreach (var someThingInstance in someThingValues)
{
if(someThingInstance.IsSomeCondition)
{
// Do the thing again
}
}
}
// Enter recursion for possible nested ISomeThings
}
ICollection<DateTime> collection = new HashSet<ISomeThing>();
That makes no sense. I assume you meant to type
ICollection<ISomeThing> collection = new HashSet<ISomeThing>();
Moving on.
Console.WriteLine(collection.GetType() as ICollection<ISomeThing>);
This is null because collection.GetType() returns a Type, and a Type does not implement ICollection<ISomething>.
Console.WriteLine(typeof(ICollection<>)
.IsAssignableFrom(collection.GetType()));
This is false because IsAssignableFrom means "can a variable of type ICollection<> be assigned a value of type HashSet<ISomething>. There is no such thing as a variable of type ICollection<>. Generic types have to be constructed before they can be the type of a variable.
I feel almost like the output lies to me when I look at line 1 and 3
It does not.
but I want to get access to the property directly because ICollection brings in Remove etc. So I need the cast to ICollection<T>.
I cannot for the life of me figure out what this sentence means. Can you clarify it?
UPDATE:
Based on the update to the question I now suspect that the actual question is either
Given a Type, I wish to know how many properties are of type ICollection<T> where T is any type that implements ISomeThing"
Easy peasy:
Type type = typeof(FooBar);
var properties = type
.GetProperties()
.Where(p => p.PropertyType.IsGenericType)
.Where(p => p.PropertyType.GetGenericTypeDefinition() == typeof(ICollection<>))
.Where(p => typeof(ISomeThing).IsAssignableFrom(
p.PropertyType.GenericTypeArguments.Single()))
Console.WriteLine(properties.Count()); // 2
or perhaps it is
Given a Type, I wish to know how many properties are of types that implement ICollection<T> where T is any type that implements ISomeThing"
That would be
var properties = type
.GetProperties()
.Where(p => p.PropertyType
.GetInterfaces()
.Concat(new [] {p.PropertyType})
.Where(i => i.IsGenericType)
.Where(i => i.GetGenericTypeDefinition() == typeof(ICollection<>))
.Where(i => typeof(ISomeThing)
.IsAssignableFrom(i.GetGenericArguments().Single()))
.Any());
UPDATE: Based on the latest update to this confusing question, the question is now how to rewrite this loop so that it works:
{
var someThingValues = value as ICollection<ISomeThing>; // <-- this results in null
foreach (var someThingInstance in someThingValues)
{
if(someThingInstance.IsSomeCondition)
{
// Do the thing again
}
}
}
That's easy. You simply don't attempt to cast to ICollection<anything> because you don't need any member of that type in your example. What do we know? That the type is ICollection<T> where T is ISomeThing. It is a requirement that this type implements non-generic IEnumerable and generic IEnumerable<T>.
It is not a requirement that the IEnumerable only yield objects that implement ISomeThing, but the author of the object would be crazy to have IEnumerable yield different objects than could be in the collection, so let's be cautious and stick a type filter on there.
var properties = instance.GetType().GetProperties();
foreach (var property in properties){
// Let's emphasize here that we don't know the type by using
// object instead of var
object value = property.GetValue(instance);
// We need to bail if this is null.
if (value == null)
continue;
if (value is ISomeThing someThingValue && someThingValue.IsSomeCondition)
{
// Do a ISomeThing-specific thing here
}
else if (property.PropertyType.GetInterfaces().Concat(new []{property.PropertyType})
.Any(i => i.IsGenericType
&& i.GetGenericTypeDefinition() == typeof(ICollection<>)
&& typeof(ISomeThing).IsAssignableFrom(i.GetGenericArguments().Single())))
{
var ie = value as IEnumerable;
Debug.Assert(ie != null);
foreach (ISomeThing someThingInstance in ie.OfType<ISomeThing>())
{
if(someThingInstance.IsSomeCondition)
{
// Do the thing again
}
}
}
// Enter recursion for possible nested ISomeThings
}
You could also tighten that up a bit with
var q = ie.OfType<ISomeThing>().
.Where(x => x.IsSomeCondition);
foreach (ISomeThing someThingInstance in q)
{
// Do the thing again
}
BONUS EXERCISE
Now, one might reasonable note that ICollection<T> is also convertible to IEnumerable<T>. We could reason like this:
We know we have ICollection<T> for some T that implements ISomeThing.
We know that we can convert ICollection<T> to IEnumerable<T> for any T.
Therefore we can convert this to IEnumerable<T> for some T that implements ISomeThing.
IEnumerable<T> is covariant in T and therefore we can convert our ICollection<T> directly to IEnumerable<ISomeThing> and enumerate that.
Therefore we should really be writing
var ie = value as IEnumerable<ISomeThing>;
Debug.Assert(ie != null);
foreach (ISomeThing someThingInstance in ie)
The above argument contains a logical flaw. Do you see it?
Give it some thought and once you figure out why this logic is wrong, leave an answer in the comments.
With the updated example, this should get you what you want. It finds all properties that are of ICollection<> and have a generic parameter that implements ISomeThing.
var properties = typeof(FooBar).GetProperties().Where(p => p.PropertyType.IsGenericType && p.PropertyType.GetGenericTypeDefinition() == typeof(ICollection<>) && p.PropertyType.GenericTypeArguments[0].GetInterfaces().Contains(typeof(ISomeThing)));

Getting all ICollection properties through reflection

I'm trying to fetch all ICollection<T> properties from class of unknown type. Also, type T (what the collection is of) is not known at compile time. Firstly I've tried this approach:
foreach (var property in entity.GetType().GetProperties())
{
if (typeof(ICollection).IsAssignableFrom(property.PropertyType) || typeof(ICollection<>).IsAssignableFrom(property.PropertyType))
{
// do something
}
}
but it's not working (evaluating false even for ICollection properties).
I got it working like this:
foreach (var property in entity.GetType().GetProperties())
{
var getMethod = property.GetGetMethod();
var test = getMethod.Invoke(entity, null);
if (test is ICollection)
{
// do something
}
}
but I do not want to execute all getters. Why is the first piece of code not working? How can I find ICollection properties without executing all getters?
It turns out that with IsAssignableFrom check you can't find whether the interface is a derivative of another interface:
Console.WriteLine(typeof(ICollection<>).IsAssignableFrom(typeof(ICollection<Int32>)));
Console.WriteLine(typeof(ICollection<Int32>).IsAssignableFrom(typeof(ICollection<>)));
will both write false;
With little help from here this is the best solution I can come of:
static IEnumerable<PropertyInfo> GetICollectionOrICollectionOfTProperties(this Type type)
{
// Get properties with PropertyType declared as interface
var interfaceProps =
from prop in type.GetProperties()
from interfaceType in prop.PropertyType.GetInterfaces()
where interfaceType.IsGenericType
let baseInterface = interfaceType.GetGenericTypeDefinition()
where (baseInterface == typeof(ICollection<>)) || (baseInterface == typeof(ICollection))
select prop;
// Get properties with PropertyType declared(probably) as solid types.
var nonInterfaceProps =
from prop in type.GetProperties()
where typeof(ICollection).IsAssignableFrom(prop.PropertyType) || typeof(ICollection<>).IsAssignableFrom(prop.PropertyType)
select prop;
// Combine both queries into one resulting
return interfaceProps.Union(nonInterfaceProps);
}
This solution may yield some duplicates(it is hardly possible, but to be sure use Distinct) and it doesn't look very nice.
But it works well on such class with properties with both the interface return types and concrete return types :
class Collections
{
public List<Int32> ListTProp
{
get;
set;
}
public IDictionary<Int32, String> IDictionaryProp
{
get;
set;
}
public ICollection ICollectionProp
{
get;
set;
}
public ICollection<DateTime> IDateTimeCollectionProp
{
get;
set;
}
}
After attempting to use the accepted answer I had a situation where only partial matches where being returned. My object had 3 ICollection<T> properties and I was only being returned with 2. I spent some time testing and trying to figure out why, but I moved on and wrote this:
public static IEnumerable<PropertyInfo> GetICollectionProperties(object entity)
{
return entity.GetType().GetProperties()
.Where(p => p.PropertyType.IsGenericType
&& p.PropertyType.GetGenericTypeDefinition() == typeof(ICollection<>));
}
I have tested with the same test cases and I am getting the correct results returned from this method.
This will not pick up non-generic ICollections, but the OP did ask for ICollection<T>properties, all-though it could be easily re-factored to include. It will also not return properties that are not exactly of type ICollection (ie, Eugene's List and IDictionary in his test case would not be returned (but again, what the OP wanted)).

Finding an ObservableCollection for given/dynamic type via reflection

I have a Class with several ObservableCollections for different types. Now, I want to find the correct Collection for a given type via reflection, because I don't want to build an if-monster which I have to update every time I add another Collection.
This method was the first step:
public ObservableCollection<T> GetObservableCollectionForType<T>()
{
foreach (PropertyInfo info in this.GetType().GetProperties())
{
if (info.GetGetMethod() != null && info.PropertyType == typeof(ObservableCollection<T>))
return (ObservableCollection<T>)this.GetType().GetProperty(info.Name).GetValue(this, null);
}
return null;
}
Now, I need a second method, which accepts a concrete object as parameter and finds the correct Collection. Somehow like this:
public ObservableCollection<T> GetObservableCollectionFor(object sObject)
{
Type wantedType = sObject.GetType();
foreach (PropertyInfo info in this.GetType().GetProperties())
{
if (info.GetGetMethod() != null && info.PropertyType == ObservableCollection<wantedType>)
return this.GetType().GetProperty(info.Name).GetValue(this, null);
}
return null;
}
Any ideas how to realize this?
Update:
A working solution:
public object GetObservableCollectionFor(object sObject)
{
Type wantedType = sObject.GetType();
foreach (PropertyInfo info in this.GetType().GetProperties())
{
if (info.GetGetMethod() != null && info.PropertyType == typeof(ObservableCollection<>).MakeGenericType(new[]{wantedType}))
return this.GetType().GetProperty(info.Name).GetValue(this, null);
}
return null;
}
This will return the correct collection as object. I still don't know how to cast to the correct generic type, but casting to IList is enough for adding and removing.
When comparing the type of the property, it looks like you need to add a call to MakeGenericType() on the ObservableCollection type. Haven't tested this but maybe something like...
public ObservableCollection<T> GetObservableCollectionFor(object sObject)
{
Type wantedType = sObject.GetType();
foreach (PropertyInfo info in this.GetType().GetProperties())
{
if (info.GetGetMethod() != null && info.PropertyType == typeof(ObservableCollection<>).MakeGenericType(new[]{Type.GetType(wantedType)})
return (ObservableCollection<T>)this.GetType().GetProperty(info.Name).GetValue(this, null);
}
return null;
}
Edit
To avoid overhead boxing with value types, the above method definition could be improved by changing the parameter type from object to type T
public ObservableCollection<T> GetObservableCollectionFor(T sObject)

CssStyleCollection Class : access properties through reflection

How can I access CssStyleCollection class properties ( most of all I interesting its key-value collection) using reflection?
// this code runns inside class that inherited from WebControl
PropertyInfo[] properties = GetType().GetProperties();
//I'am not able to do something like this
foreach (PropertyInfo property in properties)
{
if(property.Name == "Style")
{
IEnumerable x = property.GetValue(this, null) as IEnumerable;
...
}
}
Here's the syntax for getting the Style property via reflection:
PropertyInfo property = GetType().GetProperty("Style");
CssStyleCollection styles = property.GetValue(this, null) as CssStyleCollection;
foreach (string key in styles.Keys)
{
styles[key] = ?
}
Note that CssStyleCollection doesn't implement IEnumerable (it implements the indexing operator), so you can't cast it to that. If you want to get an IEnumerable, you could extract the keys using styles.Keys, and the values:
IEnumerable<string> keys = styles.Keys.OfType<string>();
IEnumerable<KeyValuePair<string,string>> kvps
= keys.Select(key => new KeyValuePair<string,string>(key, styles[key]));

How do I get the values from static properties on a static class?

I'm trying to get the names of the people in the below class. I can get the list of PropertyInfo just fine, indicating that People has Bob and Sally, but I can't get references to Bob and Sally. How do I do that?
public static class People
{
public static Person Bob { get; }
public static Person Sally { get; }
}
PropertyInfo[] properties = typeof(People).GetProperties();
foreach (PropertyInfo info in properties)
{
if (info.PropertyType == typeof(Person))
{
// how do I get a reference to the person here?
Person c = info.GetValue(?????, ?????) as Person;
if (null != c)
{
Console.WriteLine(c.Name);
}
}
}
edit changed null == c to null != c to get console.writeline to execute
Use:
Person c = (Person) info.GetValue(null, null);
if (c != null)
{
Console.WriteLine(c.Name);
}
The first null is for the target of the property - which is null because it's a static property. The second null is to say that there aren't any indexer arguments, because this is just a property, not an indexer. (They're the same kind of member to the CLR.)
I've changed the use of the result from an as to a cast as you're expecting the result to be a Person, given that you're already checked the property type.
I've then reversed the order of the operands for the comparison with null, as well as reversing the sense - you don't want to try to print out c.Name if you know that c is null! In C# the old C++ idiom of if (2 == x) to avoid accidental assignment is almost always pointless, as an if condition has to be a bool expression anyway. In my experience most people find the code more readable with the variable first and the constant second.
Here's the approach that I use, which I've thrown into its own method. This will return an array of objects that are all the instances that are statically available from the type you pass in. It should not matter if the original type is static or not.
using System;
using System.Linq;
using System.Reflection;
public static object[] RetrieveInstancesOfPublicStaticPropertysOfTypeOnType(Type typeToUse) {
var instances = new List<object>();
var propsOfSameReturnTypeAs = from prop in typeToUse.GetProperties(BindingFlags.Public | BindingFlags.Static)
where prop.PropertyType == typeToUse
select prop;
foreach (PropertyInfo props in propsOfSameReturnTypeAs) {
object invokeResult = typeToUse.InvokeMember(
props.Name,
BindingFlags.GetProperty,
null,
typeToUse,
new object[] { }
);
if (invokeResult != null) {
instances.Add(invokeResult);
}
}
return instances.ToArray();
}

Categories