Skip to content

WPF Binding improvements#565

Open
Daniel-Svensson wants to merge 3 commits intoOpenRIAServices:mainfrom
Daniel-Svensson:binding_opt
Open

WPF Binding improvements#565
Daniel-Svensson wants to merge 3 commits intoOpenRIAServices:mainfrom
Daniel-Svensson:binding_opt

Conversation

@Daniel-Svensson
Copy link
Copy Markdown
Member

@Daniel-Svensson Daniel-Svensson commented Apr 8, 2026

  • Remove ListCollectionViewProxy class (for EntitySet)
  • Create ListCollectionView directly
  • Fix CA1860: Prefer comparing 'Count' to 0 rather than using 'Any()'

Summary by CodeRabbit

  • Refactor
    • Simplified EntitySet base class interfaces by removing IEnumerable and ICollection implementations while retaining IList and change-tracking interfaces.
    • Streamlined collection view initialization; ICollectionViewFactory.CreateView() is now marked as Obsolete.
    • Optimized validation and collection state checks throughout the framework for consistency.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 8, 2026

📝 Walkthrough

Walkthrough

This pull request systematically replaces LINQ .Any() collection emptiness checks with direct .Count != 0 comparisons across the framework. Additionally, EntitySet interfaces are refactored to remove IEnumerable and ICollection implementations, while collection view creation logic is simplified to use ListCollectionView directly instead of wrapping with CollectionViewSource.

Changes

Cohort / File(s) Summary
LINQ emptiness check replacement
src/OpenRiaServices.Client/Framework/ChangeSetBuilder.cs, ComplexObject.cs, Entity.cs, Internal/MetaType.cs, InvokeOperation.cs
Replaced .Any() with .Count != 0 in validation error and collection emptiness checks, preserving same branching behavior.
Load and operation validation
src/OpenRiaServices.Client/Framework/LoadOperation.cs, src/OpenRiaServices.Server/Framework/Data/DomainServiceDescription.cs, Authentication/AuthenticationCodeProcessor.cs
Updated parameter and validation error checks from .Any() to .Count != 0 across three distinct methods.
Documentation and return type updates
src/OpenRiaServices.Client/Framework/EntityQuery.cs, ObjectStateUtility.cs
Updated XML documentation reference and narrowed private helper return type to concrete Dictionary<string, object>.
EntitySet collection view refactoring
src/OpenRiaServices.Client/Framework/EntitySet.cs
Removed IEnumerable and ICollection from interface list; simplified CreateView() to instantiate ListCollectionView directly; marked ICollectionViewFactory.CreateView() as Obsolete.
EntityCollection view creation
src/OpenRiaServices.Client/Framework/EntityCollection.cs
Eliminated CollectionViewSource wrapping; removed ListCollectionViewProxy<TEntity> type; optimized Remove() by using List.Remove() return value as conditional.
Test assertion updates
src/OpenRiaServices.Client/Test/Client.Test/Data/DomainOperationExceptionTests.cs, SubmitOperationExceptionTests.cs, EntitySetTests.cs, src/Test/OpenRiaservices.EndToEnd.Wcf.Test/Data/DomainContextTests.cs, ErrorPropagationTests.cs, InvokeOperationTests.cs, ServerSideAsyncTests.cs
Consistently replaced .Any() checks with .Count != 0 in test assertions; updated EntitySetTests helper to use CollectionViewSource.GetDefaultView() instead of ICollectionViewFactory.CreateView().

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~28 minutes

Possibly related PRs

  • #562: Modifies collection view infrastructure and EntitySet/EntityCollection interfaces in parallel with this refactoring.

Poem

🐰 Hop, hop, the .Any() calls now fade,
Count comparisons make a grand cascade,
Views unwrapped, interfaces retrace,
CollectionViewSource leaves its place!
Cleaner code in each hop and bound,
Optimization's joy abound!

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 38.46% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'WPF Binding improvements' is vague and does not clearly convey the specific technical changes made in this PR, which include removing ListCollectionViewProxy, using ListCollectionView directly, and replacing Any() with Count != 0 comparisons. Consider a more specific title like 'Replace Any() with Count != 0 for CA1860 compliance and remove ListCollectionViewProxy' or 'Optimize collection checks using Count and simplify WPF binding logic'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@Daniel-Svensson Daniel-Svensson marked this pull request as ready for review April 8, 2026 17:16
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/OpenRiaServices.Client/Framework/EntityCollection.cs`:
- Around line 814-817: Add the same deprecation attribute to the
ICollectionViewFactory.CreateView implementation in EntityCollection (the
CreateView() method returning new ListCollectionView(...)) to match EntitySet:
mark CreateView() with [Obsolete(...)] using the same message used in
EntitySet's CreateView so callers see the planned removal; ensure the attribute
is applied to the ICollectionViewFactory.CreateView method declaration inside
EntityCollection<TEntity> and keep the implementation unchanged.

In `@src/OpenRiaServices.Client/Framework/EntitySet.cs`:
- Around line 1447-1451: Update the Obsolete attribute message on
ICollectionViewFactory.CreateView to guide users toward the recommended
replacement: mention that callers should use
CollectionViewSource.GetDefaultView(...) instead of relying on
ICollectionViewFactory, and state that implementing ICollectionViewFactory will
be removed in the next major version; locate the attribute on the
ICollectionViewFactory.CreateView method (which currently returns a new
ListCollectionView(this)) and replace the message text accordingly.

In
`@src/OpenRiaServices.Client/Test/Client.Test/Data/DomainOperationExceptionTests.cs`:
- Line 23: Replace the awkward "Assert.IsFalse(... Count != 0)" checks with
direct zero-count assertions for readability: in DomainOperationExceptionTests
replace assertions like Assert.IsFalse(doe.ValidationErrors.Count != 0, ...)
with Assert.AreEqual(0, doe.ValidationErrors.Count, ...) or
Assert.IsTrue(doe.ValidationErrors.Count == 0, ...) (preserve the original
message text if desired) for each occurrence referencing
doe.ValidationErrors.Count in the test methods.

In `@src/OpenRiaServices.Client/Test/Client.Test/Data/EntitySetTests.cs`:
- Around line 329-332: The tests currently use
CollectionViewSource.GetDefaultView(entitySet) in GetICV(EntitySet) to avoid the
obsolete ICollectionViewFactory.CreateView(); update the other test file where
code still casts to ICollectionViewFactory and calls CreateView() (the call in
EntityCollectionTests.cs that obtains an ICollectionView via CreateView) to
instead call CollectionViewSource.GetDefaultView(entitySet) (or centralize by
adding/using the same GetICV helper) so tests no longer rely on the obsolete
CreateView path.

In
`@src/OpenRiaServices.Client/Test/Client.Test/Data/SubmitOperationExceptionTests.cs`:
- Line 29: The assertions in SubmitOperationExceptionTests use
Assert.IsFalse(soe.ValidationErrors.Count != 0) which is harder to read; change
each to an explicit zero-count assertion (e.g., Assert.AreEqual(0,
soe.ValidationErrors.Count) or Assert.IsTrue(soe.ValidationErrors.Count == 0) )
for clarity. Update the three occurrences referencing soe.ValidationErrors.Count
in the SubmitOperationExceptionTests constructor/test methods (the assertions
around lines checking Count at 29, 40, and 51) so they directly assert zero
rather than negating a != 0 expression.

In `@src/Test/OpenRiaservices.EndToEnd.Wcf.Test/Data/ErrorPropagationTests.cs`:
- Line 256: The assertions use double negation
(Assert.IsFalse(zip.ValidationErrors.Count != 0)) which is less readable;
replace both occurrences with a direct empty check such as Assert.AreEqual(0,
zip.ValidationErrors.Count) (or Assert.IsTrue(zip.ValidationErrors.Count == 0))
to assert the ValidationErrors collection is empty, updating the two places that
reference zip.ValidationErrors.Count.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 10bb38c2-542a-4499-be74-f863ac0ef69a

📥 Commits

Reviewing files that changed from the base of the PR and between 4b365cf and 5b88230.

📒 Files selected for processing (19)
  • src/OpenRiaServices.Client/Framework/ChangeSetBuilder.cs
  • src/OpenRiaServices.Client/Framework/ComplexObject.cs
  • src/OpenRiaServices.Client/Framework/Entity.cs
  • src/OpenRiaServices.Client/Framework/EntityCollection.cs
  • src/OpenRiaServices.Client/Framework/EntityQuery.cs
  • src/OpenRiaServices.Client/Framework/EntitySet.cs
  • src/OpenRiaServices.Client/Framework/Internal/MetaType.cs
  • src/OpenRiaServices.Client/Framework/InvokeOperation.cs
  • src/OpenRiaServices.Client/Framework/LoadOperation.cs
  • src/OpenRiaServices.Client/Framework/ObjectStateUtility.cs
  • src/OpenRiaServices.Client/Test/Client.Test/Data/DomainOperationExceptionTests.cs
  • src/OpenRiaServices.Client/Test/Client.Test/Data/EntitySetTests.cs
  • src/OpenRiaServices.Client/Test/Client.Test/Data/SubmitOperationExceptionTests.cs
  • src/OpenRiaServices.Server/Framework/Authentication/AuthenticationCodeProcessor.cs
  • src/OpenRiaServices.Server/Framework/Data/DomainServiceDescription.cs
  • src/Test/OpenRiaservices.EndToEnd.Wcf.Test/Data/DomainContextTests.cs
  • src/Test/OpenRiaservices.EndToEnd.Wcf.Test/Data/ErrorPropagationTests.cs
  • src/Test/OpenRiaservices.EndToEnd.Wcf.Test/Data/InvokeOperationTests.cs
  • src/Test/OpenRiaservices.EndToEnd.Wcf.Test/Data/ServerSideAsyncTests.cs

Comment on lines 814 to 817
ICollectionView ICollectionViewFactory.CreateView()
{
// We use the CollectionViewSource to obtain a ListCollectionView, a type internal to Silverlight
return new CollectionViewSource() { Source = new ListCollectionViewProxy<TEntity>(this) }.View;
return new ListCollectionView(new ListCollectionViewProxy<TEntity>(this));
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider marking CreateView() as obsolete for consistency with EntitySet.

In EntitySet.cs, the ICollectionViewFactory.CreateView() method is marked [Obsolete] with a note about planned removal. For API consistency, consider applying the same deprecation here.

♻️ Suggested change for consistency
+        [Obsolete("ICollectionViewFactory is planned for removal in the next major version. Use CollectionViewSource.GetDefaultView() instead.")]
         ICollectionView ICollectionViewFactory.CreateView()
         {
             return new ListCollectionView(new ListCollectionViewProxy<TEntity>(this));
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
ICollectionView ICollectionViewFactory.CreateView()
{
// We use the CollectionViewSource to obtain a ListCollectionView, a type internal to Silverlight
return new CollectionViewSource() { Source = new ListCollectionViewProxy<TEntity>(this) }.View;
return new ListCollectionView(new ListCollectionViewProxy<TEntity>(this));
}
[Obsolete("ICollectionViewFactory is planned for removal in the next major version. Use CollectionViewSource.GetDefaultView() instead.")]
ICollectionView ICollectionViewFactory.CreateView()
{
return new ListCollectionView(new ListCollectionViewProxy<TEntity>(this));
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/OpenRiaServices.Client/Framework/EntityCollection.cs` around lines 814 -
817, Add the same deprecation attribute to the ICollectionViewFactory.CreateView
implementation in EntityCollection (the CreateView() method returning new
ListCollectionView(...)) to match EntitySet: mark CreateView() with
[Obsolete(...)] using the same message used in EntitySet's CreateView so callers
see the planned removal; ensure the attribute is applied to the
ICollectionViewFactory.CreateView method declaration inside
EntityCollection<TEntity> and keep the implementation unchanged.

Comment on lines +1447 to 1451
[Obsolete("Consider removing in next major version, not implementing ICollectionViewFactory should give the same behaviour")]
ICollectionView ICollectionViewFactory.CreateView()
{
// We use the CollectionViewSource to obtain a ListCollectionView, a type internal to Silverlight
return new CollectionViewSource() { Source = new ListCollectionViewProxy<TEntity>(this) }.View;
}

/// <summary>
/// <see cref="IList"/> proxy that makes the <see cref="EntitySet"/> usable in the default collection views.
/// All operations implemented against the proxy are passed through to the source <see cref="EntitySet"/>.
/// </summary>
/// <remarks>
/// This proxy does not support a full set of list operations. However, the subset it does support
/// is sufficient for interaction with the ListCollectionView.
/// </remarks>
/// <typeparam name="T">The entity type of this proxy</typeparam>
private class ListCollectionViewProxy<T> : IList, IEnumerable<T>, INotifyCollectionChanged, ICollectionChangedListener where T : Entity
{
private readonly object _syncRoot = new object();
private readonly EntitySet<T> _source;
private readonly WeakCollectionChangedListener _weakCollectionChangedLister;

internal ListCollectionViewProxy(EntitySet<T> source)
{
this._source = source;
this._weakCollectionChangedLister =
WeakCollectionChangedListener.CreateIfNecessary(this._source, this);
}

#region IList

public int Add(object value)
=> ((IList)Source).Add(value);

public void Clear()
{
this.Source.Clear();
}

public bool Contains(object value)
{
return (value is Entity e) && this.Source.Contains(e);
}

public int IndexOf(object value)
{
return ((IList)this.Source.List).IndexOf(value);
}

public void Insert(int index, object value)
=> ((IList)Source).Insert(index, value);

public bool IsFixedSize => ((IList)Source).IsFixedSize;

public bool IsReadOnly => ((IList)Source).IsReadOnly;

public void Remove(object value)
{
if (value is T entity)
{
this.Source.Remove(entity);
}
}

public void RemoveAt(int index)
{
Source.Remove(Source.List[index]);
}

public object this[int index]
{
get
{
return this.Source.List[index];
}
set
{
throw new NotSupportedException(
string.Format(CultureInfo.CurrentCulture, Resource.IsNotSupported, "Indexed setting"));
}
}

public void CopyTo(Array array, int index)
{
((IList)this.Source.List).CopyTo(array, index);
}

public int Count
{
get { return this.Source.Count; }
}

public bool IsSynchronized
{
get { return false; }
}

public object SyncRoot
{
get { return this._syncRoot; }
}

public IEnumerator<T> GetEnumerator()
{
return this.Source.GetEnumerator();
}

IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}

private EntitySet<T> Source
{
get { return this._source; }
}

#endregion

#region INotifyCollectionChanged

public event NotifyCollectionChangedEventHandler CollectionChanged;

private void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
this.CollectionChanged?.Invoke(this, e);
}

private void OnSourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
this.OnCollectionChanged(e);
}

#endregion

#region ICollectionChangedListener

void ICollectionChangedListener.OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
this.OnSourceCollectionChanged(sender, e);
}

#endregion
return new ListCollectionView(this);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider improving the [Obsolete] message to guide users.

The current message suggests removing ICollectionViewFactory but doesn't direct users to the recommended alternative. Consider mentioning CollectionViewSource.GetDefaultView() as the replacement approach.

💡 Suggested improvement
-        [Obsolete("Consider removing in next major version, not implementing ICollectionViewFactory should give the same behaviour")]
+        [Obsolete("ICollectionViewFactory is planned for removal in the next major version. Use CollectionViewSource.GetDefaultView() instead.")]
         ICollectionView ICollectionViewFactory.CreateView()
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
[Obsolete("Consider removing in next major version, not implementing ICollectionViewFactory should give the same behaviour")]
ICollectionView ICollectionViewFactory.CreateView()
{
// We use the CollectionViewSource to obtain a ListCollectionView, a type internal to Silverlight
return new CollectionViewSource() { Source = new ListCollectionViewProxy<TEntity>(this) }.View;
}
/// <summary>
/// <see cref="IList"/> proxy that makes the <see cref="EntitySet"/> usable in the default collection views.
/// All operations implemented against the proxy are passed through to the source <see cref="EntitySet"/>.
/// </summary>
/// <remarks>
/// This proxy does not support a full set of list operations. However, the subset it does support
/// is sufficient for interaction with the ListCollectionView.
/// </remarks>
/// <typeparam name="T">The entity type of this proxy</typeparam>
private class ListCollectionViewProxy<T> : IList, IEnumerable<T>, INotifyCollectionChanged, ICollectionChangedListener where T : Entity
{
private readonly object _syncRoot = new object();
private readonly EntitySet<T> _source;
private readonly WeakCollectionChangedListener _weakCollectionChangedLister;
internal ListCollectionViewProxy(EntitySet<T> source)
{
this._source = source;
this._weakCollectionChangedLister =
WeakCollectionChangedListener.CreateIfNecessary(this._source, this);
}
#region IList
public int Add(object value)
=> ((IList)Source).Add(value);
public void Clear()
{
this.Source.Clear();
}
public bool Contains(object value)
{
return (value is Entity e) && this.Source.Contains(e);
}
public int IndexOf(object value)
{
return ((IList)this.Source.List).IndexOf(value);
}
public void Insert(int index, object value)
=> ((IList)Source).Insert(index, value);
public bool IsFixedSize => ((IList)Source).IsFixedSize;
public bool IsReadOnly => ((IList)Source).IsReadOnly;
public void Remove(object value)
{
if (value is T entity)
{
this.Source.Remove(entity);
}
}
public void RemoveAt(int index)
{
Source.Remove(Source.List[index]);
}
public object this[int index]
{
get
{
return this.Source.List[index];
}
set
{
throw new NotSupportedException(
string.Format(CultureInfo.CurrentCulture, Resource.IsNotSupported, "Indexed setting"));
}
}
public void CopyTo(Array array, int index)
{
((IList)this.Source.List).CopyTo(array, index);
}
public int Count
{
get { return this.Source.Count; }
}
public bool IsSynchronized
{
get { return false; }
}
public object SyncRoot
{
get { return this._syncRoot; }
}
public IEnumerator<T> GetEnumerator()
{
return this.Source.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
private EntitySet<T> Source
{
get { return this._source; }
}
#endregion
#region INotifyCollectionChanged
public event NotifyCollectionChangedEventHandler CollectionChanged;
private void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
this.CollectionChanged?.Invoke(this, e);
}
private void OnSourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
this.OnCollectionChanged(e);
}
#endregion
#region ICollectionChangedListener
void ICollectionChangedListener.OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
this.OnSourceCollectionChanged(sender, e);
}
#endregion
return new ListCollectionView(this);
}
[Obsolete("ICollectionViewFactory is planned for removal in the next major version. Use CollectionViewSource.GetDefaultView() instead.")]
ICollectionView ICollectionViewFactory.CreateView()
{
return new ListCollectionView(this);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/OpenRiaServices.Client/Framework/EntitySet.cs` around lines 1447 - 1451,
Update the Obsolete attribute message on ICollectionViewFactory.CreateView to
guide users toward the recommended replacement: mention that callers should use
CollectionViewSource.GetDefaultView(...) instead of relying on
ICollectionViewFactory, and state that implementing ICollectionViewFactory will
be removed in the next major version; locate the attribute on the
ICollectionViewFactory.CreateView method (which currently returns a new
ListCollectionView(this)) and replace the message text accordingly.

Assert.AreEqual(0, doe.ErrorCode, "Error code s/b 0");
Assert.AreEqual(OperationErrorStatus.ServerError, doe.Status, "Default status s/b ServerError");
Assert.IsFalse(doe.ValidationErrors.Any(), "default validationErrors should be empty");
Assert.IsFalse(doe.ValidationErrors.Count != 0, "default validationErrors should be empty");
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Simplify repeated empty checks for test readability.

On Line 23, Line 32, Line 41, Line 50, Line 59, Line 69, and Line 79, prefer direct Count == 0/AreEqual(0, Count) assertions over IsFalse(Count != 0).

Suggested cleanup pattern
-            Assert.IsFalse(doe.ValidationErrors.Count != 0, "default validationErrors should be empty");
+            Assert.AreEqual(0, doe.ValidationErrors.Count, "default validationErrors should be empty");

Also applies to: 32-32, 41-41, 50-50, 59-59, 69-69, 79-79

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/OpenRiaServices.Client/Test/Client.Test/Data/DomainOperationExceptionTests.cs`
at line 23, Replace the awkward "Assert.IsFalse(... Count != 0)" checks with
direct zero-count assertions for readability: in DomainOperationExceptionTests
replace assertions like Assert.IsFalse(doe.ValidationErrors.Count != 0, ...)
with Assert.AreEqual(0, doe.ValidationErrors.Count, ...) or
Assert.IsTrue(doe.ValidationErrors.Count == 0, ...) (preserve the original
message text if desired) for each occurrence referencing
doe.ValidationErrors.Count in the test methods.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's resolve it later in a different pr

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Daniel-Svensson, understood! Would you like me to open a GitHub issue to track this cleanup (replacing Assert.IsFalse(... Count != 0) with Assert.AreEqual(0, ..., Count) in DomainOperationExceptionTests.cs) so it doesn't get lost?


🧠 Learnings used
Learnt from: Daniel-Svensson
Repo: OpenRIAServices/OpenRiaServices PR: 512
File: src/OpenRiaServices.Server/Test/DomainServiceDescriptionTest.cs:385-387
Timestamp: 2026-03-08T23:08:53.507Z
Learning: In OpenRiaServices code reviews, ensure that the AssociationTypeDescriptor compatibility wrapper in DomainTypeDescriptionProvider.GetTypeDescriptor is applied only when there is a custom TypeDescriptionProvider and it does NOT originate from an OpenRiaServices assembly with version 5.7.0 or later. For OpenRiaServices >= 5.7.0, entities already emit EntityAssociationAttribute and must not be double-wrapped. This guideline applies to C# source files under src (e.g., any DomainTypeDescriptionProvider usage checks, tests, or related providers).

Comment on lines 329 to 332
private ICollectionView GetICV(EntitySet entitySet)
{
return ((ICollectionViewFactory)entitySet).CreateView();
return CollectionViewSource.GetDefaultView(entitySet);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if EntityCollectionTests.cs also uses the ICollectionViewFactory pattern that should be updated
rg -n "ICollectionViewFactory" --type=cs -C3 src/OpenRiaServices.Client/Test/

Repository: OpenRIAServices/OpenRiaServices

Length of output: 1826


🏁 Script executed:

#!/bin/bash
# Try to locate and check EntityCollectionTests.cs for ICollectionViewFactory usage
rg -n "ICollectionViewFactory" --type=cs src/ 2>/dev/null || echo "Search failed or file not found"

Repository: OpenRIAServices/OpenRiaServices

Length of output: 2454


🏁 Script executed:

#!/bin/bash
# Get the specific lines in EntityCollectionTests.cs around the ICollectionViewFactory usage
sed -n '410,430p' src/OpenRiaServices.Client/Test/Client.Test/Data/EntityCollectionTests.cs

Repository: OpenRIAServices/OpenRiaServices

Length of output: 772


Good alignment with the deprecation of ICollectionViewFactory.CreateView().

Using CollectionViewSource.GetDefaultView() is the recommended WPF pattern and aligns with marking CreateView() as obsolete. Note that GetDefaultView() returns a cached view per source, which differs from CreateView() that creates fresh instances, but this shouldn't affect test isolation since each test creates a new EntitySet.

Consider also updating EntityCollectionTests.cs where GetICV() still casts to ICollectionViewFactory and calls CreateView() directly (line 419), matching the pattern used here.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/OpenRiaServices.Client/Test/Client.Test/Data/EntitySetTests.cs` around
lines 329 - 332, The tests currently use
CollectionViewSource.GetDefaultView(entitySet) in GetICV(EntitySet) to avoid the
obsolete ICollectionViewFactory.CreateView(); update the other test file where
code still casts to ICollectionViewFactory and calls CreateView() (the call in
EntityCollectionTests.cs that obtains an ICollectionView via CreateView) to
instead call CollectionViewSource.GetDefaultView(entitySet) (or centralize by
adding/using the same GetICV helper) so tests no longer rely on the obsolete
CreateView path.

Assert.AreEqual(0, soe.ErrorCode, "Error code s/b 0");
Assert.AreEqual(OperationErrorStatus.Unauthorized, soe.Status, "ctor(msg, status) failed status");
Assert.IsFalse(soe.ValidationErrors.Any(), "default validationErrors should be empty");
Assert.IsFalse(soe.ValidationErrors.Count != 0, "default validationErrors should be empty");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Use clearer empty-check assertions in constructor tests.

On Line 29, Line 40, and Line 51, Assert.IsFalse(Count != 0) is harder to scan than direct zero-count assertions.

Suggested cleanup
-            Assert.IsFalse(soe.ValidationErrors.Count != 0, "default validationErrors should be empty");
+            Assert.AreEqual(0, soe.ValidationErrors.Count, "default validationErrors should be empty");

Also applies to: 40-40, 51-51

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/OpenRiaServices.Client/Test/Client.Test/Data/SubmitOperationExceptionTests.cs`
at line 29, The assertions in SubmitOperationExceptionTests use
Assert.IsFalse(soe.ValidationErrors.Count != 0) which is harder to read; change
each to an explicit zero-count assertion (e.g., Assert.AreEqual(0,
soe.ValidationErrors.Count) or Assert.IsTrue(soe.ValidationErrors.Count == 0) )
for clarity. Update the three occurrences referencing soe.ValidationErrors.Count
in the SubmitOperationExceptionTests constructor/test methods (the assertions
around lines checking Count at 29, 40, and 51) so they directly assert zero
rather than negating a != 0 expression.

// Call RejectChanges and verify ValidationErrors collection is cleared
citiesProvider.RejectChanges();
Assert.IsFalse(zip.ValidationErrors.Any());
Assert.IsFalse(zip.ValidationErrors.Count != 0);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Prefer direct empty assertions to avoid double negation.

On Line 256 and Line 270, Assert.IsFalse(...Count != 0) is correct but less readable than asserting Count == 0.

Suggested cleanup
-                Assert.IsFalse(zip.ValidationErrors.Count != 0);
+                Assert.AreEqual(0, zip.ValidationErrors.Count);
...
-                Assert.IsFalse(zip.ValidationErrors.Count != 0);
+                Assert.AreEqual(0, zip.ValidationErrors.Count);

Also applies to: 270-270

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Test/OpenRiaservices.EndToEnd.Wcf.Test/Data/ErrorPropagationTests.cs` at
line 256, The assertions use double negation
(Assert.IsFalse(zip.ValidationErrors.Count != 0)) which is less readable;
replace both occurrences with a direct empty check such as Assert.AreEqual(0,
zip.ValidationErrors.Count) (or Assert.IsTrue(zip.ValidationErrors.Count == 0))
to assert the ValidationErrors collection is empty, updating the two places that
reference zip.ValidationErrors.Count.

/// Represents a collection of <see cref="Entity"/> instances.
/// </summary>
public abstract class EntitySet : IEnumerable, ICollection, IList, INotifyCollectionChanged, IRevertibleChangeTracking, INotifyPropertyChanged
public abstract class EntitySet : IList, INotifyCollectionChanged, IRevertibleChangeTracking, INotifyPropertyChanged
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants