using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Linq.Expressions; using System.IO; using System.Linq; using System.Reflection; using System.Text; using System.Diagnostics; using System.Runtime.Serialization; using System.Diagnostics.CodeAnalysis; namespace System.Data.Linq { internal static class SourceState { internal static readonly IEnumerable Loaded = (IEnumerable)new T[] { }; internal static readonly IEnumerable Assigned = (IEnumerable)new T[] { }; } [SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes", Justification = "Microsoft: Types are never compared to each other. When comparisons happen it is against the entities that are represented by these constructs.")] public struct Link { T underlyingValue; IEnumerable source; public Link(T value) { this.underlyingValue = value; this.source = null; } public Link(IEnumerable source) { this.source = source; this.underlyingValue = default(T); } public Link(Link link) { this.underlyingValue = link.underlyingValue; this.source = link.source; } public bool HasValue { get { return this.source == null || this.HasLoadedValue || this.HasAssignedValue; } } public bool HasLoadedOrAssignedValue { get { return this.HasLoadedValue || this.HasAssignedValue; } } internal bool HasLoadedValue { get { return this.source == SourceState.Loaded; } } internal bool HasAssignedValue { get { return this.source == SourceState.Assigned; } } internal T UnderlyingValue { get { return this.underlyingValue; } } internal IEnumerable Source { get { return this.source; } } internal bool HasSource { get { return this.source != null && !this.HasAssignedValue && !this.HasLoadedValue; } } public T Value { get { if (this.HasSource) { this.underlyingValue = Enumerable.SingleOrDefault(this.source); this.source = SourceState.Loaded; } return this.underlyingValue; } set { this.underlyingValue = value; this.source = SourceState.Assigned; } } } [SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes", Justification="Microsoft: Types are never compared to each other. When comparisons happen it is against the entities that are represented by these constructs.")] public struct EntityRef where TEntity : class { IEnumerable source; TEntity entity; public EntityRef(TEntity entity) { this.entity = entity; this.source = SourceState.Assigned; } public EntityRef(IEnumerable source) { this.source = source; this.entity = default(TEntity); } public EntityRef(EntityRef entityRef) { this.source = entityRef.source; this.entity = entityRef.entity; } public TEntity Entity { get { if (this.HasSource) { IEnumerable src = this.source; this.entity = Enumerable.SingleOrDefault(src); this.source = SourceState.Loaded; } return this.entity; } set { this.entity = value; this.source = SourceState.Assigned; } } public bool HasLoadedOrAssignedValue { get { return this.HasLoadedValue || this.HasAssignedValue; } } internal bool HasValue { get { return this.source == null || this.HasLoadedValue || this.HasAssignedValue; } } internal bool HasLoadedValue { get { return this.source == SourceState.Loaded; } } internal bool HasAssignedValue { get { return this.source == SourceState.Assigned; } } internal bool HasSource { get { return this.source != null && !this.HasLoadedValue && !this.HasAssignedValue; } } internal IEnumerable Source { get { return this.source; } } internal TEntity UnderlyingValue { get { return this.entity; } } } [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix", Justification="Microsoft: Naming chosen to represent a different concept from a collection because it is delayed loaded.")] public sealed class EntitySet : IList, IList, IListSource where TEntity : class { IEnumerable source; ItemList entities; ItemList removedEntities; Action onAdd; Action onRemove; TEntity onAddEntity; TEntity onRemoveEntity; int version; private ListChangedEventHandler onListChanged; private bool isModified; private bool isLoaded; bool listChanged; public EntitySet() { } public EntitySet(Action onAdd, Action onRemove) { this.onAdd = onAdd; this.onRemove = onRemove; } internal EntitySet(EntitySet es, bool copyNotifications) { this.source = es.source; foreach (TEntity e in es.entities) entities.Add(e); foreach (TEntity e in es.removedEntities) removedEntities.Add(e); this.version = es.version; if (copyNotifications) { this.onAdd = es.onAdd; this.onRemove = es.onRemove; } } public int Count { get { Load(); return entities.Count; } } public TEntity this[int index] { get { Load(); if (index < 0 || index >= entities.Count) throw Error.ArgumentOutOfRange("index"); return entities[index]; } set { Load(); if (index < 0 || index >= entities.Count) throw Error.ArgumentOutOfRange("index"); if (value == null || IndexOf(value) >= 0) throw Error.ArgumentOutOfRange("value"); CheckModify(); TEntity old = entities[index]; OnRemove(old); OnListChanged(ListChangedType.ItemDeleted, index); OnAdd(value); entities[index] = value; OnModified(); OnListChanged(ListChangedType.ItemAdded, index); } } [SuppressMessage("Microsoft.Naming", "CA1725:ParameterNamesShouldMatchBaseDeclaration", MessageId = "0#", Justification = "Microsoft: Naming the parameter entity makes it more discoverable because it is clear what type of data should be added to this collection.")] public void Add(TEntity entity) { if (entity == null) { throw Error.ArgumentNull("entity"); } if (entity != onAddEntity) { CheckModify(); if (!entities.Contains(entity)) { OnAdd(entity); if (this.HasSource) removedEntities.Remove(entity); entities.Add(entity); OnListChanged(ListChangedType.ItemAdded, entities.IndexOf(entity)); } OnModified(); } } public void AddRange(IEnumerable collection) { if (collection == null) throw Error.ArgumentNull("collection"); CheckModify(); // convert to List in case adding elements here removes them from the 'collection' (ie entityset to entityset assignment) collection = collection.ToList(); foreach (TEntity e in collection) { if (!entities.Contains(e)) { OnAdd(e); if (this.HasSource) removedEntities.Remove(e); entities.Add(e); OnListChanged(ListChangedType.ItemAdded, entities.IndexOf(e)); } } OnModified(); } public void Assign(IEnumerable entitySource) { // No-op if assigning the same object to itself if (Object.ReferenceEquals(this, entitySource)) { return; } Clear(); if (entitySource != null) AddRange(entitySource); // When an entity set is assigned, it is considered loaded. // Since with defer loading enabled, a load is triggered // anyways, this is only necessary in cases where defer loading // is disabled. In such cases, the materializer assigns a // prefetched collection and we want IsLoaded to be true. this.isLoaded = true; } public void Clear() { Load(); CheckModify(); if (entities.Items != null) { List removeList = new List(entities.Items); foreach (TEntity e in removeList) { Remove(e); } } entities = default(ItemList); OnModified(); OnListChanged(ListChangedType.Reset, 0); } [SuppressMessage("Microsoft.Naming", "CA1725:ParameterNamesShouldMatchBaseDeclaration", MessageId = "0#", Justification = "Microsoft: Naming the parameter entity makes it more discoverable because it is clear what type of data should be added to this collection.")] public bool Contains(TEntity entity) { return IndexOf(entity) >= 0; } public void CopyTo(TEntity[] array, int arrayIndex) { Load(); if (entities.Count > 0) Array.Copy(entities.Items, 0, array, arrayIndex, entities.Count); } public IEnumerator GetEnumerator() { Load(); return new Enumerator(this); } internal IEnumerable GetUnderlyingValues() { return new UnderlyingValues(this); } class UnderlyingValues : IEnumerable { EntitySet entitySet; internal UnderlyingValues(EntitySet entitySet) { this.entitySet = entitySet; } public IEnumerator GetEnumerator() { return new Enumerator(this.entitySet); } IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); } } [SuppressMessage("Microsoft.Naming", "CA1725:ParameterNamesShouldMatchBaseDeclaration", MessageId = "0#", Justification = "Microsoft: Naming the parameter entity makes it more discoverable because it is clear what type of data should be added to this collection.")] public int IndexOf(TEntity entity) { Load(); return entities.IndexOf(entity); } [SuppressMessage("Microsoft.Naming", "CA1725:ParameterNamesShouldMatchBaseDeclaration", MessageId = "1#", Justification = "Microsoft: Naming the parameter entity makes it more discoverable because it is clear what type of data should be added to this collection.")] public void Insert(int index, TEntity entity) { Load(); if (index < 0 || index > Count) throw Error.ArgumentOutOfRange("index"); if (entity == null || IndexOf(entity) >= 0) throw Error.ArgumentOutOfRange("entity"); CheckModify(); entities.Insert(index, entity); OnListChanged(ListChangedType.ItemAdded, index); OnAdd(entity); } /// /// Returns true if this entity set has a deferred query /// that hasn't been executed yet. /// public bool IsDeferred { get { return HasSource; } } /// /// Returns true if values have been either assigned or loaded. /// internal bool HasValues { get { return this.source == null || this.HasAssignedValues || this.HasLoadedValues; } } /// /// Returns true if the entity set has been modified in any way by the user or its items /// have been loaded from the database. /// public bool HasLoadedOrAssignedValues { get { return this.HasAssignedValues || this.HasLoadedValues; } } /// /// Returns true if the set has been modified in any way by the user. /// internal bool HasAssignedValues { get { return this.isModified; } } /// /// Returns true if the set has been loaded from the database. /// internal bool HasLoadedValues { get { return this.isLoaded; } } /// /// Returns true if the set has a deferred source query that hasn't been loaded yet. /// internal bool HasSource { get { return this.source != null && !this.HasLoadedValues; } } /// /// Returns true if the collection has been loaded. /// internal bool IsLoaded { get { return this.isLoaded; } } internal IEnumerable Source { get { return this.source; } } public void Load() { if (this.HasSource) { ItemList addedEntities = entities; entities = default(ItemList); foreach (TEntity e in source) entities.Add(e); foreach (TEntity e in addedEntities) entities.Include(e); foreach (TEntity e in removedEntities) entities.Remove(e); source = SourceState.Loaded; isLoaded = true; removedEntities = default(ItemList); } } private void OnModified() { isModified = true; } [SuppressMessage("Microsoft.Naming", "CA1725:ParameterNamesShouldMatchBaseDeclaration", MessageId = "0#", Justification = "Microsoft: Naming the parameter entity makes it more discoverable because it is clear what type of data should be added to this collection.")] public bool Remove(TEntity entity) { if (entity == null || entity == onRemoveEntity) return false; CheckModify(); int index = -1; bool removed = false; if (this.HasSource) { if (!removedEntities.Contains(entity)) { OnRemove(entity); // check in entities in case it has been pre-added index = entities.IndexOf(entity); if (index != -1) { entities.RemoveAt(index); } else { removedEntities.Add(entity); } removed = true; } } else { index = entities.IndexOf(entity); if (index != -1) { OnRemove(entity); entities.RemoveAt(index); removed = true; } } if (removed) { OnModified(); // If index == -1 here, that means that the entity was not in the list before Remove was called, // so we shouldn't fire the event since the list itself will not be changed, even though the Remove will still be tracked // on the removedEntities list in case a subsequent Load brings in this entity. if (index != -1) { OnListChanged(ListChangedType.ItemDeleted, index); } } return removed; } public void RemoveAt(int index) { Load(); if (index < 0 || index >= Count) { throw Error.ArgumentOutOfRange("index"); } CheckModify(); TEntity entity = entities[index]; OnRemove(entity); entities.RemoveAt(index); OnModified(); OnListChanged(ListChangedType.ItemDeleted, index); } public void SetSource(IEnumerable entitySource) { if (this.HasAssignedValues || this.HasLoadedValues) throw Error.EntitySetAlreadyLoaded(); this.source = entitySource; } void CheckModify() { if (onAddEntity != null || onRemoveEntity != null) throw Error.ModifyDuringAddOrRemove(); version++; } void OnAdd(TEntity entity) { if (onAdd != null) { TEntity e = onAddEntity; onAddEntity = entity; try { onAdd(entity); } finally { onAddEntity = e; } } } void OnRemove(TEntity entity) { if (onRemove != null) { TEntity e = onRemoveEntity; onRemoveEntity = entity; try { onRemove(entity); } finally { onRemoveEntity = e; } } } class Enumerable : IEnumerable { EntitySet entitySet; public Enumerable(EntitySet entitySet) { this.entitySet = entitySet; } IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); } public IEnumerator GetEnumerator() { return new Enumerator(this.entitySet); } } class Enumerator : IEnumerator { EntitySet entitySet; TEntity[] items; int index; int endIndex; int version; public Enumerator(EntitySet entitySet) { this.entitySet = entitySet; this.items = entitySet.entities.Items; this.index = -1; this.endIndex = entitySet.entities.Count - 1; this.version = entitySet.version; } public void Dispose() { // Technically, calling GC.SuppressFinalize is not required because the class does not // have a finalizer, but it does no harm, protects against the case where a finalizer is added // in the future, and prevents an FxCop warning. GC.SuppressFinalize(this); } public bool MoveNext() { if (version != entitySet.version) throw Error.EntitySetModifiedDuringEnumeration(); if (index == endIndex) return false; index++; return true; } public TEntity Current { get { return items[index]; } } object IEnumerator.Current { get { return items[index]; } } void IEnumerator.Reset() { if (version != entitySet.version) throw Error.EntitySetModifiedDuringEnumeration(); index = -1; } } int IList.Add(object value) { TEntity entity = value as TEntity; if (entity == null || IndexOf(entity) >= 0) { throw Error.ArgumentOutOfRange("value"); } CheckModify(); int i = entities.Count; entities.Add(entity); OnAdd(entity); return i; } bool IList.Contains(object value) { return Contains(value as TEntity); } int IList.IndexOf(object value) { return IndexOf(value as TEntity); } void IList.Insert(int index, object value) { TEntity entity = value as TEntity; if (value == null) throw Error.ArgumentOutOfRange("value"); Insert(index, entity); } bool IList.IsFixedSize { get { return false; } } bool IList.IsReadOnly { get { return false; } } void IList.Remove(object value) { Remove(value as TEntity); } object IList.this[int index] { get { return this[index]; } set { TEntity entity = value as TEntity; if (value == null) throw Error.ArgumentOutOfRange("value"); this[index] = entity; } } void ICollection.CopyTo(Array array, int index) { Load(); if (entities.Count > 0) Array.Copy(entities.Items, 0, array, index, entities.Count); } bool ICollection.IsSynchronized { get { return false; } } object ICollection.SyncRoot { get { return this; } } bool ICollection.IsReadOnly { get { return false; } } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } void OnListChanged(ListChangedType type, int index) { listChanged = true; if (onListChanged != null) { onListChanged(this, new ListChangedEventArgs(type, index)); } } public event ListChangedEventHandler ListChanged { add { onListChanged += value; } remove { onListChanged -= value; } } bool IListSource.ContainsListCollection { get { return true; } } private IBindingList cachedList = null; IList IListSource.GetList() { if (cachedList == null || listChanged) { cachedList = GetNewBindingList(); listChanged = false; } return cachedList; } [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification="Method doesn't represent a property of the type.")] public IBindingList GetNewBindingList() { return new EntitySetBindingList(this.ToList(), this); } } struct ItemList where T : class { T[] items; int count; public int Count { get { return count; } } public T[] Items { get { return items; } } public T this[int index] { get { return items[index]; } set { items[index] = value; } } public void Add(T item) { if (items == null || items.Length == count) GrowItems(); items[count] = item; count++; } public bool Contains(T item) { return IndexOf(item) >= 0; } public Enumerator GetEnumerator() { Enumerator e; e.items = items; e.index = -1; e.endIndex = count - 1; return e; } public bool Include(T item) { if (LastIndexOf(item) >= 0) return false; Add(item); return true; } public int IndexOf(T item) { for (int i = 0; i < count; i++) { if (items[i] == item) return i; } return -1; } public void Insert(int index, T item) { if (items == null || items.Length == count) GrowItems(); if (index < count) Array.Copy(items, index, items, index + 1, count - index); items[index] = item; count++; } public int LastIndexOf(T item) { int i = count; while (i > 0) { --i; if (items[i] == item) return i; } return -1; } public bool Remove(T item) { int i = IndexOf(item); if (i < 0) return false; RemoveAt(i); return true; } public void RemoveAt(int index) { count--; if (index < count) Array.Copy(items, index + 1, items, index, count - index); items[count] = default(T); } void GrowItems() { Array.Resize(ref items, count == 0 ? 4 : count * 2); } public struct Enumerator { internal T[] items; internal int index; internal int endIndex; public bool MoveNext() { if (index == endIndex) return false; index++; return true; } public T Current { get { return items[index]; } } } } [SuppressMessage("Microsoft.Naming", "CA1724:TypeNamesShouldNotMatchNamespaces", Justification = "Microsoft: The name clearly describes function and the namespace is under a DLinq namespace which will make the distinction clear.")] [DataContract] [Serializable] public sealed class Binary : IEquatable { [DataMember(Name="Bytes")] byte[] bytes; int? hashCode; public Binary(byte[] value) { if (value == null) { this.bytes = new byte[0]; } else { this.bytes = new byte[value.Length]; Array.Copy(value, this.bytes, value.Length); } this.ComputeHash(); } public byte[] ToArray() { byte[] copy = new byte[this.bytes.Length]; Array.Copy(this.bytes, copy, copy.Length); return copy; } public int Length { get { return this.bytes.Length; } } public static implicit operator Binary(byte[] value) { return new Binary(value); } public bool Equals(Binary other) { return this.EqualsTo(other); } public static bool operator ==(Binary binary1, Binary binary2) { if ((object)binary1 == (object)binary2) return true; if ((object)binary1 == null && (object)binary2 == null) return true; if ((object)binary1 == null || (object)binary2 == null) return false; return binary1.EqualsTo(binary2); } public static bool operator !=(Binary binary1, Binary binary2) { if ((object)binary1 == (object)binary2) return false; if ((object)binary1 == null && (object)binary2 == null) return false; if ((object)binary1 == null || (object)binary2 == null) return true; return !binary1.EqualsTo(binary2); } public override bool Equals(object obj) { return this.EqualsTo(obj as Binary); } public override int GetHashCode() { if (!hashCode.HasValue) { // hash code is not marked [DataMember], so when // using the DataContractSerializer, we'll need // to recompute the hash after deserialization. ComputeHash(); } return this.hashCode.Value; } public override string ToString() { StringBuilder sb = new StringBuilder(); sb.Append("\""); sb.Append(System.Convert.ToBase64String(this.bytes, 0, this.bytes.Length)); sb.Append("\""); return sb.ToString(); } private bool EqualsTo(Binary binary) { if ((object)this == (object)binary) return true; if ((object)binary == null) return false; if (this.bytes.Length != binary.bytes.Length) return false; if (this.GetHashCode() != binary.GetHashCode()) return false; for (int i = 0, n = this.bytes.Length; i < n; i++) { if (this.bytes[i] != binary.bytes[i]) return false; } return true; } /// /// Simple hash using pseudo-random coefficients for each byte in /// the array to achieve order dependency. /// private void ComputeHash() { int s = 314, t = 159; hashCode = 0; for (int i = 0; i < bytes.Length; i++) { hashCode = hashCode * s + bytes[i]; s = s * t; } } } }