我有一个用户将数据上传到其中的网站,我只想更新属性已更改的数据。所以我正在比较两个相同类型的对象进行更改,我需要排除一些属性,例如ModifiedOn,这是一个日期。
到目前为止,我的代码使用了反射:
private bool hasChanges(object OldObject, object newObject)
{
var oldprops = (from p in OldObject.GetType().GetProperties() select p).ToList();
var newprops = (from p in newObject.GetType().GetProperties() select p).ToList();
bool isChanged = false;
foreach (PropertyInfo i in oldprops)
{
if (checkColumnNames(i.Name))
{
var newInfo = (from x in newprops where x.Name == i.Name select x).Single();
var oldVal = i.GetValue(OldObject, null);
var newVal = newInfo.GetValue(newObject, null);
if (newVal == null || oldVal == null)
{
if (newVal == null && oldVal != null)
{
isChanged = true;
return true;
}
if (oldVal == null && newVal != null)
{
isChanged = true;
return true;
}
}
else
{
if (!newVal.Equals(oldVal))
{
isChanged = true;
return true;
}
}
}
}
return isChanged;
}
我使用此方法忽略某些列:
private bool checkColumnNames(string colName)
{
if (
colName.ToLower() == "productid" ||
colName.ToLower() == "customerid" ||
colName.ToLower() == "shiptoid" ||
colName.ToLower() == "parentchildid" ||
colName.ToLower() == "categoryitemid" ||
colName.ToLower() == "volumepricingid" ||
colName.ToLower() == "tagid" ||
colName.ToLower() == "specialprice" ||
colName.ToLower() == "productsmodifierid" ||
colName.ToLower() == "modifierlistitemid" ||
colName.ToLower() == "modifierlistid" ||
colName.ToLower() == "categoryitemid" ||
colName.ToLower() == "createdon" ||
colName.ToLower() == "createdby" ||
colName.ToLower() == "modifiedon" ||
colName.ToLower() == "modifiedby" ||
colName.ToLower() == "deletedon" ||
colName.ToLower() == "deletedby" ||
colName.ToLower() == "appendproductmodifiers" ||
colName.ToLower() == "introdate" ||
colName.ToLower() == "id" ||
colName.ToLower() == "discontinued" ||
colName.ToLower() == "stagingcategories"
)
return false;
return true;
}
这一直非常有效,但现在我让用户在一次上传中比较了50,000多个项目,这需要很长时间。
有没有更快的方法来实现这一目标?
答案 0 :(得分:5)
使用expression trees或dynamic methods编译和缓存代码。您可能会看到性能提升10-100倍。您的原始反射代码用于检索属性,您可以将其用作创建编译版本的基础。
示例强>
这是我在框架中使用的一段代码,用于读取对象的所有属性以跟踪状态更改。在这种情况下,我不知道该对象的任何属性名称。所有属性值都放在StringBuilder
。
我从原始代码中简化了这一点;它仍然可以编译,但你可能需要调整它。
private static DynamicMethod CreateChangeTrackingReaderIL( Type type, Type[] types )
{
var method = new DynamicMethod( string.Empty, typeof( string ), new[] { type } );
ILGenerator il = method.GetILGenerator();
LocalBuilder lbInstance = il.DeclareLocal( type );
// place the input parameter of the function onto the evaluation stack
il.Emit( OpCodes.Ldarg_0 );
// store the input value
il.Emit( OpCodes.Stloc, lbInstance );
// declare a StringBuilder
il.Emit( OpCodes.Newobj, typeof( StringBuilder ).GetConstructor( Type.EmptyTypes ) );
foreach( Type t in types )
{
// any logic to retrieve properties can go here...
List<PropertyInfo> properties = __Properties.GetTrackableProperties( t );
foreach( PropertyInfo pi in properties )
{
MethodInfo mi = pi.GetGetMethod();
if( null == mi )
{
continue;
}
il.Emit( OpCodes.Ldloc, lbInstance ); // bring the stored reference onto the eval stack
il.Emit( OpCodes.Callvirt, mi ); // call the appropriate getter method
if( pi.PropertyType.IsValueType )
{
il.Emit( OpCodes.Box, pi.PropertyType ); // box the return value if necessary
}
// append it to the StringBuilder
il.Emit( OpCodes.Callvirt, typeof( StringBuilder ).GetMethod( "Append", new Type[] { typeof( object ) } ) );
}
}
// call ToString() on the StringBuilder
il.Emit( OpCodes.Callvirt, typeof( StringBuilder ).GetMethod( "ToString", Type.EmptyTypes ) );
// return the last value on the eval stack (output of ToString())
il.Emit( OpCodes.Ret );
return method;
}
请注意,如果您不熟悉IL生成,大多数人会发现表达式树更容易使用。两种方法都有类似的结果。
答案 1 :(得分:0)
如果您只是使用反射来创建和编译使用上述逻辑的方法,那肯定会更快。这应该比反映每个对象快得多。
答案 2 :(得分:0)
对象是否保证具有相同的类型?即使没有,你可以检查它们,如果它们是相同的类型,请将它们发送到这个方法:
private bool hasChanges(object OldObject, object newObject)
{
var props = OldObject.GetType().GetProperties();
foreach (PropertyInfo i in props)
{
if (checkColumnNames(i.Name))
{
var oldVal = i.GetValue(OldObject, null);
var newVal = i.GetValue(newObject, null);
if (newVal == null)
{
if (oldVal != null)
{
return true;
}
}
else if (oldVal == null)
{
return true;
}
else if (!newVal.Equals(oldVal))
{
return true;
}
}
}
return false;
}
这比您的方法效率稍高。正如Tim Medora和PinnyM指出的那样,动态发出代码并缓存结果会更快,这意味着你只需要反射命中一次,而不是每个对象一次。
另请注意,根据Best Practices for Using Strings in the .NET Framework,您应该使用ToUpper而不是ToLower进行字符串比较,但您应该使用String.Equals(string, string, StringComparison)
而不是自己转换案例。这将有一个优势,至少:如果字符串长度不同,则Equals返回false,因此您跳过大小写转换。这也将节省一些时间。
另一个想法:
如果对象的类型不同,您仍然可以通过加入属性集合而不是使用重复的线性搜索来改进算法:
private bool hasChanges(object OldObject, object newObject)
{
var oldprops = OldObject.GetType().GetProperties();
var newprops = newObject.GetType().GetProperties();
var joinedProps = from oldProp in oldprops
join newProp in newProps
on oldProp.Name equals newProp.Name
select new { oldProp, newProp }
foreach (var pair in joinedProps)
{
if (checkColumnNames(pair.oldProp.Name))
{
var oldVal = pair.oldProp.GetValue(OldObject, null);
var newVal = pair.newProp.GetValue(newObject, null);
//etcetera
最后的想法(灵感来自Tim Schmelter的评论):
在您的课程上覆盖object.Equals
,然后使用
private bool HasChanges(object o1, object o2) { return !o1.Equals(o2); }
示例类:
class SomeClass
{
public string SomeString { get; set; }
public int SomeInt { get; set; }
public DateTime SomeDateTime { get; set; }
public bool Equals(object other)
{
SomeClass other1 = other as SomeClass;
if (other1 != null)
return other1.SomeInt.Equals(SomeInt)
&& other1.SomeDateTime.Equals(SomeDateTime)
&& other1.SomeString.Equals(SomeString); //or whatever string equality check you prefer
//possibly check for other types here, if necessary
return false;
}
}