From playground to production: typed C# for SyteLine IDOs
Stop guessing IDO property names. Generate a strongly-typed C# SDK from your own SyteLine tenant.
var items = await SlItems.Query()
.Select(x => x.Item, x => x.Description, x => x.UnitCost)
.Filter("UnitCost > 100 AND (Description LIKE 'FG%' OR Stocked = 1)")
.OrderBy("UnitCost DESC, Item")
.Take(50)
.ToListAsync(); // typed rows; UnitCost is decimal?, not a string
639 properties on SLItems alone, IntelliSense on every one of them, including your
ue_ customizations.
The pain of raw CSI REST development
Every CSI developer has lived these:
- Property names guessed from form fields or SQL tables. A typo returns silent nulls, not an error.
- Stored-procedure invokes take a positional string array. One misplaced parameter and the SP runs with the wrong values, with no compile error.
- Every value comes back as a JSON string. Dates arrive as
"20260518 17:05:15.070". - Token exchange, session lifetime, paging bookmarks, retries: all hand-rolled.
// 1. OAuth token dance (client id/secret + service account keys)... ~30 lines elsewhere
// 2. Build the load URL by hand; property names are guesses
var url = $"{baseUrl}/load/SLItems?properties=Item,Description,UnitCost" +
"&filter=UnitCost%20%3E%20100&orderBy=UnitCost%20DESC,Item&recordCap=50";
var resp = await http.GetAsync(url);
var json = await resp.Content.ReadAsStringAsync();
// 3. Everything is a string; parse each field yourself and hope the name was right
foreach (var row in JsonDocument.Parse(json).RootElement.GetProperty("Items").EnumerateArray())
{
var costText = row.GetProperty("UnitCost").GetString(); // "40.57200000"
var cost = decimal.Parse(costText!, CultureInfo.InvariantCulture);
// dates arrive as "20260518 17:05:15.070" and need a custom parse too
}
// 4. MoreRowsExist? Build the bookmark continuation loop yourself.The same read, with the generated SDK, is the six lines at the top of this page.
What csigen does
- Run one command against your own tenant with your own
.ionapi. Credentials never leave your machine; the tool reads metadata only. - It emits a compilable C# project: typed row records, typed method wrappers from the IDO definition, a fluent query builder, and JSON converters for CSI's string-typed wire format.
- Because it reads your tenant live, customizations (
ue_fields) come along with exact types.
The generated surface, by example
1. Typed projection query
var rows = await SlItems.Query()
.Select(x => x.Item, x => x.Description, x => x.UnitCost)
.OrderBy("Item").Take(3)
.ToListAsync(client);FG-1001 Finished widget, blue 40.572
FG-1002 Finished widget, red 180.320
FG-1003 Finished widget, green 180.3202. Auto-paging stream
Bookmark handling disappears.
await foreach (var row in SlItems.Query()
.Select(x => x.Item)
.OrderBy("Item")
.Take(500) // page size; bookmarks handled for you
.ToStreamAsync(client))
{
Process(row.Item);
}3. Typed method call with named parameters
The positional-roulette killer. Inputs are named, typed, and placed in the IDO's parameter order for you.
InvokeResult result = await SlItems.CanUpdateCostsSpAsync(
pItem: "FG-1001",
pCostType: "S",
pCostMethod: "S",
pPMTCode: "",
pJob: "",
pSuffix: "",
whse: "MAIN");
// Under the hood: positional slots in IDO Sequence order, output slots as placeholders.4. Ambient client, set once
CsiGatewayDefaults.Client = client; // Program.cs, once
var pick = await SlItems.Query().Select(x => x.Item).Take(10).ToListAsync(); // no client arg5. Regeneration-safe extensions
Hand-written members live in a sibling file and survive a csigen re-run.
// Yours, in a sibling file; never overwritten by a csigen re-run.
public sealed partial record SlItemsRow
{
public string DisplayName => $"{Item} - {Description}";
}The speed is in what disappears
csigen does not type faster. It deletes whole categories of write-run-debug loops.
| Task | Raw ION REST | With SDK + csigen |
|---|---|---|
| Find a property on a 639-column IDO | Search docs/SQL, guess, re-run | IntelliSense; a typo is a compile error |
| Read a decimal or date | Parse strings by hand, culture bugs | Typed properties; converters handle CSI formats |
| Call an SP with 12 parameters | Hand-build a positional string array | Named, typed arguments in order |
| Page through 10k rows | Manual bookmark loop | await foreach auto-paging |
| Tenant adds a ue_ column | Hand-edit models, hope | Re-run csigen; the typed property appears |
| Auth / session / retries | Hand-rolled | SDK-managed session and resilience |
SLItems alone carries 639 properties and 88 methods with 545 parameters on a customized tenant. Nobody memorizes that. Your compiler can.
Your customizations come along
Generated from a live tenant, the row record includes your ue_ fields as first-class,
correctly typed members. This is the part no stock, pre-built SDK can do, and it is why csigen runs at
your site instead of shipping generic DLLs.
// Generated from a live tenant: ue_ fields are first-class, correctly typed members.
DateTime? added = row.ue_CustomDate;
string? code = row.ue_CustomCode;Getting started
Today
The CsiGateway.Client SDK is packaged and shipping. Explore any IDO live in the playground, then write typed queries against it.
Early access csigen
Generate a typed SDK from your own tenant (generate --ido YourIdo). We are onboarding early users now.
On the roadmap: relationship navigation, multi-IDO runs, typed method outputs, and dotnet-tool packaging.

