2009-07-11 Michael Barker <mike@middlesoft.co.uk>
[mono.git] / mcs / class / System.Data.Linq / src / DbLinq / Data / Linq / EntitySet.cs
1 #region MIT license\r
2 // \r
3 // MIT license\r
4 //\r
5 // Copyright (c) 2007-2008 Jiri Moudry, Pascal Craponne\r
6 // \r
7 // Permission is hereby granted, free of charge, to any person obtaining a copy\r
8 // of this software and associated documentation files (the "Software"), to deal\r
9 // in the Software without restriction, including without limitation the rights\r
10 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r
11 // copies of the Software, and to permit persons to whom the Software is\r
12 // furnished to do so, subject to the following conditions:\r
13 // \r
14 // The above copyright notice and this permission notice shall be included in\r
15 // all copies or substantial portions of the Software.\r
16 // \r
17 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r
18 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r
19 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r
20 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r
21 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r
22 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r
23 // THE SOFTWARE.\r
24 // \r
25 #endregion\r
26 \r
27 using System;\r
28 using System.Collections;\r
29 using System.Collections.Generic;\r
30 using System.Text;\r
31 using System.Linq;\r
32 using System.Linq.Expressions;\r
33 using System.ComponentModel;\r
34 using DbLinq;\r
35 \r
36 #if MONO_STRICT\r
37 namespace System.Data.Linq\r
38 #else\r
39 namespace DbLinq.Data.Linq\r
40 #endif\r
41 {\r
42     public sealed class EntitySet<TEntity> : ICollection, ICollection<TEntity>, IEnumerable, IEnumerable<TEntity>, IList, IList<TEntity>, IListSource\r
43         where TEntity : class\r
44     {\r
45         private readonly Action<TEntity> onAdd;\r
46         private readonly Action<TEntity> onRemove;\r
47 \r
48         private IEnumerable<TEntity> deferredSource;\r
49         private bool deferred;\r
50                 private bool assignedValues;\r
51         private List<TEntity> source;\r
52         private List<TEntity> Source\r
53         {\r
54             get\r
55             {\r
56                 if (source != null)\r
57                     return source;\r
58                 if (deferredSource != null)\r
59                     return source = deferredSource.ToList();\r
60                 return source = new List<TEntity>();\r
61             }\r
62         }\r
63 \r
64 \r
65         /// <summary>\r
66         /// Gets or sets a value indicating whether this instance has loaded or assigned values.\r
67         /// </summary>\r
68         /// <value>\r
69         ///     <c>true</c> if this instance has loaded or assigned values; otherwise, <c>false</c>.\r
70         /// </value>\r
71         public bool HasAssignedValues\r
72         {\r
73                         get { return assignedValues; }\r
74         }\r
75 \r
76         public bool HasLoadedValues\r
77         {\r
78             get { return source != null; }\r
79         }\r
80 \r
81         /// <summary>\r
82         /// Gets a value indicating whether this instance has loaded or assigned values.\r
83         /// </summary>\r
84         /// <value>\r
85         ///     <c>true</c> if this instance has loaded or assigned values; otherwise, <c>false</c>.\r
86         /// </value>\r
87         public bool HasLoadedOrAssignedValues\r
88         {\r
89             get { return HasLoadedValues || HasAssignedValues; }\r
90         }\r
91 \r
92         /// <summary>\r
93         /// Initializes a new instance of the <see cref="EntitySet&lt;TEntity&gt;"/> class.\r
94         /// </summary>\r
95         /// <param name="onAdd">The on add.</param>\r
96         /// <param name="onRemove">The on remove.</param>\r
97         [DbLinqToDo]\r
98         public EntitySet(Action<TEntity> onAdd, Action<TEntity> onRemove)\r
99             : this()\r
100         {\r
101             this.onAdd = onAdd;\r
102             this.onRemove = onRemove;\r
103         }\r
104 \r
105         /// <summary>\r
106         /// Initializes a new instance of the <see cref="EntitySet&lt;TEntity&gt;"/> class.\r
107         /// </summary>\r
108         public EntitySet()\r
109         {\r
110         }\r
111 \r
112         /// <summary>\r
113         /// entry point for 'foreach' statement.\r
114         /// </summary>\r
115         public IEnumerator<TEntity> GetEnumerator()\r
116         {\r
117             deferred = false;\r
118             return Source.GetEnumerator();\r
119         }\r
120 \r
121         /// <summary>\r
122         /// Enumerates all entities\r
123         /// </summary>\r
124         /// <returns></returns>\r
125         IEnumerator IEnumerable.GetEnumerator()\r
126         {\r
127             return GetEnumerator();\r
128         }\r
129 \r
130         /// <summary>\r
131         /// Gets the source expression (used to nest queries)\r
132         /// </summary>\r
133         /// <value>The expression.</value>\r
134         internal Expression Expression\r
135         {\r
136             get\r
137             {\r
138                 if (deferred && this.deferredSource is IQueryable<TEntity>)\r
139                     return (deferredSource as IQueryable<TEntity>).Expression;\r
140                 else\r
141                     return Expression.Constant(this);\r
142             }\r
143         }\r
144 \r
145         /// <summary>\r
146         /// Adds a row\r
147         /// </summary>\r
148         public void Add(TEntity entity)\r
149         {\r
150             if (entity == null)\r
151                 throw new ArgumentNullException("entity");\r
152 \r
153             if (Source.Contains (entity))\r
154                 return;\r
155             Source.Add(entity);\r
156             OnAdd(entity);\r
157             ListChangedEventHandler handler = ListChanged;\r
158             if (!deferred && deferredSource != null && handler != null)\r
159                 handler(this, new ListChangedEventArgs(ListChangedType.ItemAdded, Source.Count - 1));\r
160         }\r
161 \r
162         [DbLinqToDo]\r
163         bool IListSource.ContainsListCollection\r
164         {\r
165             get { throw new NotImplementedException(); }\r
166         }\r
167 \r
168         IList IListSource.GetList()\r
169         {\r
170                         //It seems that Microsoft is doing a similar thing in L2SQL, matter of fact, after doing a GetList().Add(new TEntity()), HasAssignedValues continues to be false\r
171                         //This seems like a bug on their end, but we'll do the same for consistency\r
172             return this;\r
173         }\r
174 \r
175         #region IList<TEntity> Members\r
176 \r
177         /// <summary>\r
178         /// Returns entity's index\r
179         /// </summary>\r
180         /// <param name="entity">The entity.</param>\r
181         /// <returns></returns>\r
182         public int IndexOf(TEntity entity)\r
183         {\r
184             deferred = false;\r
185             return Source.IndexOf(entity);\r
186         }\r
187 \r
188         /// <summary>\r
189         /// Inserts entity at specified index.\r
190         /// </summary>\r
191         /// <param name="index">The index.</param>\r
192         /// <param name="entity">The entity.</param>\r
193         public void Insert(int index, TEntity entity)\r
194         {\r
195             if (Source.Contains(entity))\r
196                 throw new ArgumentOutOfRangeException();\r
197             OnAdd(entity);\r
198             deferred = false;\r
199             Source.Insert(index, entity);\r
200             ListChangedEventHandler handler = ListChanged;\r
201             if (handler != null)\r
202                 handler(this, new ListChangedEventArgs(ListChangedType.ItemAdded, index));\r
203         }\r
204 \r
205         /// <summary>\r
206         /// Removes entity at specified index\r
207         /// </summary>\r
208         /// <param name="index"></param>\r
209         public void RemoveAt(int index)\r
210         {\r
211             deferred = false;\r
212             var item = Source[index];\r
213             Source.RemoveAt(index);\r
214             OnRemove(item);\r
215             ListChangedEventHandler handler = ListChanged;\r
216             if (handler != null)\r
217                 handler(this, new ListChangedEventArgs(ListChangedType.ItemDeleted, index));\r
218         }\r
219 \r
220         /// <summary>\r
221         /// Gets or sets the <see cref="TEntity"/> at the specified index.\r
222         /// </summary>\r
223         /// <value></value>\r
224         public TEntity this[int index]\r
225         {\r
226             get\r
227             {\r
228                 deferred = false;\r
229                 return Source[index];\r
230             }\r
231             set\r
232             {\r
233                 OnRemove(Source[index]);\r
234                 OnAdd(value);\r
235                 deferred = false;\r
236                 var handler = ListChanged;\r
237                 if (handler != null)\r
238                 {\r
239                     handler(this, new ListChangedEventArgs(ListChangedType.ItemDeleted, index));\r
240                     handler(this, new ListChangedEventArgs(ListChangedType.ItemAdded, index));\r
241                 }\r
242                 Source[index] = value;\r
243             }\r
244         }\r
245 \r
246         #endregion\r
247 \r
248         #region ICollection<TEntity> Members\r
249 \r
250         /// <summary>\r
251         /// Removes all items in collection\r
252         /// </summary>\r
253         public void Clear()\r
254         {\r
255             ListChangedEventHandler handler = ListChanged;\r
256             deferred = false;\r
257                         assignedValues = true;\r
258             if (deferredSource != null && handler != null)\r
259             {\r
260                 foreach (var item in Source)\r
261                     handler(this, new ListChangedEventArgs(ListChangedType.ItemDeleted, 0));\r
262             }\r
263             if (handler != null)\r
264                 handler(this, new ListChangedEventArgs(ListChangedType.Reset, 0));\r
265             Source.Clear();\r
266         }\r
267 \r
268         /// <summary>\r
269         /// Determines whether [contains] [the specified entity].\r
270         /// </summary>\r
271         /// <param name="entity">The entity.</param>\r
272         /// <returns>\r
273         ///     <c>true</c> if [contains] [the specified entity]; otherwise, <c>false</c>.\r
274         /// </returns>\r
275         [DbLinqToDo]\r
276         public bool Contains(TEntity entity)\r
277         {\r
278             deferred = false;\r
279             return Source.Contains(entity);\r
280         }\r
281 \r
282         /// <summary>\r
283         /// Copies items to target array\r
284         /// </summary>\r
285         /// <param name="array"></param>\r
286         /// <param name="arrayIndex"></param>\r
287         public void CopyTo(TEntity[] array, int arrayIndex)\r
288         {\r
289             deferred = false;\r
290             Source.CopyTo(array, arrayIndex);\r
291         }\r
292 \r
293         /// <summary>\r
294         /// Returns entities count\r
295         /// </summary>\r
296         public int Count\r
297         {\r
298             get\r
299             {\r
300                 deferred = false;\r
301                 return Source.Count;\r
302             }\r
303         }\r
304 \r
305         /// <summary>\r
306         /// Gets a value indicating whether the <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only.\r
307         /// </summary>\r
308         /// <value></value>\r
309         /// <returns>true if the <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only; otherwise, false.\r
310         /// </returns>\r
311         bool ICollection<TEntity>.IsReadOnly\r
312         {\r
313             get { throw new NotImplementedException(); }\r
314         }\r
315 \r
316         /// <summary>\r
317         /// Removes the specified entity.\r
318         /// </summary>\r
319         /// <param name="entity">The entity.</param>\r
320         /// <returns></returns>\r
321         public bool Remove(TEntity entity)\r
322         {\r
323             int i = Source.IndexOf(entity);\r
324             if(i < 0)\r
325                 return false;\r
326             deferred = false;\r
327             Source.Remove(entity);\r
328             OnRemove(entity);\r
329             ListChangedEventHandler handler = ListChanged;\r
330             if (deferredSource != null && handler != null)\r
331                 handler(this, new ListChangedEventArgs(ListChangedType.ItemDeleted, i));\r
332             return true;\r
333         }\r
334 \r
335         #endregion\r
336 \r
337         #region IList Members\r
338 \r
339         int IList.Add(object value)\r
340         {\r
341             var v = value as TEntity;\r
342             if (v != null && !Contains(v))\r
343             {\r
344                 Add(v);\r
345                 return Count - 1;\r
346             }\r
347             throw new ArgumentOutOfRangeException("value");\r
348         }\r
349 \r
350         void IList.Clear()\r
351         {\r
352             this.Clear();\r
353         }\r
354 \r
355         bool IList.Contains(object value)\r
356         {\r
357             var v = value as TEntity;\r
358             if (v != null)\r
359                 return Contains(v);\r
360             return false;\r
361         }\r
362 \r
363         int IList.IndexOf(object value)\r
364         {\r
365             return this.IndexOf(value as TEntity);\r
366         }\r
367 \r
368         void IList.Insert(int index, object value)\r
369         {\r
370             this.Insert(index, value as TEntity);\r
371         }\r
372 \r
373         bool IList.IsFixedSize\r
374         {\r
375             get { return false; }\r
376         }\r
377 \r
378         bool IList.IsReadOnly\r
379         {\r
380             get { return false; }\r
381         }\r
382 \r
383         void IList.Remove(object value)\r
384         {\r
385             this.Remove(value as TEntity);\r
386         }\r
387 \r
388         void IList.RemoveAt(int index)\r
389         {\r
390             this.RemoveAt(index);\r
391         }\r
392 \r
393         object IList.this[int index]\r
394         {\r
395             get\r
396             {\r
397                 return this[index];\r
398             }\r
399             set\r
400             {\r
401                 this[index] = value as TEntity;\r
402             }\r
403         }\r
404 \r
405         #endregion\r
406 \r
407         #region ICollection Members\r
408 \r
409         void ICollection.CopyTo(Array array, int index)\r
410         {\r
411             for (int i = 0; i < Source.Count; ++i)\r
412             {\r
413                 array.SetValue(this[i], index + i);\r
414             }\r
415         }\r
416 \r
417         int ICollection.Count\r
418         {\r
419             get { return this.Count; }\r
420         }\r
421 \r
422         [DbLinqToDo]\r
423         bool ICollection.IsSynchronized\r
424         {\r
425             get { throw new NotImplementedException(); }\r
426         }\r
427 \r
428         [DbLinqToDo]\r
429         object ICollection.SyncRoot\r
430         {\r
431             get { throw new NotImplementedException(); }\r
432         }\r
433 \r
434         #endregion\r
435 \r
436         /// <summary>\r
437         /// Gets a value indicating whether this instance is deferred.\r
438         /// </summary>\r
439         /// <value>\r
440         ///     <c>true</c> if this instance is deferred; otherwise, <c>false</c>.\r
441         /// </value>\r
442         public bool IsDeferred\r
443         {\r
444             get { return deferred; }\r
445         }\r
446 \r
447         /// <summary>\r
448         /// Adds the range.\r
449         /// </summary>\r
450         /// <param name="collection">The collection.</param>\r
451         public void AddRange(IEnumerable<TEntity> collection)\r
452         {\r
453             foreach (var entity in collection)\r
454             {\r
455                 Add(entity);\r
456             }\r
457         }\r
458 \r
459         /// <summary>\r
460         /// Assigns the specified entity source.\r
461         /// </summary>\r
462         /// <param name="entitySource">The entity source.</param>\r
463         public void Assign(IEnumerable<TEntity> entitySource)\r
464         {\r
465             // notifies removals and adds\r
466             Clear();\r
467             foreach (var entity in entitySource)\r
468             {\r
469                 OnAdd(entity);\r
470             }\r
471             this.source = entitySource.ToList();\r
472             // this.SourceInUse = sourceAsList;\r
473         }\r
474 \r
475         /// <summary>\r
476         /// Sets the entity source.\r
477         /// </summary>\r
478         /// <param name="entitySource">The entity source.</param>\r
479         public void SetSource(IEnumerable<TEntity> entitySource)\r
480         {\r
481 #if false\r
482             Console.WriteLine("# EntitySet<{0}>.SetSource: HashCode={1}; Stack={2}", typeof(TEntity).Name,\r
483                 GetHashCode(), new System.Diagnostics.StackTrace());\r
484 #endif\r
485             if(HasLoadedOrAssignedValues)\r
486                 throw new InvalidOperationException("The EntitySet is already loaded and the source cannot be changed.");\r
487             deferred = true;\r
488             deferredSource = entitySource;\r
489         }\r
490 \r
491         /// <summary>\r
492         /// Loads all entities.\r
493         /// </summary>\r
494         public void Load()\r
495         {\r
496             deferred = false;\r
497             var _ = Source;\r
498         }\r
499 \r
500         /// <summary>\r
501         /// Gets a new binding list.\r
502         /// </summary>\r
503         /// <returns></returns>\r
504         public IBindingList GetNewBindingList()\r
505         {\r
506             return new BindingList<TEntity>(Source.ToList());\r
507         }\r
508 \r
509         // TODO: implement handler call\r
510         public event ListChangedEventHandler ListChanged;\r
511 \r
512         /// <summary>\r
513         /// Called when entity is added.\r
514         /// </summary>\r
515         /// <param name="entity">The entity.</param>\r
516         private void OnAdd(TEntity entity)\r
517         {\r
518                         assignedValues = true;\r
519             if (onAdd != null)\r
520                 onAdd(entity);\r
521         }\r
522         /// <summary>\r
523         /// Called when entity is removed\r
524         /// </summary>\r
525         /// <param name="entity">The entity.</param>\r
526         private void OnRemove(TEntity entity)\r
527         {\r
528                         assignedValues = true;\r
529             if (onRemove != null)\r
530                 onRemove(entity);\r
531         }\r
532     }\r
533 }\r