I was looking for a general solution for cascading delete in linq to sql from c#, but I could not find any yet.
So I came up with my own solution, which handles one to many relations.
It would not be the general way to delete entities, but some edge case requires it. So before it goes to the production environment, I would like to know that what are you think about it?
It seems to work, but what are the pros and cons, where it can fail? Note that it only supposed to traverse the relation tree downside.
public class CascadingDeleteHelper
{
public void Delete(object entity, Func<object, bool> beforeDeleteCallback)
{
if (!(entity is BusinessEntity))
{
throw new ArgumentException("Argument is not a valid BusinessEntity");
}
if (beforeDeleteCallback == null || beforeDeleteCallback(entity))
{
Type currentType = entity.GetType();
foreach (var property in currentType.GetProperties())
{
var attribute = property
.GetCustomAttributes(true)
.Where(a => a is AssociationAttribute)
.FirstOrDefault();
if (attribute != null)
{
AssociationAttribute assoc = attribute as AssociationAttribute;
if (!assoc.IsForeignKey)
{
var propertyValue = property.GetValue(entity, null);
if (propertyValue != null && propertyValue is IEnumerable)
{
IEnumerable relations = propertyValue as IEnumerable;
List<object> relatedEntities = new List<object>();
foreach (var relation in relations)
{
relatedEntities.Add(relation);
}
relatedEntities.ForEach(e => Delete(e, beforeDeleteCallback));
}
}
}
}
SingletonDataContext.DataContext.GetTable(currentType).DeleteOnSubmit(entity);
SingletonDataContext.DataContext.SubmitChanges();
}
}
}
Thank you very much,
negra
Pros of your solution:
It's generic
It does not require database changes on schema updates
Cons:
Error prone (more custom code)
Performance is (most likely) not optimal
There is a less complex, but more maintenance heavy solution, which is adding ON CASCADE DELETE rules into your database.
Related
Background
I have created object graphs in Entity Framework where any given object A will have a table Ac that tracks changes for it. These objects may also connect to each other, such as A being 1-many to B. Here is an example graph:
A -> Ac
/ \
Bc <- B \
/ \
Cc <- C D -> Dc
I want to be able to load an object and specific connected objects at a point in time by using the change tables to pull those records and apply them. Ideally, I'd like to be able to either use or mimic the .Include function from Entity Framework.
The Issue
Pulling out which objects are already included in an IQueryable is not as easy as I guessed it would be. Looking at an IQueryable<T> with a child object of T Include()-ed, I can see that these relationships are stored in some sort of Span object within an Arguments property - but these are both internal classes and trying to retrieve this information has a lot of steps.
Here is what I have so far:
public static void LoadVersion<T>( this IQueryable<T> query, DateTime targetDateTime )
{
//grab the value of the "Arguments" property on query.Expression
//this has to be done through reflection because "Arguments" is not accessible otherwise
PropertyInfo argumentsPropertyInfo = query.Expression.GetType().GetProperties().FirstOrDefault( x => x.Name == "Arguments" );
dynamic argumentsPropertyValue = argumentsPropertyInfo.GetValue( query.Expression );
for (int i = 0; i < argumentsPropertyValue.Count; i++)
{
//This gets me a System.Data.Entity.Core.Objects.Span, but that class is internal
//In the watch, I can see span -> SpanList[0].Navigations[0] gives me the name of the class in the .Include()
// This is the value I need
dynamic span = argumentsPropertyValue[i].Value;
//So if I try to pull it out using the same reflection trick as before, I get
// a dynamic {System.Reflection.PropertyInfo[0]} (not a list, as you would normally expect),
// and accessing those values & methods makes the debugger exit without an exception
dynamic spanPropertyInfo = argumentsPropertyValue[i].Value.GetType().GetProperties();
//this makes the debugger exit without an exception
dynamic spanPropertyValue = spanPropertyInfo[0].GetValue(span);
//this also makes the debugger exit without an exception (with the above line commented out, of course)
dynamic spanPropertyValue2 = spanPropertyInfo.GetValue( span );
}
}
Based on how difficult it is for me to find what is Included in a Query, I can't help but think that I am doing this entirely the wrong way. Digging through some of the Entity Framework 6.1.3 source code hasn't shed much light on this.
Edit
I've been playing around with the code provided by Alex Derck, but I realized I still need a few pieces to make this work the way I want.
Here is the version of VisitMethodCall I implemented:
protected override Expression VisitMethodCall( MethodCallExpression node )
{
if (node.Method.Name != "Include" && node.Method.Name != "IncludeSpan") return base.VisitMethodCall(node);
try
{
string includedObjectName = (string) node.Arguments.First().GetPrivatePropertyValue( "Value" );
if (includedObjectName != null)
{
_includes.Add(includedObjectName);
}
}
catch (Exception e ){ }
return base.VisitMethodCall( node );
}
I'm able to construct a query with includes and get the names of the objects I included using the IncludeVisitor, but the main goal to use these was to be able to find the related tables and add them to the include.
So when I have the equivalent of this:
var query = ctx.Persons.Include(p => p.Parents).Include(p => p.Children);
// includes[0] = "Parents"
// includes[1] = "Children"
var includes = IncludeVisitor.GetIncludes(query.Expression);
I am successfully grabbing the includes, and I can then find the related tables (Parents -> ParentsChanges, Children -> ChildrenChanges), but I'm not 100% sure how to add these back to the include.
The main problem here is when it's a nested statement:
context.A.Include(x => x.B).Include(x => x.C).Include(x => x.B.Select(y => y.D))
I can successfully traverse that whole graph and get the names of A, B, C, and D, but I need to be able to add a statement like this back to the include:
[...].Include(x => x.B.Select(y => y.D.Select(z => z.DChanges)))
I can find DChanges just fine, but I don't know how to build that include back up because I don't know how many steps are between DChanges and the original item (A).
After looking a bit in the source code of Entity Framework I noticed the includes are not part of the Expression, but rather part of the IQueryable. If you think about it, it's pretty obvious it should be that way. Expressions can't actually execute code themselves, they are translated by a provider (which is also part of the IQueryable), and not all providers should know how to translate an Include method. In the source code you can see the IQueryable.Include method calls the following small method:
public ObjectQuery<T> Include(string path)
{
Check.NotEmpty(path, "path");
return new ObjectQuery<T>(QueryState.Include(this, path));
}
The query (casted to an ObjectQuery) simply gets returned and only it's internal QueryState gets changed, nothing at all happens to the expression. In the debugger you can see the EntitySets that will be included if you look in the IQueryable, but I haven't been able to put them into a list (_cachedPlan is always null when I try to access it through reflection).
I think after seeing this, the thing you're trying to do is not possible, so I would keep a static list of strings in my dbContext and implement a custom Include extension method:
public partial class TestDB
{
public static ICollection<Expression> Includes { get; set; } = new List<Expression>();
public TestDB() : base()
{
Includes = new List<Expression>();
}
...
}
public static class EntityExtensions
{
public static IQueryable<T> CustomInclude<T, TProperty>(this IQueryable<T> query,
Expression<Func<T,TProperty>> include) where T : class
{
TestDB.Includes.Add(include);
return query.Include(include);
}
}
You could also 'override' the normal Include method from System.Data.Entity.
I say 'override', because technically it's not really possible to override an extension method, but you can just create an extension method called Include with the same parameters yourself, and if you don't include System.Data.Entity where you use it, there's no ambiguity between your own method and the one from System.Data.Entity:
public static class EntityExtensions
{
public static IQueryable<T> Include<T, TProperty>(this IQueryable<T> query,
Expression<Func<T,TProperty>> include) where T : class
{
TestDB.Includes.Add(include);
var method = typeof(QueryableExtensions)
.GetMethods(BindingFlags.Public | BindingFlags.Static)
.Where(m => m.Name == "Include")
.First(m => m.GetParameters().All(p => p.ParameterType.IsGenericType));
var generic = method.MakeGenericMethod(typeof(T), typeof(TProperty));
return (IQueryable<T>)generic.Invoke(query, new object[] { query, include });
}
}
I write here another answer to your question (but not the solution you need).
You can retrieve the objects added to entity framework 6 include list from an already retrieved entity in the same way the entity proxy does.
The property to retrieve if a property should be lazy loaded on access (so not already loaded and not in include list) is Relationship.IsLoaded. You can find the list of relationships in YourEntityWithProxy._entityWrapper.Relationships.
_entityWrapper and other properties are private so you need to use reflection to read them.
With the help of Alex, I was able to get what I wanted.
First, to get the name of the includes, I used a small variation of one of the earlier versions of the answer Alex posted:
internal static class IncludeVisitorExtensions
{
public static object GetPrivatePropertyValue( this object obj, string propName )
{
PropertyInfo propertyInfo = obj.GetType().GetProperty( propName, BindingFlags.Public
| BindingFlags.NonPublic | BindingFlags.Instance );
return propertyInfo.GetValue( obj, null );
}
public static object GetPrivateFieldValue( this object obj, string fieldName )
{
FieldInfo fieldInfo = obj.GetType().GetField( fieldName, BindingFlags.Public
| BindingFlags.NonPublic | BindingFlags.Instance );
return fieldInfo?.GetValue( obj );
}
}
internal class IncludeVisitor : ExpressionVisitor
{
private static readonly IncludeVisitor Visitor;
private static List<string> _includes;
private IncludeVisitor() { }
static IncludeVisitor()
{
Visitor = new IncludeVisitor();
}
public static ICollection<string> GetIncludes( Expression expr )
{
_includes = new List<string>();
Visitor.Visit( expr );
return _includes;
}
protected override Expression VisitMethodCall( MethodCallExpression node )
{
if (node.Method.Name != "Include" && node.Method.Name != "IncludeSpan")
return base.VisitMethodCall( node );
//"Include" == .Where() is present in the query
//"IncludeSpan" == no .Where() in the query
try
{
if (node.Method.Name == "Include")
{
string includedObjectName = (string) node.Arguments.First().GetPrivatePropertyValue("Value");
if (includedObjectName != null)
{
_includes.Add(includedObjectName);
}
}
else if (node.Method.Name == "IncludeSpan")
{
var spanList =
node.Arguments.First().GetPrivatePropertyValue("Value").GetPrivatePropertyValue("SpanList");
var navigations = ((IEnumerable<object>) spanList).Select(s => s.GetPrivateFieldValue("Navigations"));
foreach (var nav in navigations)
_includes.Add(string.Join(".", (IEnumerable<string>) nav));
}
}
catch (Exception e) { }
return base.VisitMethodCall( node );
}
}
One little detail I found when testing his code is the difference in how the included tables are found in the expression based on the presence of a .Where() in the IQueryable<>. Thankfully, this can be checked based on the method name, and while the code is a bit ugly, it does dance around the vastly different structures to return the correct name of the table.
Now I have the name of the table, pluralized, as a string. This is because the name is from the DbContext, so I can reflect over the properties and get the Type of the table:
List<PropertyInfo> contextProperties = typeof( TContext ).GetProperties().ToList();
PropertyInfo prop = contextProperties.First( x => x.Name == s );
With the Type, I can accurately find the table I need via Navigation Properties, then I can build a string to send into my new .Include:
ICollection<string> includes = IncludeVisitor.GetIncludes( query.Expression );
foreach (string include in includes)
{
//sometimes the returned include string will be two tables joined with a '.'; these need to be split and each one checked independently
List<string> split = include.Split( '.' ).ToList();
foreach (string s in split)
{
//using .First here because we expect the property to exist
PropertyInfo prop = contextProperties.First( x => x.Name == s );
//the property will be of type DbSet<ObjectType>, so grab the first generic argument (in this case, the object type)
Type dbSetPropertyType = prop.PropertyType.GetGenericArguments().First();
//Get the type we're looking to add in the .Include
var targetTable = GetTargetTableBasedOnTypeViaNavigation(dbSetPropertyType);
//get the name of the property based on the type of the table we just looked up
PropertyInfo contextProperty = contextProperties.SingleOrDefault(x => x.PropertyType.IsGenericType && x.PropertyType.GetGenericArguments().First().Name == targetTable.Name );
string includeString = "";
//build the string and add it to the query
includeString += include + "." + contextPropertyForChangeTracker.Name;
query = query.Include(includeString);
}
}
This worked on my initial test data sets, though I'm not sure how well it will handle more complex graphs.
I have a generic class that performs add/update on entities of type T. The AddOrUpdate() method takes in a DbSet collection to act on as well as a list of items to add or update in the DbSet. The ItemExists() is used to check to see if an item already exists in the collection. If it does, we update. If not, we add. The method essentially compares the primary key of the item passed in with every single item in the table, and returns true (as well as the database object itself) if there's a match.
The code works fine for tables with small number of records. For larger tables however, the ItemExists() method is very inefficient. The method uses a foreach loop which itself is inside another foreach loop in the caller method, giving O(n^2).
An easier way would be to simply use contextDataSet.Contains(item), but that throws an exception that says Unable to create a constant value of type which makes sense since EF can't translate the class into a SQL query. So that's a no go.
Now my actual question: is there a way to replace the entire DbSet<T> with IEnumerable<T> that gets passed in? The IEnumerable that gets passed in is bound to a datagrid on the view and essentially includes all the items, so logically speaking, replacing the entire collection should be safe. Any help is greatly appreciated.
Code
public void AddOrUpdate<I, P>(Expression<Func<I, P>> dbSetExpression, IEnumerable<T> itemsToUpdate)
where I : DbContext, new()
where P : DbSet<T>
{
DataFactory.PerformOperation<I>(c =>
{
if (m_primaryKey == null && !TryFindPrimaryKey(c))
{
throw new ArgumentException("Primary key cannot be null.");
}
// Get the table name from expression passed in.
string dbsetName = ((MemberExpression)dbSetExpression.Body).Member.Name;
var propertyInfo = c.GetType().GetProperties().Single(p => p.Name == dbsetName);
// Get the values in the table.
DbSet<T> contextDataSet = propertyInfo.GetValue(c) as DbSet<T>;
foreach (var item in itemsToUpdate)
{
// If the primary key already exists, we're updating. Otherwise we're adding a new entity.
T existingItem;
if (ItemExists(contextDataSet, item, out existingItem) && existingItem != null)
{
c.Entry(existingItem).CurrentValues.SetValues(item);
}
else
{
contextDataSet.Add(item);
}
}
c.SaveChanges();
});
}
private bool ItemExists(DbSet<T> itemInDbSet, T itemInList, out T existingItem)
{
foreach (var dbItem in itemInDbSet)
{
// Get the primary key value in the database.
var dbValue = dbItem.GetType().GetProperties().Single(
p => p.Name == m_primaryKey).GetValue(dbItem);
// Get the primary key value from the item passed in.
var itemValue =
itemInList.GetType().GetProperties().Single(
p => p.Name == m_primaryKey).GetValue(itemInList);
// Compare the two values.
if (dbValue.ToString() == itemValue.ToString())
{
existingItem = dbItem;
return true;
}
}
existingItem = null;
return false;
}
Relate to : Set Properties that not null using linq and reflection
Hi experts
I change the code in above link:
public static void MyCopy<T>(this T src, T dest)
{
var notNullProps = typeof(T).GetProperties()
.Where(x => x.GetValue(src, null) != null);
foreach (var p in notNullProps)
{
p.SetValue(dest, p.GetValue(src, null),null);
}
}
and I wrote this code to copy peroperties:
NorthwindModel1.Order ord1 = new NorthwindModel1.Order() {CustomerID="Nima",Freight=1.33m,ShipCity="Agha" };
NorthwindModel1.Order ord2 = new NorthwindModel1.Order() ;
ord1.MyCopy(ord2);
but I got this error:
The EntityReference has already been initialized. InitializeRelatedReference should only be used to initialize a new EntityReference during deserialization of an entity object.
please help me to solve this problem
As mentioned in the comments, your reflective code is not the problem, but the fact that (as the exception message explicitly tells) you are indirectly triggering a reset of one of your entity references. My advice is twofold: either modify your reflective code to ONLY copy scalar properties (strings, dates, etc..) -- alternatively ignore references and collections -- OR use serialization:
public static T CloneBySerialization<T>(this T source) where T : EntityObject {
var serializer = new DataContractSerializer(typeof(T));
using (var ios = new MemoryStream()) {
serializer.WriteObject(ios, source);
ios.Seek(0, SeekOrigin.Begin);
return ((T) serializer.ReadObject(ios));
}
}
I must warn you that with this approach you will end up the full object graph or references. If the cloned object is an entity, YOU WILL NOT BE ABLE to use it/attach it to another context, due to the fact that references and foreign keys have been also copied, "verbatim", and this will all likely result in conflicts. The problem gets worse if you're using identity columns in keys.
I've done a lot of magic in my previous work on these matters, and as far as cloning is concerned, the code above is all you need. All, really.
However, to fix the context issue and the usability of your cloned entity, you will have to clear-off the references and under the assumptions that you're also working with "root" entities in the ¹ ↔ * directional relational graph (I hope I am remotely clear, because the story is long) the following will also be necessary.
public static void ClearReferences(this EntityObject entity) {
if (entity == null)
return;
foreach (var p in entity.GetType().GetProperties()) {
if (p.PropertyType.IsGenericType) {
var propertyType = p.PropertyType.GetGenericTypeDefinition();
if (propertyType == typeof(EntityReference<>)) {
var reference = p.GetValue(entity) as dynamic;
if (reference.EntityKey != null) {
reference.EntityKey = null;
((EntityObject) reference.Value).ClearReferences();
}
}
if (propertyType == typeof(EntityCollection<>)) {
var children = (p.GetValue(entity) as IEnumerable<EntityObject>).ToList(); // covariance
foreach (var child in children)
child.ClearReferences();
}
}
}
}
So the idea is that you first clone (via serialization/deserialization), then you "purify".
I've had a look at other questions similar to this one but I couldn't find any workable answers.
I've been using the following code to generate unique keys for storing the results of my linq queries to the cache.
string key = ((LambdaExpression)expression).Body.ToString();
foreach (ParameterExpression param in expression.Parameters)
{
string name = param.Name;
string typeName = param.Type.Name;
key = key.Replace(name + ".", typeName + ".");
}
return key;
It seems to work fine for simple queries containing integers or booleans but when my query contains nested constant expressions e.g.
// Get all the crops on a farm where the slug matches the given slug.
(x => x.Crops.Any(y => slug == y.Slug) && x.Deleted == false)
The key returned is thus:
(True AndAlso (Farm.Crops.Any(y =>
(value(OzFarmGuide.Controllers.FarmController+<>c__DisplayClassd).slug
== y.Slug)) AndAlso (Farm.Deleted == False)))
As you can see any crop name I pass will give the same key result. Is there a way I can extract the value of the given parameter so that I can differentiate between my queries?
Also converting the y to say the correct type name would be nice.....
As Polity and Marc said in their comments, what you need is a partial evaluator of the LINQ expression. You can read how to do that using ExpressionVisitor in Matt Warren's LINQ: Building an IQueryable Provider - Part III. The article Caching the results of LINQ queries by Pete Montgomery (linked to by Polity) describes some more specifics regarding this kind of caching, e.g. how to represent collections in the query.
Also, I'm not sure I would rely on ToString() like this. I think it's meant mostly for debugging purposes and it might change in the future. The alternative would be creating your own IEqualityComparer<Expression> that can create a hash code for any expression and can compare two expressions for equality. I would probably do that using ExpressionVisitor too, but doing so would be quite tedious.
I've been trying to figure out a scenario where this kind of approach could be useful without leading to bloated cache that is insanely hard to maintain.
I know this isn't directly answering your question, but I want to raise a few questions about this approach that, at first, may sound tempting:
How did you plan to manage parameter ordering? Ie. (x => x.blah == "slug" && !x.Deleted) cache key should equal (x => !x.Deleted && x.blah == "slug") cache key.
How did you plan to avoid duplicate objects in cache? Ie. Same farm from multiple queries would by design be cached separately with each query. Say, for each slug that appears in the farm, we have a separate copy of the farm.
Extending the above with more parameters, such as parcel, farmer etc. would lead to more matching queries with each having a separate copy of the farm cached. The same applies to each type you might query plus the parameters might not be in the same order
Now, what happens if you update the farm? Without knowing which cached queries would contain your farm, you'd be forced to kill your whole cache. Which kind of is counterproductive to what you're trying to achieve.
I can see the reasoning behind this approach. A 0-maintenance performance layer. However, if the above points are not taken into consideration, the approach will first kill the performance, then lead to a lot of attempts to maintain it, then prove to be completely unmaintainable.
I've been down that road. Eventually wasted a lot of time and gave up.
I found a much better approach by caching each resulting entity separately when the results come from the backend with an extension method for each type separately or through a common interface.
Then you can build extension method for your lambda expressions to first try the cache before hitting the db.
var query = (x => x.Crops.Any(y => slug == y.Slug) && x.Deleted == false);
var results = query.FromCache();
if (!results.Any()) {
results = query.FromDatabase();
results.ForEach(x = x.ToCache());
}
Of course, you will still need to track which queries have actually hit the database to avoid query A returning 3 farms from DB satisfying query B with one matching farm from cache while the database would actually have 20 matching farms available. So, each query stll need to hit DB at least once.
And you need to track queries returning 0 results to avoid them consequently hitting the DB for nothing.
But all in all, you get away with a lot less code and as a bonus, when you update a farm, you can
var farm = (f => f.farmId == farmId).FromCache().First();
farm.Name = "My Test Farm";
var updatedFarm = farm.ToDatabase();
updatedFarm.ToCache();
What about this?
public class KeyGeneratorVisitor : ExpressionVisitor
{
protected override Expression VisitParameter(ParameterExpression node)
{
return Expression.Parameter(node.Type, node.Type.Name);
}
protected override Expression VisitMember(MemberExpression node)
{
if (CanBeEvaluated(node))
{
return Expression.Constant(Evaluate(node));
}
else
{
return base.VisitMember(node);
}
}
private static bool CanBeEvaluated(MemberExpression exp)
{
while (exp.Expression.NodeType == ExpressionType.MemberAccess)
{
exp = (MemberExpression) exp.Expression;
}
return (exp.Expression.NodeType == ExpressionType.Constant);
}
private static object Evaluate(Expression exp)
{
if (exp.NodeType == ExpressionType.Constant)
{
return ((ConstantExpression) exp).Value;
}
else
{
MemberExpression mexp = (MemberExpression) exp;
object value = Evaluate(mexp.Expression);
FieldInfo field = mexp.Member as FieldInfo;
if (field != null)
{
return field.GetValue(value);
}
else
{
PropertyInfo property = (PropertyInfo) mexp.Member;
return property.GetValue(value, null);
}
}
}
}
This will replace the complex constant expressions to their original values as well as the parameter names to their type names. So just have to create a new KeyGeneratorVisitor instance and call its Visit or VisitAndConvert method with your expression.
Please note that the Expression.ToString method will be also invoked on your complex types, so either override their ToString methods or write a custom logic for them in the Evaluate method.
How about:
var call = expression.Body as MethodCallExpression;
if (call != null)
{
List<object> list = new List<object>();
foreach (Expression argument in call.Arguments)
{
object o = Expression.Lambda(argument, expression.Parameters).Compile().DynamicInvoke();
list.Add(o);
}
StringBuilder keyValue = new StringBuilder();
keyValue.Append(expression.Body.ToString());
list.ForEach(e => keyValue.Append(String.Format("_{0}", e.ToString())));
string key = keyValue.ToString();
}
considering that fairly static data should not be re-evaluated but cached instead, I wondered if it is possible to use Reflection to obtain class properties once, and then cache them so that I could dynamically evaluate object properties and read/assign values, but not have the Reflection overhead every time I do that. Is this possible (Sample code?) ?
To clarify a bit, lets say I have this class:
public class Cloud
{
Boolean IsWhite;
}
and I'm trying to now make a method that allows me to do something like this (pseudocode):
Update(myCloudInstance, new {IsWhite, true});
Update should now check with the cache first if it knows already the properties of Cloud (typeof(myCloudInstance)), and then use cached information to assign the property "IsWhite" the value "true" instead of doing Reflection again.
Any ideas on how to do this?
It's not clear exactly what you're doing, but caching can certainly make a difference with reflection.
In particular, if you're invoking methods (or property getters/setters) and can do so in a type-safe way as far as the calling code is concerned, it can make a huge difference if you convert the MethodInfo into a strongly-typed delegate once and then reuse that.
If you could give us a complete example of what you're trying to do, that would help us to come up with more specific ideas or even code. If you're just going to cache a PropertyInfo that may not have as much (or any) effect - it's possible that the normal Type.GetProperty (etc) methods are already pretty fast. As ever with performance questions, the key is to measure what you're actually doing. Make a change and measure again, etc.
The cost of reflection does not need to be as big as you think. In addition to delegates (that Jon discusses) you can also use things like HyperDescriptor to minimise the cost of reflection without changing the code much - it simply becomes PropertyDescriptor instead:
PropertyDescriptorCollection props = TypeDescriptor.GetProperties(myCloudInstance);
// ideally cache props, but not essential
then
object val = props["IsWhite"].GetValue(myCloudInstance);
or if you use it lots, consider storing the PropertyDescriptor somewhere, too.
However... like Jon, I'm really not 100% sure what you're trying to do!
I created a hashtable to cache the reflection results. First time, it's neccessary to make a call to GetProperties and store the results into the hastable. Next times, first check the hashtable for the List of PropertyInfo objects. If exists, use it. If not, invoke GetProperties.
I use this to map a datareader to a List of Entities.
My implementation is based on: A Defense on Reflection in .Net, by Nick Harrison (http://www.simple-talk.com/dotnet/.net-framework/a-defense-of-reflection-in-.net/).
So, there it is:
public class MapeadorDataReaderListaObjetos
{
private Hashtable properties;
private Hashtable Properties
{
get
{
if (properties == null)
properties = new Hashtable();
return properties;
}
set { properties = value; }
}
private void LoadProperties(object targetObject, Type targetType)
{
var flags = BindingFlags.DeclaredOnly| BindingFlags.Instance| BindingFlags.Public;
if (properties == null)
{
List<PropertyInfo> propertyList = new List<PropertyInfo>();
PropertyInfo[] objectProperties = targetType.GetProperties(flags);
foreach (PropertyInfo currentProperty in objectProperties)
{
propertyList.Add(currentProperty);
}
properties = new Hashtable();
properties[targetType.FullName] = propertyList;
}
if (properties[targetType.FullName] == null)
{
List<PropertyInfo> propertyList = new List<PropertyInfo>();
PropertyInfo[] objectProperties = targetType.GetProperties(flags);
foreach (PropertyInfo currentProperty in objectProperties)
{
propertyList.Add(currentProperty);
}
properties[targetType.FullName] = propertyList;
}
}
public void MapearDataReaderListaObjetos <T> (IDataReader dr, List<T> lista) where T: new()
{
Type businessEntityType = typeof(T);
List<T> entitys = new List<T>();
T miObjeto = new T();
LoadProperties(miObjeto, businessEntityType);
List<PropertyInfo> sourcePoperties = Properties[businessEntityType.FullName] as List<PropertyInfo>;
while (dr.Read())
{
T newObject = new T();
for (int index = 0; index < dr.FieldCount; index++)
{
for (int _indice = 0; _indice < sourcePoperties.Count; _indice++)
{
if (sourcePoperties[_indice].Name.ToUpper() == dr.GetName(index).ToUpper());
{
string _tipoProp = sourcePoperties[_indice].PropertyType.ToString();
PropertyInfo info = sourcePoperties[_indice] as PropertyInfo;
if ((info != null) && info.CanWrite)
{
info.SetValue(newObject, dr.GetValue(index), null);
}
}
}
}
entitys.Add(newObject);
}
dr.Close();
lista = entitys;
}
}
Then, I call it from my DataAcces Layer, like this:
public List <Entities.ENFactura> ListaxIdFactura (SqlTransaction Tr, Entities.ENFactura oBEFactura)
{
SqlConnection Cn = new SqlConnection();
Cn = _Connection.ConexionSEG();
List<Entities.ENFactura> loBEFactura = new List<Entities.ENFactura>();
using (Cn)
{
Cn.Open();
SqlDataReader drd = (odaSQL.fSelDrd(Cn, Tr, "Pa_CC_Factura_Listar_x_IdProveedor", oBEFactura));
if (drd != null)
{
if (drd.HasRows)
{
mapeador.MapearDataReaderListaObjetos <ENFactura>(drd, loBEFactura);
}
}
}
return (loBEFactura);
}
So, this way, the DAL gets a datareader, map it to a list of business entities, and return it to the Business Logic Layer.
This class (MapeadorDataReaderListaObjetos) has some issues still, particularly at:
info.SetValue(newObject, _valor, null);
newObject and _valor must be the same type or you'll get an exception (conversion from System.Int64 to System.Int32, in case your entity property is Int32 and its corresponding field at the database table is bigint, for example).
Also, if an entity property is another entity, this will not work, because datareaders do not return entity objects.
Obviously, this can be improved.
Regarding reflection and delegates, i found this article: Reflection - Slow or Fast? Demonstration with Solutions, by Abhishek Sur, at
http://www.abhisheksur.com/2010/11/reflection-slow-or-faster-demonstration.html
Another good article is: Dodge Common Performance Pitfalls to Craft Speedy Applications, by Joel Pobar, at http://msdn.microsoft.com/en-us/magazine/cc163759.aspx.
Hope this helps.
I think the best way to do it is to get the getter or setter method, convert it to a delegate, and work with the delegate, there is no faster way:
PropertyInfo propertyInfoProperty1 = type.GetType().GetProperty("Property1");
Func<TYPE, string> get_Property1 = (Func<TYPE, string>)Delegate.CreateDelegate(typeof(Func<TYPE, string>), propertyInfoProperty1.GetGetMethod());
Then call the getter method:
string value = get_Property1(type);
You can cache the delegates.
Dynamic assembly should help with the concern about reflection performance. Somebody has implemented property assessors using dynamic assembly here.