5 // Copyright (c) 2007-2008 Jiri Moudry, Pascal Craponne
\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
14 // The above copyright notice and this permission notice shall be included in
\r
15 // all copies or substantial portions of the Software.
\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
28 using System.Collections;
\r
29 using System.Collections.Generic;
\r
32 using System.Linq.Expressions;
\r
33 using System.ComponentModel;
\r
37 namespace System.Data.Linq
\r
39 namespace DbLinq.Data.Linq
\r
42 public sealed class EntitySet<TEntity> : ICollection, ICollection<TEntity>, IEnumerable, IEnumerable<TEntity>, IList, IList<TEntity>, IListSource
\r
43 where TEntity : class
\r
45 private readonly Action<TEntity> onAdd;
\r
46 private readonly Action<TEntity> onRemove;
\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
58 if (deferredSource != null)
\r
59 return source = deferredSource.ToList();
\r
60 return source = new List<TEntity>();
\r
66 /// Gets or sets a value indicating whether this instance has loaded or assigned values.
\r
69 /// <c>true</c> if this instance has loaded or assigned values; otherwise, <c>false</c>.
\r
71 public bool HasAssignedValues
\r
73 get { return assignedValues; }
\r
76 public bool HasLoadedValues
\r
78 get { return source != null; }
\r
82 /// Gets a value indicating whether this instance has loaded or assigned values.
\r
85 /// <c>true</c> if this instance has loaded or assigned values; otherwise, <c>false</c>.
\r
87 public bool HasLoadedOrAssignedValues
\r
89 get { return HasLoadedValues || HasAssignedValues; }
\r
93 /// Initializes a new instance of the <see cref="EntitySet<TEntity>"/> class.
\r
95 /// <param name="onAdd">The on add.</param>
\r
96 /// <param name="onRemove">The on remove.</param>
\r
98 public EntitySet(Action<TEntity> onAdd, Action<TEntity> onRemove)
\r
101 this.onAdd = onAdd;
\r
102 this.onRemove = onRemove;
\r
106 /// Initializes a new instance of the <see cref="EntitySet<TEntity>"/> class.
\r
113 /// entry point for 'foreach' statement.
\r
115 public IEnumerator<TEntity> GetEnumerator()
\r
118 return Source.GetEnumerator();
\r
122 /// Enumerates all entities
\r
124 /// <returns></returns>
\r
125 IEnumerator IEnumerable.GetEnumerator()
\r
127 return GetEnumerator();
\r
131 /// Gets the source expression (used to nest queries)
\r
133 /// <value>The expression.</value>
\r
134 internal Expression Expression
\r
138 if (deferred && this.deferredSource is IQueryable<TEntity>)
\r
139 return (deferredSource as IQueryable<TEntity>).Expression;
\r
141 return Expression.Constant(this);
\r
148 public void Add(TEntity entity)
\r
150 if (entity == null)
\r
151 throw new ArgumentNullException("entity");
\r
153 if (Source.Contains (entity))
\r
155 Source.Add(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
163 bool IListSource.ContainsListCollection
\r
165 get { throw new NotImplementedException(); }
\r
168 IList IListSource.GetList()
\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
175 #region IList<TEntity> Members
\r
178 /// Returns entity's index
\r
180 /// <param name="entity">The entity.</param>
\r
181 /// <returns></returns>
\r
182 public int IndexOf(TEntity entity)
\r
185 return Source.IndexOf(entity);
\r
189 /// Inserts entity at specified index.
\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
195 if (Source.Contains(entity))
\r
196 throw new ArgumentOutOfRangeException();
\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
206 /// Removes entity at specified index
\r
208 /// <param name="index"></param>
\r
209 public void RemoveAt(int index)
\r
212 var item = Source[index];
\r
213 Source.RemoveAt(index);
\r
215 ListChangedEventHandler handler = ListChanged;
\r
216 if (handler != null)
\r
217 handler(this, new ListChangedEventArgs(ListChangedType.ItemDeleted, index));
\r
221 /// Gets or sets the <see cref="TEntity"/> at the specified index.
\r
223 /// <value></value>
\r
224 public TEntity this[int index]
\r
229 return Source[index];
\r
233 OnRemove(Source[index]);
\r
236 var handler = ListChanged;
\r
237 if (handler != null)
\r
239 handler(this, new ListChangedEventArgs(ListChangedType.ItemDeleted, index));
\r
240 handler(this, new ListChangedEventArgs(ListChangedType.ItemAdded, index));
\r
242 Source[index] = value;
\r
248 #region ICollection<TEntity> Members
\r
251 /// Removes all items in collection
\r
253 public void Clear()
\r
255 ListChangedEventHandler handler = ListChanged;
\r
257 assignedValues = true;
\r
258 if (deferredSource != null && handler != null)
\r
260 foreach (var item in Source)
\r
261 handler(this, new ListChangedEventArgs(ListChangedType.ItemDeleted, 0));
\r
263 if (handler != null)
\r
264 handler(this, new ListChangedEventArgs(ListChangedType.Reset, 0));
\r
269 /// Determines whether [contains] [the specified entity].
\r
271 /// <param name="entity">The entity.</param>
\r
273 /// <c>true</c> if [contains] [the specified entity]; otherwise, <c>false</c>.
\r
276 public bool Contains(TEntity entity)
\r
279 return Source.Contains(entity);
\r
283 /// Copies items to target array
\r
285 /// <param name="array"></param>
\r
286 /// <param name="arrayIndex"></param>
\r
287 public void CopyTo(TEntity[] array, int arrayIndex)
\r
290 Source.CopyTo(array, arrayIndex);
\r
294 /// Returns entities count
\r
301 return Source.Count;
\r
306 /// Gets a value indicating whether the <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only.
\r
308 /// <value></value>
\r
309 /// <returns>true if the <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only; otherwise, false.
\r
311 bool ICollection<TEntity>.IsReadOnly
\r
313 get { throw new NotImplementedException(); }
\r
317 /// Removes the specified entity.
\r
319 /// <param name="entity">The entity.</param>
\r
320 /// <returns></returns>
\r
321 public bool Remove(TEntity entity)
\r
323 int i = Source.IndexOf(entity);
\r
327 Source.Remove(entity);
\r
329 ListChangedEventHandler handler = ListChanged;
\r
330 if (deferredSource != null && handler != null)
\r
331 handler(this, new ListChangedEventArgs(ListChangedType.ItemDeleted, i));
\r
337 #region IList Members
\r
339 int IList.Add(object value)
\r
341 var v = value as TEntity;
\r
342 if (v != null && !Contains(v))
\r
347 throw new ArgumentOutOfRangeException("value");
\r
355 bool IList.Contains(object value)
\r
357 var v = value as TEntity;
\r
359 return Contains(v);
\r
363 int IList.IndexOf(object value)
\r
365 return this.IndexOf(value as TEntity);
\r
368 void IList.Insert(int index, object value)
\r
370 this.Insert(index, value as TEntity);
\r
373 bool IList.IsFixedSize
\r
375 get { return false; }
\r
378 bool IList.IsReadOnly
\r
380 get { return false; }
\r
383 void IList.Remove(object value)
\r
385 this.Remove(value as TEntity);
\r
388 void IList.RemoveAt(int index)
\r
390 this.RemoveAt(index);
\r
393 object IList.this[int index]
\r
397 return this[index];
\r
401 this[index] = value as TEntity;
\r
407 #region ICollection Members
\r
409 void ICollection.CopyTo(Array array, int index)
\r
411 for (int i = 0; i < Source.Count; ++i)
\r
413 array.SetValue(this[i], index + i);
\r
417 int ICollection.Count
\r
419 get { return this.Count; }
\r
423 bool ICollection.IsSynchronized
\r
425 get { throw new NotImplementedException(); }
\r
429 object ICollection.SyncRoot
\r
431 get { throw new NotImplementedException(); }
\r
437 /// Gets a value indicating whether this instance is deferred.
\r
440 /// <c>true</c> if this instance is deferred; otherwise, <c>false</c>.
\r
442 public bool IsDeferred
\r
444 get { return deferred; }
\r
448 /// Adds the range.
\r
450 /// <param name="collection">The collection.</param>
\r
451 public void AddRange(IEnumerable<TEntity> collection)
\r
453 foreach (var entity in collection)
\r
460 /// Assigns the specified entity source.
\r
462 /// <param name="entitySource">The entity source.</param>
\r
463 public void Assign(IEnumerable<TEntity> entitySource)
\r
465 // notifies removals and adds
\r
467 foreach (var entity in entitySource)
\r
471 this.source = entitySource.ToList();
\r
472 // this.SourceInUse = sourceAsList;
\r
476 /// Sets the entity source.
\r
478 /// <param name="entitySource">The entity source.</param>
\r
479 public void SetSource(IEnumerable<TEntity> entitySource)
\r
482 Console.WriteLine("# EntitySet<{0}>.SetSource: HashCode={1}; Stack={2}", typeof(TEntity).Name,
\r
483 GetHashCode(), new System.Diagnostics.StackTrace());
\r
485 if(HasLoadedOrAssignedValues)
\r
486 throw new InvalidOperationException("The EntitySet is already loaded and the source cannot be changed.");
\r
488 deferredSource = entitySource;
\r
492 /// Loads all entities.
\r
501 /// Gets a new binding list.
\r
503 /// <returns></returns>
\r
504 public IBindingList GetNewBindingList()
\r
506 return new BindingList<TEntity>(Source.ToList());
\r
509 // TODO: implement handler call
\r
510 public event ListChangedEventHandler ListChanged;
\r
513 /// Called when entity is added.
\r
515 /// <param name="entity">The entity.</param>
\r
516 private void OnAdd(TEntity entity)
\r
518 assignedValues = true;
\r
523 /// Called when entity is removed
\r
525 /// <param name="entity">The entity.</param>
\r
526 private void OnRemove(TEntity entity)
\r
528 assignedValues = true;
\r
529 if (onRemove != null)
\r