Project · EF Core companions
EF read-scope and cursor companion
Eight independent BCL checks for supplied read scope, deletion policy and composite cursors; no authorization, EF translation or database emulation.
All C# source files below are loaded together in the playground. Files written by a run live in its virtual directory.
Companion.csproj
<Project Sdk="Microsoft.NET.Sdk"><PropertyGroup><OutputType>Exe</OutputType><TargetFramework>net9.0</TargetFramework><ImplicitUsings>enable</ImplicitUsings><Nullable>enable</Nullable></PropertyGroup></Project>
Program.cs
Console.WriteLine(ReadChecks.Run() + " read checks passed");
Domain/ReadRules.cs
namespace SharpRune.EfCompanion;
public sealed record Row(string Tenant, int SortKey, int Id, bool Deleted);
public sealed record Cursor(int SortKey, int Id);
public static class ReadRules
{
public static int[] Page(IEnumerable<Row> rows, string trustedTenant, bool includeDeleted, Cursor? after, int size)
{
if (string.IsNullOrWhiteSpace(trustedTenant)) throw new ArgumentException("Trusted scope required", nameof(trustedTenant));
if (size is < 1 or > 100) throw new ArgumentOutOfRangeException(nameof(size));
return rows.Where(x => x.Tenant == trustedTenant && (includeDeleted || !x.Deleted))
.Where(x => after is null || x.SortKey > after.SortKey || x.SortKey == after.SortKey && x.Id > after.Id)
.OrderBy(x => x.SortKey).ThenBy(x => x.Id).Take(size).Select(x => x.Id).ToArray();
}
}
Checks/ReadChecks.cs
using SharpRune.EfCompanion;
public static class ReadChecks
{
public static int Run()
{
var rows = new[] { new Row("A", 10, 1, false), new Row("A", 10, 2, true), new Row("A", 10, 3, false), new Row("A", 20, 4, false), new Row("B", 5, 5, false) };
int count = 0;
void Check(bool value, string name) { if (!value) throw new InvalidOperationException(name); count++; }
string Join(int[] values) => string.Join(",", values);
Check(Join(ReadRules.Page(rows, "A", false, null, 2)) == "1,3", "first page ties");
Check(Join(ReadRules.Page(rows, "A", false, new Cursor(10, 3), 2)) == "4", "exclusive next page");
Check(Join(ReadRules.Page(rows, "A", true, null, 3)) == "1,2,3", "include deleted scope");
Check(Join(ReadRules.Page(rows, "B", false, null, 2)) == "5", "separate supplied scope");
Check(ReadRules.Page(rows, "Unknown", false, null, 2).Length == 0, "empty matching set");
bool invalid = false; try { ReadRules.Page(rows, "", false, null, 2); } catch (ArgumentException) { invalid = true; }
Check(invalid, "missing scope rejected");
invalid = false; try { ReadRules.Page(rows, "A", false, null, 0); } catch (ArgumentOutOfRangeException) { invalid = true; }
Check(invalid, "zero size rejected");
invalid = false; try { ReadRules.Page(rows, "A", false, null, 101); } catch (ArgumentOutOfRangeException) { invalid = true; }
Check(invalid, "oversized page rejected");
return count;
}
}