2 using System.Collections;
3 using System.Collections.Generic;
4 using System.ComponentModel;
5 using System.Linq.Expressions;
8 using System.Reflection;
10 using System.Diagnostics;
11 using System.Runtime.Serialization;
12 using System.Diagnostics.CodeAnalysis;
14 namespace System.Data.Linq {
15 internal static class SourceState<T> {
16 internal static readonly IEnumerable<T> Loaded = (IEnumerable<T>)new T[] { };
17 internal static readonly IEnumerable<T> Assigned = (IEnumerable<T>)new T[] { };
20 [SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes", Justification = "[....]: Types are never compared to each other. When comparisons happen it is against the entities that are represented by these constructs.")]
21 public struct Link<T> {
23 IEnumerable<T> source;
25 public Link(T value) {
26 this.underlyingValue = value;
30 public Link(IEnumerable<T> source) {
32 this.underlyingValue = default(T);
35 public Link(Link<T> link) {
36 this.underlyingValue = link.underlyingValue;
37 this.source = link.source;
40 public bool HasValue {
41 get { return this.source == null || this.HasLoadedValue || this.HasAssignedValue; }
44 public bool HasLoadedOrAssignedValue {
45 get { return this.HasLoadedValue || this.HasAssignedValue; }
48 internal bool HasLoadedValue {
49 get { return this.source == SourceState<T>.Loaded; }
52 internal bool HasAssignedValue {
53 get { return this.source == SourceState<T>.Assigned; }
56 internal T UnderlyingValue {
57 get { return this.underlyingValue; }
60 internal IEnumerable<T> Source {
61 get { return this.source; }
64 internal bool HasSource {
65 get { return this.source != null && !this.HasAssignedValue && !this.HasLoadedValue; }
71 this.underlyingValue = Enumerable.SingleOrDefault(this.source);
72 this.source = SourceState<T>.Loaded;
74 return this.underlyingValue;
77 this.underlyingValue = value;
78 this.source = SourceState<T>.Assigned;
83 [SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes", Justification="[....]: Types are never compared to each other. When comparisons happen it is against the entities that are represented by these constructs.")]
84 public struct EntityRef<TEntity>
85 where TEntity : class {
86 IEnumerable<TEntity> source;
89 public EntityRef(TEntity entity) {
91 this.source = SourceState<TEntity>.Assigned;
94 public EntityRef(IEnumerable<TEntity> source) {
96 this.entity = default(TEntity);
99 public EntityRef(EntityRef<TEntity> entityRef) {
100 this.source = entityRef.source;
101 this.entity = entityRef.entity;
104 public TEntity Entity {
106 if (this.HasSource) {
108 IEnumerable<TEntity> src = this.source;
109 this.entity = Enumerable.SingleOrDefault(src);
110 this.source = SourceState<TEntity>.Loaded;
116 this.source = SourceState<TEntity>.Assigned;
120 public bool HasLoadedOrAssignedValue {
121 get { return this.HasLoadedValue || this.HasAssignedValue; }
124 internal bool HasValue {
125 get { return this.source == null || this.HasLoadedValue || this.HasAssignedValue; }
128 internal bool HasLoadedValue {
129 get { return this.source == SourceState<TEntity>.Loaded; }
132 internal bool HasAssignedValue {
133 get { return this.source == SourceState<TEntity>.Assigned; }
136 internal bool HasSource {
137 get { return this.source != null && !this.HasLoadedValue && !this.HasAssignedValue; }
140 internal IEnumerable<TEntity> Source {
141 get { return this.source; }
144 internal TEntity UnderlyingValue {
145 get { return this.entity; }
149 [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix", Justification="[....]: Naming chosen to represent a different concept from a collection because it is delayed loaded.")]
150 public sealed class EntitySet<TEntity> : IList, IList<TEntity>, IListSource
151 where TEntity : class {
152 IEnumerable<TEntity> source;
153 ItemList<TEntity> entities;
154 ItemList<TEntity> removedEntities;
155 Action<TEntity> onAdd;
156 Action<TEntity> onRemove;
158 TEntity onRemoveEntity;
160 private ListChangedEventHandler onListChanged;
161 private bool isModified;
162 private bool isLoaded;
168 public EntitySet(Action<TEntity> onAdd, Action<TEntity> onRemove) {
170 this.onRemove = onRemove;
173 internal EntitySet(EntitySet<TEntity> es, bool copyNotifications) {
174 this.source = es.source;
175 foreach (TEntity e in es.entities) entities.Add(e);
176 foreach (TEntity e in es.removedEntities) removedEntities.Add(e);
177 this.version = es.version;
178 if (copyNotifications) {
179 this.onAdd = es.onAdd;
180 this.onRemove = es.onRemove;
187 return entities.Count;
191 public TEntity this[int index] {
194 if (index < 0 || index >= entities.Count)
195 throw Error.ArgumentOutOfRange("index");
196 return entities[index];
200 if (index < 0 || index >= entities.Count)
201 throw Error.ArgumentOutOfRange("index");
202 if (value == null || IndexOf(value) >= 0)
203 throw Error.ArgumentOutOfRange("value");
205 TEntity old = entities[index];
207 OnListChanged(ListChangedType.ItemDeleted, index);
210 entities[index] = value;
212 OnListChanged(ListChangedType.ItemAdded, index);
216 [SuppressMessage("Microsoft.Naming", "CA1725:ParameterNamesShouldMatchBaseDeclaration", MessageId = "0#", Justification = "[....]: Naming the parameter entity makes it more discoverable because it is clear what type of data should be added to this collection.")]
217 public void Add(TEntity entity) {
218 if (entity == null) {
219 throw Error.ArgumentNull("entity");
221 if (entity != onAddEntity) {
223 if (!entities.Contains(entity)) {
225 if (this.HasSource) removedEntities.Remove(entity);
226 entities.Add(entity);
227 OnListChanged(ListChangedType.ItemAdded, entities.IndexOf(entity));
233 public void AddRange(IEnumerable<TEntity> collection) {
234 if (collection == null)
235 throw Error.ArgumentNull("collection");
237 // convert to List in case adding elements here removes them from the 'collection' (ie entityset to entityset assignment)
238 collection = collection.ToList();
239 foreach (TEntity e in collection) {
240 if (!entities.Contains(e)) {
242 if (this.HasSource) removedEntities.Remove(e);
244 OnListChanged(ListChangedType.ItemAdded, entities.IndexOf(e));
250 public void Assign(IEnumerable<TEntity> entitySource) {
251 // No-op if assigning the same object to itself
252 if (Object.ReferenceEquals(this, entitySource)) {
257 if (entitySource != null)
258 AddRange(entitySource);
260 // When an entity set is assigned, it is considered loaded.
261 // Since with defer loading enabled, a load is triggered
262 // anyways, this is only necessary in cases where defer loading
263 // is disabled. In such cases, the materializer assigns a
264 // prefetched collection and we want IsLoaded to be true.
265 this.isLoaded = true;
268 public void Clear() {
271 if (entities.Items != null) {
272 List<TEntity> removeList = new List<TEntity>(entities.Items);
273 foreach (TEntity e in removeList) {
277 entities = default(ItemList<TEntity>);
279 OnListChanged(ListChangedType.Reset, 0);
282 [SuppressMessage("Microsoft.Naming", "CA1725:ParameterNamesShouldMatchBaseDeclaration", MessageId = "0#", Justification = "[....]: Naming the parameter entity makes it more discoverable because it is clear what type of data should be added to this collection.")]
283 public bool Contains(TEntity entity) {
284 return IndexOf(entity) >= 0;
287 public void CopyTo(TEntity[] array, int arrayIndex) {
289 if (entities.Count > 0) Array.Copy(entities.Items, 0, array, arrayIndex, entities.Count);
292 public IEnumerator<TEntity> GetEnumerator() {
294 return new Enumerator(this);
297 internal IEnumerable<TEntity> GetUnderlyingValues() {
298 return new UnderlyingValues(this);
301 class UnderlyingValues : IEnumerable<TEntity> {
302 EntitySet<TEntity> entitySet;
303 internal UnderlyingValues(EntitySet<TEntity> entitySet) {
304 this.entitySet = entitySet;
306 public IEnumerator<TEntity> GetEnumerator() {
307 return new Enumerator(this.entitySet);
309 IEnumerator IEnumerable.GetEnumerator() {
310 return this.GetEnumerator();
314 [SuppressMessage("Microsoft.Naming", "CA1725:ParameterNamesShouldMatchBaseDeclaration", MessageId = "0#", Justification = "[....]: Naming the parameter entity makes it more discoverable because it is clear what type of data should be added to this collection.")]
315 public int IndexOf(TEntity entity) {
317 return entities.IndexOf(entity);
320 [SuppressMessage("Microsoft.Naming", "CA1725:ParameterNamesShouldMatchBaseDeclaration", MessageId = "1#", Justification = "[....]: Naming the parameter entity makes it more discoverable because it is clear what type of data should be added to this collection.")]
321 public void Insert(int index, TEntity entity) {
323 if (index < 0 || index > Count)
324 throw Error.ArgumentOutOfRange("index");
325 if (entity == null || IndexOf(entity) >= 0)
326 throw Error.ArgumentOutOfRange("entity");
328 entities.Insert(index, entity);
329 OnListChanged(ListChangedType.ItemAdded, index);
335 /// Returns true if this entity set has a deferred query
336 /// that hasn't been executed yet.
338 public bool IsDeferred
340 get { return HasSource; }
344 /// Returns true if values have been either assigned or loaded.
346 internal bool HasValues {
347 get { return this.source == null || this.HasAssignedValues || this.HasLoadedValues; }
351 /// Returns true if the entity set has been modified in any way by the user or its items
352 /// have been loaded from the database.
354 public bool HasLoadedOrAssignedValues {
355 get { return this.HasAssignedValues || this.HasLoadedValues; }
359 /// Returns true if the set has been modified in any way by the user.
361 internal bool HasAssignedValues {
362 get { return this.isModified; }
366 /// Returns true if the set has been loaded from the database.
368 internal bool HasLoadedValues {
369 get { return this.isLoaded; }
373 /// Returns true if the set has a deferred source query that hasn't been loaded yet.
375 internal bool HasSource {
376 get { return this.source != null && !this.HasLoadedValues; }
380 /// Returns true if the collection has been loaded.
382 internal bool IsLoaded {
384 return this.isLoaded;
388 internal IEnumerable<TEntity> Source {
389 get { return this.source; }
393 if (this.HasSource) {
394 ItemList<TEntity> addedEntities = entities;
395 entities = default(ItemList<TEntity>);
396 foreach (TEntity e in source) entities.Add(e);
397 foreach (TEntity e in addedEntities) entities.Include(e);
398 foreach (TEntity e in removedEntities) entities.Remove(e);
399 source = SourceState<TEntity>.Loaded;
401 removedEntities = default(ItemList<TEntity>);
405 private void OnModified() {
409 [SuppressMessage("Microsoft.Naming", "CA1725:ParameterNamesShouldMatchBaseDeclaration", MessageId = "0#", Justification = "[....]: Naming the parameter entity makes it more discoverable because it is clear what type of data should be added to this collection.")]
410 public bool Remove(TEntity entity) {
411 if (entity == null || entity == onRemoveEntity) return false;
414 bool removed = false;
415 if (this.HasSource) {
416 if (!removedEntities.Contains(entity)) {
418 // check in entities in case it has been pre-added
419 index = entities.IndexOf(entity);
421 entities.RemoveAt(index);
424 removedEntities.Add(entity);
429 index = entities.IndexOf(entity);
432 entities.RemoveAt(index);
438 // If index == -1 here, that means that the entity was not in the list before Remove was called,
439 // so we shouldn't fire the event since the list itself will not be changed, even though the Remove will still be tracked
440 // on the removedEntities list in case a subsequent Load brings in this entity.
442 OnListChanged(ListChangedType.ItemDeleted, index);
448 public void RemoveAt(int index) {
450 if (index < 0 || index >= Count) {
451 throw Error.ArgumentOutOfRange("index");
454 TEntity entity = entities[index];
456 entities.RemoveAt(index);
458 OnListChanged(ListChangedType.ItemDeleted, index);
461 public void SetSource(IEnumerable<TEntity> entitySource) {
462 if (this.HasAssignedValues || this.HasLoadedValues)
463 throw Error.EntitySetAlreadyLoaded();
464 this.source = entitySource;
468 if (onAddEntity != null || onRemoveEntity != null)
469 throw Error.ModifyDuringAddOrRemove();
473 void OnAdd(TEntity entity) {
475 TEntity e = onAddEntity;
476 onAddEntity = entity;
485 void OnRemove(TEntity entity) {
486 if (onRemove != null) {
487 TEntity e = onRemoveEntity;
488 onRemoveEntity = entity;
497 class Enumerable : IEnumerable<TEntity> {
498 EntitySet<TEntity> entitySet;
499 public Enumerable(EntitySet<TEntity> entitySet) {
500 this.entitySet = entitySet;
502 IEnumerator IEnumerable.GetEnumerator() {
503 return this.GetEnumerator();
505 public IEnumerator<TEntity> GetEnumerator() {
506 return new Enumerator(this.entitySet);
510 class Enumerator : IEnumerator<TEntity> {
511 EntitySet<TEntity> entitySet;
517 public Enumerator(EntitySet<TEntity> entitySet) {
518 this.entitySet = entitySet;
519 this.items = entitySet.entities.Items;
521 this.endIndex = entitySet.entities.Count - 1;
522 this.version = entitySet.version;
525 public void Dispose()
527 // Technically, calling GC.SuppressFinalize is not required because the class does not
528 // have a finalizer, but it does no harm, protects against the case where a finalizer is added
529 // in the future, and prevents an FxCop warning.
530 GC.SuppressFinalize(this);
533 public bool MoveNext() {
534 if (version != entitySet.version)
535 throw Error.EntitySetModifiedDuringEnumeration();
536 if (index == endIndex) return false;
541 public TEntity Current {
542 get { return items[index]; }
545 object IEnumerator.Current {
546 get { return items[index]; }
549 void IEnumerator.Reset() {
550 if (version != entitySet.version)
551 throw Error.EntitySetModifiedDuringEnumeration();
556 int IList.Add(object value) {
557 TEntity entity = value as TEntity;
558 if (entity == null || IndexOf(entity) >= 0) {
559 throw Error.ArgumentOutOfRange("value");
562 int i = entities.Count;
563 entities.Add(entity);
568 bool IList.Contains(object value) {
569 return Contains(value as TEntity);
572 int IList.IndexOf(object value) {
573 return IndexOf(value as TEntity);
576 void IList.Insert(int index, object value) {
577 TEntity entity = value as TEntity;
579 throw Error.ArgumentOutOfRange("value");
580 Insert(index, entity);
583 bool IList.IsFixedSize {
584 get { return false; }
587 bool IList.IsReadOnly {
588 get { return false; }
591 void IList.Remove(object value) {
592 Remove(value as TEntity);
595 object IList.this[int index] {
600 TEntity entity = value as TEntity;
601 if (value == null) throw Error.ArgumentOutOfRange("value");
602 this[index] = entity;
606 void ICollection.CopyTo(Array array, int index) {
608 if (entities.Count > 0) Array.Copy(entities.Items, 0, array, index, entities.Count);
611 bool ICollection.IsSynchronized {
612 get { return false; }
615 object ICollection.SyncRoot {
619 bool ICollection<TEntity>.IsReadOnly {
620 get { return false; }
623 IEnumerator IEnumerable.GetEnumerator() {
624 return GetEnumerator();
627 void OnListChanged(ListChangedType type, int index) {
629 if (onListChanged != null) {
630 onListChanged(this, new ListChangedEventArgs(type, index));
634 public event ListChangedEventHandler ListChanged {
636 onListChanged += value;
639 onListChanged -= value;
643 bool IListSource.ContainsListCollection {
647 private IBindingList cachedList = null;
649 IList IListSource.GetList() {
650 if (cachedList == null || listChanged) {
651 cachedList = GetNewBindingList();
657 [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification="Method doesn't represent a property of the type.")]
658 public IBindingList GetNewBindingList() {
659 return new EntitySetBindingList<TEntity>(this.ToList(), this);
663 struct ItemList<T> where T : class {
668 get { return count; }
672 get { return items; }
675 public T this[int index] {
676 get { return items[index]; }
677 set { items[index] = value; }
680 public void Add(T item) {
681 if (items == null || items.Length == count) GrowItems();
686 public bool Contains(T item) {
687 return IndexOf(item) >= 0;
690 public Enumerator GetEnumerator() {
694 e.endIndex = count - 1;
698 public bool Include(T item) {
699 if (LastIndexOf(item) >= 0) return false;
704 public int IndexOf(T item) {
705 for (int i = 0; i < count; i++) {
706 if (items[i] == item) return i;
711 public void Insert(int index, T item) {
712 if (items == null || items.Length == count) GrowItems();
713 if (index < count) Array.Copy(items, index, items, index + 1, count - index);
718 public int LastIndexOf(T item) {
722 if (items[i] == item) return i;
727 public bool Remove(T item) {
728 int i = IndexOf(item);
729 if (i < 0) return false;
734 public void RemoveAt(int index) {
736 if (index < count) Array.Copy(items, index + 1, items, index, count - index);
737 items[count] = default(T);
741 Array.Resize(ref items, count == 0 ? 4 : count * 2);
744 public struct Enumerator {
747 internal int endIndex;
749 public bool MoveNext() {
750 if (index == endIndex) return false;
756 get { return items[index]; }
761 [SuppressMessage("Microsoft.Naming", "CA1724:TypeNamesShouldNotMatchNamespaces", Justification = "[....]: The name clearly describes function and the namespace is under a DLinq namespace which will make the distinction clear.")]
764 public sealed class Binary : IEquatable<Binary> {
765 [DataMember(Name="Bytes")]
769 public Binary(byte[] value) {
771 this.bytes = new byte[0];
774 this.bytes = new byte[value.Length];
775 Array.Copy(value, this.bytes, value.Length);
780 public byte[] ToArray() {
781 byte[] copy = new byte[this.bytes.Length];
782 Array.Copy(this.bytes, copy, copy.Length);
787 get { return this.bytes.Length; }
790 public static implicit operator Binary(byte[] value) {
791 return new Binary(value);
794 public bool Equals(Binary other) {
795 return this.EqualsTo(other);
798 public static bool operator ==(Binary binary1, Binary binary2) {
799 if ((object)binary1 == (object)binary2)
801 if ((object)binary1 == null && (object)binary2 == null)
803 if ((object)binary1 == null || (object)binary2 == null)
805 return binary1.EqualsTo(binary2);
808 public static bool operator !=(Binary binary1, Binary binary2) {
809 if ((object)binary1 == (object)binary2)
811 if ((object)binary1 == null && (object)binary2 == null)
813 if ((object)binary1 == null || (object)binary2 == null)
815 return !binary1.EqualsTo(binary2);
818 public override bool Equals(object obj) {
819 return this.EqualsTo(obj as Binary);
822 public override int GetHashCode() {
823 if (!hashCode.HasValue) {
824 // hash code is not marked [DataMember], so when
825 // using the DataContractSerializer, we'll need
826 // to recompute the hash after deserialization.
829 return this.hashCode.Value;
832 public override string ToString() {
833 StringBuilder sb = new StringBuilder();
835 sb.Append(System.Convert.ToBase64String(this.bytes, 0, this.bytes.Length));
837 return sb.ToString();
840 private bool EqualsTo(Binary binary) {
841 if ((object)this == (object)binary)
843 if ((object)binary == null)
845 if (this.bytes.Length != binary.bytes.Length)
847 if (this.GetHashCode() != binary.GetHashCode())
849 for (int i = 0, n = this.bytes.Length; i < n; i++) {
850 if (this.bytes[i] != binary.bytes[i])
857 /// Simple hash using pseudo-random coefficients for each byte in
858 /// the array to achieve order dependency.
860 private void ComputeHash() {
861 int s = 314, t = 159;
863 for (int i = 0; i < bytes.Length; i++) {
864 hashCode = hashCode * s + bytes[i];