1: using System.Collections.Generic;
2: using System.IO;
3: using System.Runtime.Serialization.Formatters.Binary;
4: using System.Text;
5: using System;
6: using System.Reflection;
7: using System.Diagnostics;
8:
9: namespace SubPropertyColumns {
10: /// <summary>
11: /// This class is used to access a list of child properties as an indexed property of the parent.<br/>
12: /// The child properties will be accessible via a <code>ParentObject.AccessorProperty[Key] = Value</code> syntax.<br/>
13: /// This class assumes that the values in the sub attributes have been seralized using a binary formatter.
14: /// If the value in the sub attribute cannot be de-serialized, it is returned as a string.
15: /// </summary>
16: /// <typeparam name="T">The type of the object that will hold the attributes.</typeparam>
17: public class SubAttributeAccessor<T> {
18: private const string STR_MissingParentIEnumerable =
19: "Class \"{0}\" must have a property named \"{1}\" that must return an object that implements IEnumberable<{0}>";
20:
21: private const string STR_MissingParentIList =
22: "Class \"{0}\" must have a property named \"{1}\" that must return an object that implements IList<{0}>";
23:
24: private const string STR_MissingKey =
25: "Class \"{0}\" must have a property named \"{1}\" that is of type string. Check the property name or return type.";
26:
27: private const string STR_StrMissingValue =
28: "Class \"{0}\" must have a property named \"{1}\". Check the property name.";
29:
30: /// <summary>
31: /// The accessor must have a reference to a parent object in order to access its properties
32: /// </summary>
33: private object fParent = null;
34:
35: /// <summary>
36: /// The property on the parent object that holds a list of child objects.</br>
37: /// This list of child objects holds the sub attribute keys and values.</br>
38: /// Each child object must hold a key and a value.
39: /// </summary>
40: private string fPropertyName = "";
41:
42: /// <summary>
43: /// The property on the child object that holds the key of the sub attribute.
44: /// </summary>
45: private string fKeyPropertyName = "";
46:
47: /// <summary>
48: /// The property on the child object that will hold the actual value of the sub attribute.
49: /// </summary>
50: private string fValuePropertyName = "";
51:
52: private PropertyInfo fParentPropertyInfo;
53: private PropertyInfo fKeyPropertyInfo;
54: private PropertyInfo fValuePropertyInfo;
55:
56: private IEnumerable<T> fAttributeEnumeration;
57: private IList<T> fAttributeList;
58:
59: /// <summary>
60: /// Constructor for the SubAttributeAccessor
61: /// </summary>
62: /// <param name="parent">The main object that will be accessed using this accessor.</param>
63: /// <param name="propertyName">The property on the parent object that holds a list of child objects.</param>
64: /// <param name="fieldName">The property on the child object that will be the key of the sub attributes.</param>
65: /// <param name="valueName">The property on the child object that will be the value of the sub attributes.</param>
66: public SubAttributeAccessor(object parent,
67: string propertyName,
68: string fieldName,
69: string valueName) {
70:
71: fParent = parent;
72: fPropertyName = propertyName;
73: fKeyPropertyName = fieldName;
74: fValuePropertyName = valueName;
75:
76: Type parentType = fParent.GetType();
77: Type itemType = typeof(T);
78:
79: //PropertyInfo for the property on the parent object that holds the list of attribute objects
80: fParentPropertyInfo = parentType.GetProperty(fPropertyName);
81:
82: if ( fParentPropertyInfo == null
83: || !typeof(IEnumerable<T>).IsAssignableFrom(fParentPropertyInfo.PropertyType)) {
84:
85: throw new ArgumentException(
86: string.Format(STR_MissingParentIEnumerable, typeof(T).Name, fPropertyName));
87: }
88:
89: if (!typeof(IList<T>).IsAssignableFrom(fParentPropertyInfo.PropertyType)) {
90:
91: throw new ArgumentException(
92: string.Format(STR_MissingParentIList,
93: typeof(T).Name, fPropertyName));
94: }
95:
96:
97: //PropertyInfo for the property on the child attribute obeject that holds the key for the attribute
98: fKeyPropertyInfo = itemType.GetProperty(fKeyPropertyName);
99: if (fKeyPropertyInfo == null || fKeyPropertyInfo.PropertyType != typeof(string)) {
100:
101: throw new ArgumentException(
102: string.Format(STR_MissingKey, typeof(T).Name, fKeyPropertyName));
103: }
104:
105: //PropertyInfo for the property on the child attribute objects that holds the value for the attribute
106: fValuePropertyInfo = itemType.GetProperty(fValuePropertyName);
107: if (fValuePropertyInfo == null) {
108:
109: throw new ArgumentException(
110: string.Format(STR_StrMissingValue, typeof(T).Name, fValuePropertyName));
111: }
112:
113: object propertyObject = fParentPropertyInfo.GetValue(fParent, new object[] { });
114:
115: if (propertyObject is IEnumerable<T>) {
116: fAttributeEnumeration = (IEnumerable<T>)propertyObject;
117: }
118:
119: if (propertyObject is IList<T>) {
120: fAttributeList = (IList<T>)propertyObject;
121: }
122:
123: }
124:
125: /// <summary>
126: /// A Dictionary to organize the attributes by their name
127: /// </summary>
128: private Dictionary<string, T> fAttributeDictionary = null;
129:
130: /// <summary>
131: /// A Dictionary to organize the attributes by their name.<br/>
132: /// This property ensures that the Dictionary is
133: /// created and populated the first time it is accessed
134: /// </summary>
135: private Dictionary<string, T> AttributeDictionary {
136: get {
137: if (fAttributeDictionary == null) {
138: fAttributeDictionary = new Dictionary<string, T>();
139:
140: foreach (T attribute in fAttributeEnumeration) {
141: string fieldNameObject = fKeyPropertyInfo.GetValue(attribute, new object[] { }) as string;
142: fAttributeDictionary.Add(fieldNameObject, attribute);
143: }
144: }
145:
146: return fAttributeDictionary;
147: }
148: }
149:
150: /// <summary>
151: /// This is the indexed property that provides access to
152: /// the list of child attributes.
153: /// </summary>
154: /// <param name="index">The value of the key property on the object to be looked up</param>
155: /// <returns>The value of the value property on the object that has the matching key property</returns>
156: public object this[string index] {
157: get {
158: object result = null;
159:
160: //if the value for a given key exists, it should be in the dictionary
161: if (AttributeDictionary.ContainsKey(index)) {
162:
163: //Assume that the value in the attribute has been serialized using a binary formatter
164:
165: //create a memory stream for storing the binary data
166: MemoryStream stream = new MemoryStream();
167:
168: //attribute is the object that contains the key and value properties
169: T attribute = AttributeDictionary[index];
170:
171: //valueObject is the data that was stored in the value property of the value object
172: object valueObject = fValuePropertyInfo.GetValue(attribute, new object[] { });
173:
174: //if valueObject is indeed an array of bytes, store it in a strongly typed object.
175: byte[] value = new byte[] { };
176: if (valueObject is byte[]) {
177: value = (byte[])valueObject;
178: }
179:
180:
181: try {
182: //write the binary data from the database into a stream
183: stream.Write(value, 0, value.Length);
184: stream.Position = 0;
185:
186: //use the BinaryFormatter to deserialize the object
187: BinaryFormatter formatter = new BinaryFormatter();
188:
189: //result is the deserialized data from the value property of the object
190: //with the key property matching the specified index
191: result = formatter.Deserialize(stream);
192: }
193:
194: catch {
195: //if something goes wrong, get a string representation
196: //of the binary data instead
197: StringBuilder sb = new StringBuilder();
198: foreach (byte aByte in value) {
199: sb.Append((char)aByte);
200: }
201:
202: //result is the string represetation of the data from the value property of the object
203: //with the key property matching the specified index
204: result = sb.ToString();
205: }
206:
207: finally {
208: //no matter what happens we should always close the stream.
209: stream.Close();
210: }
211:
212: }
213:
214: return result;
215: }
216:
217: set {
218: //create a memory stream to hold the data as we serialize it
219: MemoryStream stream = new MemoryStream();
220:
221: try {
222: //use the BinaryFormatter to serialize the object into a stream
223: BinaryFormatter formatter = new BinaryFormatter();
224: formatter.Serialize(stream, value);
225:
226: //put the stream into a buffer
227: stream.Position = 0;
228: byte[] buffer = new byte[stream.Length];
229: stream.Read(buffer, 0, (int)stream.Length);
230:
231:
232: //if the dictionary already contains an entry for this key
233: //just use it
234: if (AttributeDictionary.ContainsKey(index)) {
235: T attribute = AttributeDictionary[index];
236:
237: //put the binary data buffer into the value property of the attribute object
238: //with the key property that matches
239: fValuePropertyInfo.SetValue(attribute, buffer, new object[] { });
240: }
241:
242: else {
243: //if this is a new attribute we need to put the value into
244: //a new attribute and put the new attribute into the parent object
245: //as well as into the Dictionary
246:
247:
248: //Create a new attribute object
249: T newAttribute = Activator.CreateInstance<T>();
250:
251: //set the value for the key property
252: fKeyPropertyInfo.SetValue(newAttribute, index, new object[] { });
253:
254: //set the value for the value property
255: fValuePropertyInfo.SetValue(newAttribute, buffer, new object[] { });
256:
257: //add the attribute to the parent object
258: fAttributeList.Add(newAttribute);
259:
260: //put the attribute into the dictionary
261: AttributeDictionary.Add(index, newAttribute);
262: }
263: }
264: finally {
265: //always close the stream
266: stream.Close();
267: }
268:
269: }
270: }
271:
272: }
273: }