diff --git a/src/OpenRiaServices.Client/Framework/EntityCollection.cs b/src/OpenRiaServices.Client/Framework/EntityCollection.cs
index d7385024..2ff61cb7 100644
--- a/src/OpenRiaServices.Client/Framework/EntityCollection.cs
+++ b/src/OpenRiaServices.Client/Framework/EntityCollection.cs
@@ -20,7 +20,7 @@ namespace OpenRiaServices.Client
/// Represents a collection of associated Entities.
///
/// The type of in the collection
- public sealed class EntityCollection : IEntityCollection, IEntityCollection
+ public sealed class EntityCollection : IEntityCollection, IEntityCollection, IList, IReadOnlyList
#if HAS_COLLECTIONVIEW
, ICollectionViewFactory
#endif
@@ -311,7 +311,7 @@ public void Remove(TEntity entity)
if (idx != -1)
{
- if (this.RemoveEntity(entity))
+ if (this.RemoveEntity(entity, idx))
{
// If the entity was removed, raise a collection changed notification. Note that the Detach call above might
// have caused a dynamic removal behind the scenes resulting in the entity no longer being in the collection,
@@ -368,14 +368,32 @@ private bool TryAddEntity(TEntity entity)
}
}
- private bool RemoveEntity(TEntity entity)
+ private bool RemoveEntity(TEntity entity, int index)
{
if (this.EntitiesHashSet.Remove(entity))
{
- bool isRemoved = this.Entities.Remove(entity);
- Debug.Assert(isRemoved, "The entity should be present in both Entities and EntitiesHashSet");
+ Debug.Assert(object.ReferenceEquals(entity, Entities[index]));
+ this.Entities.RemoveAt(index);
return true;
}
+ Debug.Fail("Expected item to be part of Set");
+ return false;
+ }
+
+ ///
+ /// Remove the entity if part of the collection and returns it's index through .(-1 if no removal)
+ ///
+ /// entity to remove
+ /// the index of the entity before removal, or -1 if not removed
+ private bool TryRemoveEntity(TEntity entity, out int index)
+ {
+ if (this.EntitiesHashSet.Remove(entity))
+ {
+ index = this.Entities.IndexOf(entity);
+ this.Entities.RemoveAt(index);
+ return true;
+ }
+ index = -1;
return false;
}
@@ -620,17 +638,14 @@ private void OnEntityAssociationUpdated(Entity entity)
{
// Add matching entity to our set. When adding, we use the stronger Filter to
// filter out New entities
- bool added = this.TryAddEntity(typedEntity);
- Debug.Assert(added);
- this.RaiseCollectionChangedNotification(NotifyCollectionChangedAction.Add, typedEntity, this.Entities.Count - 1);
+ if (this.TryAddEntity(typedEntity))
+ this.RaiseCollectionChangedNotification(NotifyCollectionChangedAction.Add, typedEntity, this.Entities.Count - 1);
}
- else if (containsEntity && !this._entityPredicate(typedEntity))
+ // The entity is in our set but is no longer a match, so we need to remove it.
+ // Here we use the predicate directly, since even if the entity is New if it
+ // no longer matches it should be removed.
+ else if (!this._entityPredicate(typedEntity) && this.TryRemoveEntity(typedEntity, out int idx))
{
- // The entity is in our set but is no longer a match, so we need to remove it.
- // Here we use the predicate directly, since even if the entity is New if it
- // no longer matches it should be removed.
- int idx = this.Entities.IndexOf(typedEntity);
- this.RemoveEntity(typedEntity);
this.RaiseCollectionChangedNotification(NotifyCollectionChangedAction.Remove, typedEntity, idx);
}
}
@@ -648,14 +663,13 @@ private void SourceSet_CollectionChanged(object sender, NotifyCollectionChangedE
if (this._parent.EntityState != EntityState.New &&
args.Action == NotifyCollectionChangedAction.Add)
{
- TEntity[] newEntities = args.NewItems.OfType().Where(this.Filter).ToArray();
- if (newEntities.Length > 0)
+ List newEntities = args.NewItems.OfType().Where(this.Filter).ToList();
+ if (newEntities.Count > 0)
{
- int newStartingIdx = -1;
+ int newStartingIdx = this.Entities.Count;
List affectedEntities = new List();
foreach (TEntity newEntity in newEntities)
{
- newStartingIdx = this.Entities.Count;
if (this.TryAddEntity(newEntity))
{
affectedEntities.Add(newEntity);
@@ -664,34 +678,21 @@ private void SourceSet_CollectionChanged(object sender, NotifyCollectionChangedE
if (affectedEntities.Count > 0)
{
-#if SILVERLIGHT
- // SL doesn't support the constructor taking a list of objects
- this.RaiseCollectionChangedNotification(args.Action, (TEntity)affectedEntities.Single(), newStartingIdx);
-#else
this.RaiseCollectionChangedNotification(args.Action, affectedEntities, newStartingIdx);
-#endif
}
}
}
else if (args.Action == NotifyCollectionChangedAction.Remove)
{
// if the entity is in our cached collection, remove it
- TEntity[] entitiesToRemove = args.OldItems.OfType().Where(p => this.EntitiesHashSet.Contains(p)).ToArray();
- if (entitiesToRemove.Length > 0)
+ foreach (TEntity entityToRemove in args.OldItems.OfType())
{
- int oldStartingIdx = this.Entities.IndexOf(entitiesToRemove[0]);
- foreach (TEntity removedEntity in entitiesToRemove)
+ // If entity was part of the collection and removed, raise an event
+ if (this.TryRemoveEntity(entityToRemove, out int idx))
{
- this.RemoveEntity(removedEntity);
+ // Should we do a single reset event if multiple entitites are removed ??
+ this.RaiseCollectionChangedNotification(args.Action, entityToRemove, idx);
}
-
-#if SILVERLIGHT
- //// REVIEW: Should we instead send out a reset event?
- // SL doesn't support the constructor taking a list of objects
- this.RaiseCollectionChangedNotification(args.Action, entitiesToRemove.Single(), oldStartingIdx);
-#else
- this.RaiseCollectionChangedNotification(args.Action, entitiesToRemove, oldStartingIdx);
-#endif
}
}
else if (args.Action == NotifyCollectionChangedAction.Reset)
@@ -790,7 +791,7 @@ IEnumerable IEntityCollection.Entities
{
get
{
- return this.Cast();
+ return this;
}
}
@@ -1012,7 +1013,16 @@ void ICollectionChangedListener.OnCollectionChanged(object sender, NotifyCollect
#endif
#endregion
- #region ICollection Members
+ #region ICollection, IReadOnlyList Members
+
+ ///
+ /// Gets the entity at the specified index in the collection.
+ ///
+ /// **Important**: Make sure to check first to ensure the collection is initialized
+ /// The zero-based index of the entity to retrieve.
+ /// The entity located at the specified index.
+ public TEntity this[int index] => Entities[index];
+
bool ICollection.IsReadOnly
{
get
@@ -1021,22 +1031,30 @@ bool ICollection.IsReadOnly
return IsSourceExternal;
}
}
+
void ICollection.CopyTo(TEntity[] array, int arrayIndex)
{
this.Load();
this.Entities.CopyTo(array, arrayIndex);
}
+
bool ICollection.Contains(TEntity item)
{
this.Load();
return this.EntitiesHashSet.Contains(item);
}
+
bool ICollection.Remove(TEntity item)
{
- bool removed = this.EntitiesHashSet.Contains(item);
- Remove(item);
- return removed;
+ this.Load();
+ if (this.EntitiesHashSet.Contains(item))
+ {
+ Remove(item);
+ return true;
+ }
+ return false;
}
+
///
/// Removes all items.
///
@@ -1046,6 +1064,77 @@ void ICollection.Clear()
foreach (var item in this.Entities.ToList())
Remove(item);
}
+
+ #endregion
+
+ #region IList, ICollection
+ bool IList.IsFixedSize => this.IsSourceExternal;
+
+ bool IList.IsReadOnly => this.IsSourceExternal;
+
+ bool ICollection.IsSynchronized => false;
+
+ object ICollection.SyncRoot => ((ICollection)Entities).SyncRoot;
+
+ ///
+ object IList.this[int index]
+ {
+ get => this[index];
+ set => throw new NotSupportedException(string.Format(CultureInfo.InvariantCulture, Resource.IsNotSupported, "Index setter"));
+ }
+
+ int IList.Add(object value)
+ {
+ int countBefore = this.Count;
+ Add((TEntity)value);
+
+ if (this.Count == countBefore + 1)
+ return countBefore;
+ else if (this.Count == countBefore)
+ return -1;
+ else
+ return Entities.IndexOf((TEntity)value, countBefore);
+ }
+
+ void IList.Clear()
+ {
+ ((ICollection)this).Clear();
+ }
+
+ bool IList.Contains(object value)
+ {
+ return value is TEntity entity && ((ICollection)this).Contains(entity);
+ }
+
+ int IList.IndexOf(object value)
+ {
+ if (value is not TEntity entity)
+ return -1;
+
+ Load();
+ return Entities.IndexOf(entity);
+ }
+
+ void IList.Insert(int index, object value)
+ {
+ throw new NotSupportedException(string.Format(CultureInfo.InvariantCulture, Resource.IsNotSupported, "Insert"));
+ }
+
+ void IList.Remove(object value)
+ {
+ Remove((TEntity)value);
+ }
+
+ void IList.RemoveAt(int index)
+ {
+ throw new NotSupportedException(string.Format(CultureInfo.InvariantCulture, Resource.IsNotSupported, "RemoveAt"));
+ }
+
+ void ICollection.CopyTo(Array array, int index)
+ {
+ this.Load();
+ ((ICollection)Entities).CopyTo(array, index);
+ }
#endregion
}
}
diff --git a/src/OpenRiaServices.Client/Framework/EntitySet.cs b/src/OpenRiaServices.Client/Framework/EntitySet.cs
index 5e04b18e..0390cc9d 100644
--- a/src/OpenRiaServices.Client/Framework/EntitySet.cs
+++ b/src/OpenRiaServices.Client/Framework/EntitySet.cs
@@ -18,7 +18,7 @@ namespace OpenRiaServices.Client
///
/// Represents a collection of instances.
///
- public abstract class EntitySet : IEnumerable, ICollection, INotifyCollectionChanged, IRevertibleChangeTracking, INotifyPropertyChanged
+ public abstract class EntitySet : IEnumerable, ICollection, IList, INotifyCollectionChanged, IRevertibleChangeTracking, INotifyPropertyChanged
{
private readonly Dictionary> _associationUpdateCallbackMap = new();
private readonly Type _entityType;
@@ -481,7 +481,10 @@ public void Remove(Entity entity)
this.OnCollectionChanged(NotifyCollectionChangedAction.Remove, entity, idx);
}
- internal bool Contains(Entity entity)
+ /// Determines whether the contains the specified entity.
+ /// The element to locate in the object.
+ /// true if the object contains the specified element; otherwise, false.
+ public bool Contains(Entity entity)
{
return this._set.Contains(entity);
}
@@ -940,6 +943,65 @@ void ICollection.CopyTo(Array array, int index)
}
#endregion
+ #region IList
+ bool IList.IsFixedSize => (_supportedOperations & (EntitySetOperations.Remove | EntitySetOperations.Add)) == 0;
+
+ bool IList.IsReadOnly => (_supportedOperations & (EntitySetOperations.Remove | EntitySetOperations.Add)) == 0;
+
+ object IList.this[int index]
+ {
+ get => _list[index];
+ set => throw new NotSupportedException(string.Format(CultureInfo.InvariantCulture, Resource.IsNotSupported, "Index setter"));
+ }
+
+ int IList.Add(object value)
+ {
+ int countBefore = Count;
+ Add((Entity)value);
+
+ if (Count == countBefore + 1)
+ return countBefore;
+ else if (Count == countBefore)
+ return -1;
+ else
+ return List.IndexOf(value);
+ }
+
+ bool IList.Contains(object value)
+ {
+ return value is Entity e && Contains(e);
+ }
+
+ int IList.IndexOf(object value)
+ {
+ return _list.IndexOf(value);
+ }
+
+ void IList.Insert(int index, object value)
+ {
+ throw new NotSupportedException(string.Format(CultureInfo.InvariantCulture, Resource.IsNotSupported, "Insert"));
+ }
+
+ void IList.Remove(object value)
+ {
+ try
+ {
+ if (value is Entity entity)
+ Remove(entity);
+ }
+ catch (InvalidOperationException ioe) when (ioe.Message == Resource.EntitySet_EntityNotInSet)
+ {
+ // Don't throw if item was not in the collection
+ }
+ }
+
+ void IList.RemoveAt(int index)
+ {
+ throw new NotSupportedException(string.Format(CultureInfo.InvariantCulture, Resource.IsNotSupported, "RemoveAt"));
+ }
+
+ #endregion
+
#region INotifyCollectionChanged Members
///
@@ -1213,12 +1275,13 @@ protected override void VisitEntityRef(IEntityRef entityRef, Entity parent, Meta
/// Represents a collection of instances, providing change tracking and other services.
///
/// The type of this set will contain
- public sealed class EntitySet : EntitySet, IEntityCollection
+ public sealed class EntitySet : EntitySet, IEntityCollection, IReadOnlyList
#if HAS_COLLECTIONVIEW
, ICollectionViewFactory
#endif
where TEntity : Entity
{
+ private new List List => (List)base.List;
///
/// Initializes a new instance of the EntitySet class
///
@@ -1246,7 +1309,7 @@ protected override Entity CreateEntity()
{
throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Resources.Cannot_Create_Abstract_Entity, typeof(TEntity)));
}
- TEntity entity = (TEntity)Activator.CreateInstance(typeof(TEntity));
+ TEntity entity = Activator.CreateInstance();
return entity;
}
@@ -1266,7 +1329,7 @@ protected override Entity CreateEntity()
/// The enumerator
public new IEnumerator GetEnumerator()
{
- return ((IList)List).GetEnumerator();
+ return List.GetEnumerator();
}
///
@@ -1340,20 +1403,14 @@ protected override void OnCollectionChanged(NotifyCollectionChangedAction action
base.OnCollectionChanged(action, affectedObject, index);
}
- #region IEnumerable Members
- IEnumerator IEnumerable.GetEnumerator()
- {
- return this.GetEnumerator();
- }
- #endregion
-
#region ICollection Members
void ICollection.CopyTo(TEntity[] array, int arrayIndex)
{
- ((IList)List).CopyTo(array, arrayIndex);
+ List.CopyTo(array, arrayIndex);
}
- bool ICollection.Contains(TEntity item)
+ ///
+ public bool /*ICollection.*/Contains(TEntity item)
{
return base.Contains(item);
}
@@ -1377,6 +1434,10 @@ bool ICollection.Remove(TEntity item)
}
#endregion
+ #region IReadOnlyList Members
+ TEntity IReadOnlyList.this[int index] => List[index];
+ #endregion
+
#region ICollectionViewFactory
#if HAS_COLLECTIONVIEW
///
@@ -1414,22 +1475,7 @@ internal ListCollectionViewProxy(EntitySet source)
#region IList
public int Add(object value)
- {
- T entity = value as T;
- if (entity == null)
- {
- throw new ArgumentException(
- string.Format(CultureInfo.CurrentCulture, Resource.MustBeAnEntity, "value"),
- nameof(value));
- }
-
- int countBefore = this.Source.Count;
- this.Source.Add(entity);
-
- return this.Source.Count == countBefore + 1
- ? countBefore
- : ((List)this.Source.List).IndexOf(entity, countBefore);
- }
+ => ((IList)Source).Add(value);
public void Clear()
{
@@ -1443,24 +1489,15 @@ public bool Contains(object value)
public int IndexOf(object value)
{
- return this.Source.List.IndexOf(value);
+ return ((IList)this.Source.List).IndexOf(value);
}
public void Insert(int index, object value)
- {
- throw new NotSupportedException(
- string.Format(CultureInfo.CurrentCulture, Resource.IsNotSupported, "Insert"));
- }
+ => ((IList)Source).Insert(index, value);
- public bool IsFixedSize
- {
- get { return !(this.Source.CanAdd || this.Source.CanRemove); }
- }
+ public bool IsFixedSize => ((IList)Source).IsFixedSize;
- public bool IsReadOnly
- {
- get { return !(this.Source.CanAdd || this.Source.CanRemove); }
- }
+ public bool IsReadOnly => ((IList)Source).IsReadOnly;
public void Remove(object value)
{
@@ -1472,7 +1509,7 @@ public void Remove(object value)
public void RemoveAt(int index)
{
- this.Remove(this[index]);
+ Source.Remove(Source.List[index]);
}
public object this[int index]
@@ -1490,7 +1527,7 @@ public object this[int index]
public void CopyTo(Array array, int index)
{
- this.Source.List.CopyTo(array, index);
+ ((IList)this.Source.List).CopyTo(array, index);
}
public int Count
diff --git a/src/OpenRiaServices.Client/Framework/LoadResult.cs b/src/OpenRiaServices.Client/Framework/LoadResult.cs
index 6fa67e44..5857454c 100644
--- a/src/OpenRiaServices.Client/Framework/LoadResult.cs
+++ b/src/OpenRiaServices.Client/Framework/LoadResult.cs
@@ -1,7 +1,6 @@
using System;
using System.Collections;
using System.Collections.Generic;
-using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
@@ -11,9 +10,9 @@ namespace OpenRiaServices.Client
/// The result of a sucessfully completed load operation
///
/// The type of the entity loaded.
- public class LoadResult : IReadOnlyCollection, ICollection, ILoadResult where TEntity : Entity
+ public class LoadResult : IReadOnlyList, ICollection, ILoadResult where TEntity : Entity
{
- private readonly ReadOnlyCollection _loadedEntites;
+ private readonly Data.ReadOnlyObservableLoaderCollection _loadedEntites;
///
/// Initializes a new instance of the class.
@@ -77,7 +76,14 @@ public LoadResult(EntityQuery query, LoadBehavior loadBehavior, IEnumer
/// Gets the number of top level Entities loaded
///
/// The number top level Entities loaded.
- public int Count { get { return _loadedEntites.Count; } }
+ public int Count => _loadedEntites.Count;
+
+ ///
+ /// Gets the entity at the specified zero-based index.
+ ///
+ /// The zero-based index of the entity to retrieve.
+ /// The entity located at the specified index.
+ public TEntity this[int index] => _loadedEntites[index];
#region ICollection, IEnumerator implementations
///