Throw ObjectNotFoundException from FindRequired; add run-console wrappers
- DbBase.FindRequired / Client.FindByNameRequired now throw the domain-specific ObjectNotFoundException (NHibernate lineage; Rails equivalent: ActiveRecord::RecordNotFound) on a lookup miss; the no-context case stays InvalidOperationException (configuration error) - Console demo catches the new type; tests (18)/(21) assert it, (19)/(23) keep InvalidOperationException with rationale comments - scripts/run-console.sh delegates to dotnet.sh; run-console.ps1 is the Windows/PowerShell counterpart (mise-first, repo-root cd) Yak: objectnotfoundexception-run-console-wrappers-3p8b
This commit is contained in:
@@ -44,9 +44,6 @@ foreach (var client in DbBase.Context!.Tracked.OfType<Client>())
|
||||
// ----- find by name -------------------------------------------------------
|
||||
Console.WriteLine("\n=== Find by Name (null-returning) ===");
|
||||
// No context argument: FindByName falls back to the DbBase.Context singleton.
|
||||
// This is the ActiveRecord ergonomic — call sites don't carry the context,
|
||||
// but the null that comes back on a miss can't tell "no such client" from
|
||||
// "nobody configured the context".
|
||||
var found = Client.FindByName("Jane Doe");
|
||||
if (found is not null)
|
||||
{
|
||||
@@ -66,7 +63,7 @@ try
|
||||
Console.WriteLine($"Found: {required.Name} [{required.Id}]");
|
||||
Console.WriteLine($" Orders: {required.Orders.Count}");
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
catch (ObjectNotFoundException ex)
|
||||
{
|
||||
Console.WriteLine($"Not found: {ex.Message}");
|
||||
}
|
||||
@@ -79,7 +76,7 @@ try
|
||||
{
|
||||
Client.FindByNameRequired("Nobody Here");
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
catch (ObjectNotFoundException ex)
|
||||
{
|
||||
var msg = ex.Message.ReplaceLineEndings(" ").Trim();
|
||||
Console.WriteLine($"FindByNameRequired(\"Nobody Here\"): throws - {msg}");
|
||||
|
||||
@@ -43,9 +43,10 @@ public class Client : DbBase
|
||||
|
||||
/// <summary>
|
||||
/// Throwing variant of <see cref="FindByName(string,DbContext?)"/>. Returns
|
||||
/// the matching <see cref="Client"/> or throws <see cref="InvalidOperationException"/>
|
||||
/// with a message that includes the searched <paramref name="name"/> and
|
||||
/// whether a context was configured.
|
||||
/// the matching <see cref="Client"/> or throws <see cref="ObjectNotFoundException"/>
|
||||
/// with a message that includes the searched <paramref name="name"/>.
|
||||
/// Throws <see cref="InvalidOperationException"/> instead when no context at all
|
||||
/// is configured (a configuration error, not a lookup miss).
|
||||
///
|
||||
/// Delegates to <see cref="DbBase.FindRequired{T}"/>.
|
||||
/// </summary>
|
||||
@@ -53,8 +54,11 @@ public class Client : DbBase
|
||||
/// <param name="db">The context whose tracked entities to search. When
|
||||
/// <c>null</c>, falls back to the <see cref="DbBase.Context"/> singleton.</param>
|
||||
/// <returns>The first client whose <see cref="Client.Name"/> equals <paramref name="name"/>.</returns>
|
||||
/// <exception cref="ObjectNotFoundException">
|
||||
/// Thrown when no matching client is found.
|
||||
/// </exception>
|
||||
/// <exception cref="InvalidOperationException">
|
||||
/// Thrown when no matching client is found or no context is configured.
|
||||
/// Thrown when no context is configured (neither passed explicitly nor set as the singleton).
|
||||
/// </exception>
|
||||
public static Client FindByNameRequired(string name, DbContext? db = null)
|
||||
=> DbBase.FindRequired<Client>(c => c.Name == name, db, $"name == \"{name}\"");
|
||||
|
||||
@@ -86,8 +86,9 @@ public class DbBase
|
||||
/// <summary>
|
||||
/// Throwing variant of <see cref="Find{T}(System.Predicate{T},DbContext?)"/>. Finds the first entity
|
||||
/// whose runtime type matches <typeparamref name="T"/> and satisfies
|
||||
/// <paramref name="predicate"/>. Throws <see cref="InvalidOperationException"/>
|
||||
/// when no match is found or when no <see cref="Context"/> singleton is configured.
|
||||
/// <paramref name="predicate"/>. Throws <see cref="ObjectNotFoundException"/>
|
||||
/// when no match is found, and <see cref="InvalidOperationException"/> when no
|
||||
/// <see cref="Context"/> singleton is configured (a configuration error, not a lookup miss).
|
||||
///
|
||||
/// The exception message includes <paramref name="predicateToString"/> so
|
||||
/// callers can debug which lookup failed.
|
||||
@@ -98,8 +99,11 @@ public class DbBase
|
||||
/// <param name="predicateToString">A human-readable description of the predicate, used in the
|
||||
/// exception message when the search fails.</param>
|
||||
/// <returns>The first matching entity.</returns>
|
||||
/// <exception cref="ObjectNotFoundException">
|
||||
/// Thrown when no entity matches <paramref name="predicate"/>.
|
||||
/// </exception>
|
||||
/// <exception cref="InvalidOperationException">
|
||||
/// Thrown when no entity matches <paramref name="predicate"/> or when no context is available.
|
||||
/// Thrown when no context is available (neither passed explicitly nor set as the singleton).
|
||||
/// </exception>
|
||||
public static T FindRequired<T>(Predicate<T> predicate, DbContext? db, string predicateToString)
|
||||
where T : DbBase
|
||||
@@ -117,7 +121,7 @@ public class DbBase
|
||||
if (e is T candidate && predicate(candidate))
|
||||
return candidate;
|
||||
|
||||
throw new InvalidOperationException(
|
||||
throw new ObjectNotFoundException(
|
||||
$"FindRequired<{typeof(T).Name}>({predicateToString}) — " +
|
||||
$"no matching {typeof(T).Name} found in context.");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace Before;
|
||||
|
||||
/// <summary>
|
||||
/// Thrown when a "required" ActiveRecord-style lookup —
|
||||
/// <see cref="DbBase.FindRequired{T}"/> / <see cref="Client.FindByNameRequired"/> —
|
||||
/// finds no matching entity in the context.
|
||||
///
|
||||
/// Named after NHibernate's <c>ObjectNotFoundException</c> (Rails' ActiveRecord
|
||||
/// raises <c>ActiveRecord::RecordNotFound</c> for the same situation), so a
|
||||
/// lookup miss is distinguishable from unrelated
|
||||
/// <see cref="InvalidOperationException"/>s such as EF's <c>Single()</c>
|
||||
/// "Sequence contains no elements".
|
||||
/// </summary>
|
||||
public class ObjectNotFoundException : Exception
|
||||
{
|
||||
public ObjectNotFoundException(string message) : base(message) { }
|
||||
}
|
||||
Reference in New Issue
Block a user