I have implemented a deep copy method, when stepping into this method - the class is copied as expected.
However if i step over the method and inspect the class instance, some of the properties are not copied correctly.
Profile profile = new Profile();
profile = Model.Copy<Profile>(Profile.GetProfileById(ProfileID));
profile.Save();
The above code shows where the class instance is created and the copy is called.
Conforming with the database, the profile I require to copy has 2 items in the AddressCollection property.
If I put a breakpoint on the line profile.Save() and step over the copy method, the instance isn't copied properly and the AddressCollection property has 0 items.
However if i actually step into the copy method, the instance is copied properly and returns the AddressCollection with 2 items.
Copy Method
public T Copy<T>(T oldObject) where T : CRMBusinessObjectBase
{
return Copy<T>(oldObject, null, null);
}
public T Copy<T>(T oldObject, CRMBusinessObjectBase parentObject, string parentProperty) where T : CRMBusinessObjectBase
{
//Create copy of business object
T copy = null;
System.Runtime.Serialization.Formatters.Binary.BinaryFormatter sz = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
//Serialize and then deserialize to a new object
using (MemoryStream ms = new MemoryStream())
{
sz.Serialize(ms, oldObject);
ms.Position = 0;
copy = (T)sz.Deserialize(ms);
}
//Set business object as new
copy.SetAsNew();
//Set parent property if specified
if (parentObject != null && !string.IsNullOrEmpty(parentProperty))
{
copy.GetType().GetProperty(parentProperty).SetValue(copy, parentObject, null);
}
return copy;
}
I have tried changing the BinaryFormatter to use a DataContractSerializer without any luck.
I have also tried to use reflection instead of serialization and the same problem occurs.
Not too sure where the problem could be.
Screenshots
Before the copy
After the copy
This issue is likely related to the use of the Watch window, or hovering over variables while debugging.
See http://dotdotnet.blogspot.com.au/2010/04/lazy-load-eager-debugging.html :
Another thing is that debugger and specifically Visual Studio Watch
window is not so friendly to lazy loading because the Watch window try
to evaluate the value of each property which cause all the lazy
loading properties to be loaded.
Related
I need to access a specific property inside a COM object (the iTunes COM Library). You can access this property with the dynamic view of the Visual Studio debugger.
I tried to get this property using Reflection but I don't get any private properties or fields back.
I can access all the Properties that I also see in the debugger using this line:
new Microsoft.CSharp.RuntimeBinder.DynamicMetaObjectProviderDebugView(myObject).Items
However, I would rather not use this call because I believe an easier solution exists.
If you have iTunes installed this would be a simple example of what I'm trying to achieve:
iTunesAppClass app;
if (Process.GetProcessesByName("iTunes").Any())
{
app = new iTunesAppClass();
}
else
{
return;
}
foreach (IITPlaylist playlist in app.LibrarySource.Playlists)
{
// This does not work. There is no "Parent".
//var parent = playlist.Parent;
Type playListType = playlist.GetType();
// both contain 0 results
var fields = playListType.GetFields(BindingFlags.NonPublic);
var properties = playListType.GetFields(BindingFlags.NonPublic);
// works but only during runtime
//var parent2 = new Microsoft.CSharp.RuntimeBinder.DynamicMetaObjectProviderDebugView(playlist).Items[4];
}
In Revit 2013 I have tool that I'm making that copies dimensions from one drafting view to another. I've got it to properly create a new version of a dimension including the Curve, DimensionType, and References but I'm having trouble with the properties Above, Below, Prefix, and Suffix. They copy just fine if at least one of them has a value. However, if none of them have a value then it will throw an AccessViolationException when I try to access them. I have tried to catch that exception but it bubbles up and it crashes Revit (I'm assuming it's caused by native code that fails).
How can I check to see if these properties have any value when I do my copying without triggering this AccessViolationException?
Autodesk Discussion Group Question
The DimensionData class is my own used for storing the dimension information so that it can be used to create the dimension in a separate document.
private IEnumerable<DimensionData> GetDimensionDataSet(Document document,
View view)
{
if (document == null)
throw new ArgumentNullException("document");
if (view == null)
throw new ArgumentNullException("view");
List<DimensionData> dimensionDataSet = new List<DimensionData>();
FilteredElementCollector dimensionCollector =
new FilteredElementCollector(document, view.Id);
dimensionCollector.OfClass(typeof(Dimension));
foreach (Dimension oldDimension in dimensionCollector)
{
Line oldDimensionLine = (Line)oldDimension.Curve;
string dimensionTypeName = oldDimension.DimensionType.Name;
List<ElementId> oldReferences = new List<ElementId>();
foreach (Reference oldReference in oldDimension.References)
oldReferences.Add(oldReference.ElementId);
DimensionData dimensionData;
try
{
string prefix = oldDimension.Prefix;
dimensionData = new DimensionData(oldDimensionLine,
oldReferences,
dimensionTypeName,
prefix,
oldDimension.Suffix,
oldDimension.Above,
oldDimension.Below);
}
catch (AccessViolationException)
{
dimensionData = new DimensionData(oldDimensionLine,
oldReferences, dimensionTypeName);
}
dimensionDataSet.Add(dimensionData);
}
return dimensionDataSet;
}
Regarding transactions: As far as I'm aware, you are only required to be inside a transaction when you are making any sort of CHANGE (modifications, deletions, additions). If all you are doing is collecting dimension information, you would not need a transaction, but when you use that information to create new dimensions in another document, that code would have to be inside a transaction. I have had a number of programs under development which did not yet modify the document but simply collected parameter settings and posted them to a TaskDialog.Show(). These programs worked fine, and I don't see anything in your code that actually modifies your model, so that doesn't seem to be your issue.
It seems like I bug.
Can you post the issue to the ADN Support?
The solution I can suggest is to use Parameters of the Dimension element instead of Dimension class properties.
For example, you can get Suffix and Prefix by following code
var suffixParameter =
oldDimension.get_Parameter(BuiltInParameter.SPOT_SLOPE_SUFFIX);
string suffix = null;
if (suffixParameter != null)
{
suffix = suffixParameter.AsString();
}
var prefixParameter =
oldDimension.get_Parameter(BuiltInParameter.SPOT_SLOPE_PREFIX);
string prefix = null;
if (prefixParameter != null)
{
prefix = prefixParameter.AsString();
}
Unfortunatelly, I don't tell you how to get Above and Below Properties via parameters, because I don't have a project to test. But you can easily determine parameters using BuiltInParameter Checker
Hope it helps.
I'm storing some common settings used in a C# program inside the ApplicationSettings. Most of the settings are strings, but one of them is a StringCollection.
A null exception is occurring in some code that loops through the default settings at Properties.Settings.Default and adds them to a dictionary (in preparation to send as parameters), as shown below.
// Generate parameters
Dictionary<string, string> signalparams = new Dictionary<string, string>();
// Add parameters
foreach (SettingsProperty property in Properties.Settings.Default.Properties)
{
SettingsPropertyValue value = new SettingsPropertyValue(property);
if (value.Property.SerializeAs == SettingsSerializeAs.Xml)
{
// Here's where the error occurs
signalparams.Add(value.Name, value.SerializedValue.ToString());
}
else if (value.Property.SerializeAs == SettingsSerializeAs.String)
{
signalparams.Add(value.Name, value.PropertyValue.ToString());
}
}
The strings settings are added fine, but when it reaches the StringCollection, it (correctly) evaluates that the property has SerializeAs == SettingsSerializeAs.Xml. However, the SerializedValue is null, and thus ToString() throws an exception.
The strange thing is that when running the debugger, the SerializedValue is null until I try viewing the value variable's properties in the locals windows. At that point, SerializedValue contains the correct XML serialized format for the StringCollection, and the program continues fine.
Why is this happening?
The issue stems probably from the fact that SerializedValue is a property that is implemented something like that:
public object SerializedValue
{
get
{
if (this._ChangedSinceLastSerialized)
return CalculateSerializedData();
else
return _cachedData;
}
}
What happens in your case, in my opinion, is that you're getting _cachedData when you first access the SettingPropertyValue, then, by viewing value via the debugger, you cause _ChangedSinceLastSerialized to be true which causes the next call to SerializedValue property to return the actual serialized value.
The next question is to find out why _ChangedSinceLastSerialized in your case is set to false. The logic of SettingsPropertyValue says (you can see it in Reflector in PropertyValue property of the class) that _ChangedSinceLastSerialized is set to true when the user either accesses the settings (in case of most object types), so for example merely accessing your setting like that: MyAppSettings.Default.MySettingObject would change _ChangedSinceLastSerialized to true.
What could happen in your case that you have code similar to this:
object storingObject = MyAppSettings.Default.MySettingObject;
// Now I will use storingObject to access the setting.
Once you do something like that and keep using storingObject instead of directly accessing MyAppSettings you can create a situation in which the object changed while _ChangedSinceLastSerialized remains false.
I hope that was helpful.
I actually dropped trying to use SettingsPropertyValue and decided to serialize the object to XML manually. I simply used the SettingsProperty.Name as a key to retrieve the values of the settings stored in Properties.Settings.Default, since it is a dictionary-like object. It fixed the issue, but of course the root cause still remains unknown. The revised code looks something like this:
// Generate parameters
Dictionary<string, string> signalparams = new Dictionary<string, string>();
// Add parameters
foreach (SettingsProperty property in Properties.Settings.Default.Properties)
{
if (property.SerializeAs == SettingsSerializeAs.String)
{
signalparams.Add(property.Name, Properties.Settings.Default[property.Name].ToString());
}
else if (property.SerializeAs == SettingsSerializeAs.Xml)
{
// Serialize collection into XML manually
XmlSerializer serializer = new XmlSerializer(Properties.Settings.Default[property.Name].GetType());
StringBuilder sb = new StringBuilder();
XmlWriter writer = XmlWriter.Create(sb);
serializer.Serialize(writer, Properties.Settings.Default[property.Name]);
writer.Close();
signalparams.Add(property.Name, sb.ToString());
}
}
In our app, we need to save properties of objects to the same database table regardless of the type of object, in the form of propertyName, propertyValue, propertyType. We decided to use XamlWriter to save all of the given object's properties. We then use XamlReader to load up the XAML that was created, and turn it back into the value for the property. This works fine for the most part, except for empty strings. The XamlWriter will save an empty string as below.
<String xmlns="clr-namespace:System;assembly=mscorlib" xml:space="preserve" />
The XamlReader sees this string and tries to create a string, but can't find an empty constructor in the String object to use, so it throws a ParserException.
The only workaround that I can think of is to not actually save the property if it is an empty string. Then, as I load up the properties, I can check for which ones did not exist, which means they would have been empty strings.
Is there some workaround for this, or is there even a better way of doing this?
We had a similar problem to this when trying to serialize strings. The only way we could resolve it was to create a StringWrapper struct or class that had the appropriate constructors. We then used this type to load and save our string values.
I also got the problem and searched the web for a solution but could not find one.
I solved it by inspecting the saved XML and fixing the empty strings, like this (feed FixSavedXaml with the output from XamlWriter):
static string FixSavedXaml(string xaml)
{
bool isFixed = false;
var xmlDocument = new System.Xml.XmlDocument();
xmlDocument.LoadXml(xaml);
FixSavedXmlElement(xmlDocument.DocumentElement, ref isFixed);
if (isFixed) // Only bothering with generating new xml if something was fixed
{
StringBuilder xmlStringBuilder = new StringBuilder();
var settings = new System.Xml.XmlWriterSettings();
settings.Indent = false;
settings.OmitXmlDeclaration = true;
using (var xmlWriter = System.Xml.XmlWriter.Create(xmlStringBuilder, settings))
{
xmlDocument.Save(xmlWriter);
}
return xmlStringBuilder.ToString();
}
return xaml;
}
static void FixSavedXmlElement(System.Xml.XmlElement xmlElement, ref bool isFixed)
{
// Empty strings are written as self-closed element by XamlWriter,
// and the XamlReader can not handle this because it can not find an empty constructor and throws an exception.
// To fix this we change it to use start and end tag instead (by setting IsEmpty to false on the XmlElement).
if (xmlElement.LocalName == "String" &&
xmlElement.NamespaceURI == "clr-namespace:System;assembly=mscorlib")
{
xmlElement.IsEmpty = false;
isFixed = true;
}
foreach (var childElement in xmlElement.ChildNodes.OfType<System.Xml.XmlElement>())
{
FixSavedXmlElement(childElement, ref isFixed);
}
}
This is a 2 parter.
First I am having a heck of a time getting the paste part of a copy and paste operation to work.
I have a method that copies information to the Clipboard, works perfectly.
private void CopyData(string format, object data, string text)
{
bool addedData = false;
DataObject copyData = new DataObject();
if (!string.IsNullOrEmpty(text))
{
copyData.SetData(DataFormats.Text, text);
addedData = true;
}
if (!string.IsNullOrEmpty(format) && data != null)
{
copyData.SetData(format, false, data);
addedData = true;
//this is only for testing
object obj = null;
if (copyData.GetDataPresent(format))
obj = (object)copyData.GetData(format);
}
if (addedData)
Clipboard.SetDataObject(copyData, true);
}
When I check that the data was added the object (obj) is not null.
However when I then go to paste the data from a different method using the same format key I get null, every time.
private void PasteFromClipboard()
{
object obj = null;
IDataObject paste = null;
if (Clipboard.GetDataObject().GetDataPresent("mydatatype"))
obj = (object)Clipboard.GetDataObject().GetData("mydatatype");
else
return;
if (obj == null)
throw new NullReferenceException("Could not gather information from the
}
I have tried everything that I can think of and it just doesn't make sense. I created an array of strings to capture all of the format keys the DataObject was holding and "mydatatype" was the first one. I have tried casting, not casting, using (Clipboard.GetDataObject().GetData("mydatatype") as object) and I just cannot figure it out. I know that there is data there because I can go to NotePad and paste the text that I copied along with the object.
Any thoughts as to why I would be able to get the data in one method, but not another?
Secondly I was wondering how I would go about making a Cut & Paste operation work between two of my windows. I am thinking about something like Excel, where if only the text is pasted the data will remain, however if the objects are pasted then the source will be deleted.
Thanks
Patrick.
Try pulling the data out as Text (instead of "mydatatype") - at least to confirm you can read from the clipboard. This is most likely what Notepad is reading. Also, does it matter that you're copying with "format" but pasting with "mydatatype"?
Could it be that text parameter always has a value and gets set. Then possibly the second if the one that would set the object does not get executed. Or if it does since the data was set in the first if statement the second set fails to set it correctly.
My recommendation would be to walk the code in the debugger during the copy operation.
Before the paste use GetDataObject().GetFormats() to enumerate the list of formatting codes.
Perhaps your using the wrong one.. just an idea
Try using reflection like this:
private static T TryGetClipboardData<T>(IDataObject clipboardData, string dataFormat)
{
System.Reflection.FieldInfo fieldInfo = clipboardData.GetType().GetField("innerData", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
var outerData = fieldInfo.GetValue(clipboardData);
if (outerData == null)
{
return default(T);
}
fieldInfo = outerData.GetType().GetField("innerData", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
var innerData = fieldInfo.GetValue(outerData);
if (innerData is System.Runtime.InteropServices.ComTypes.IDataObject)
{
// It is (probably) necessary to wrap COM IDataObject to Windows.Forms.IDataObject
System.Windows.Forms.DataObject wrappedDataObject = new System.Windows.Forms.DataObject(innerData);
var data = wrappedDataObject.GetData(dataFormat);
if (data is T)
{
return (T)data;
}
}
return default(T);
}
I suspect the COM object of your data in the Clipboard had a difficult time converting itself to the format you specified. I'm also playing safe with the input format string so that it is registered as a proper clipboard format.
HTH