ECSQL in iTwin.js Code Examples
This page contains generic example code that can be used across the IModelDb, ECDb, and IModelConnection classes. In the examples, the identifier iModel
is used as an object that could be any of those classes.
For more info and examples specific to running in the frontend and backend, check out:
See also:
The createQueryReader
Function
All of the iModel classes above provide a createQueryReader
method for executing ECSQL statements on an iModel and reading the results of the query. The execution and results are handled by the returned ECSqlReader.
For refererence, here are all three
createQueryReader
methods.
Here is the TypeScript method signature for createQueryReader
:
createQueryReader(ecsql: string, params?: QueryBinder, config?: QueryOptions): ECSqlReader
The
ecsql
string is the ECSQL statement that will be executed on the iModel. This is where you provide an ECSQL statement to query an iModel. E.g.,SELECT ECInstanceId, ECClassId FROM BisCore.Element
The
params
argument of type QueryBinder contains any bindings for the ECSQL statement.The
config
argument of type QueryOptions is for additional options for how the query will be executed. Some examples are:rowFormat
for determining how query results will look. For an explanation of the available formats, see ECSQL Row Formats.limit
for specifying how many rows can be returned at most.restartToken
for canceling the execution of a previous query and starting a new one.
Iterating Over Query Results
Use the ECSqlReader created by the createQueryReader
function to iterate over query results. There are three primary ways to do so:
1. Stream them using ECSqlReader as an asynchronous iterator.
for await (const row of iModel.createQueryReader("SELECT ECInstanceId, ECClassId FROM bis.Element")) {
console.log(`ECInstanceId is ${row[0]}`);
console.log(`ECClassId is ${row.ecclassid}`);
}
Results are QueryRowProxy objects. See Handling a Row of Query Results for how to handle the results.
2. Iterate over them manually using ECSqlReader.step.
const reader = iModel.createQueryReader("SELECT ECInstanceId, ECClassId FROM bis.Element");
while (await reader.step()) {
console.log(`ECInstanceId is ${reader.current[0]}`);
console.log(`ECClassId is ${reader.current.ecclassid}`);
}
Results are QueryRowProxy objects. See Handling a Row of Query Results for how to handle the results.
3. Capture all of the results at once in an array using QueryRowProxy.toArray.
const reader = iModel.createQueryReader("SELECT ECInstanceId, ECClassId FROM bis.Element");
const allRows = await reader.toArray();
console.log(`First ECInstanceId is ${allRows[0][0]}`);
console.log(`First ECClassId is ${allRows[0][1]}`);
Results are JavaScript literals. See Working with Rows as JavaScript Literals for how to handle the results.
Handling a Row of Query Results
The format of the query results is dependent on the provided rowFormat
in the config
parameter. Click here to read about ECSQL Row Formats in detail.
When iterating over each row one at a time (as an asynchronous iterator or with step
), each row will be a QueryRowProxy object. The rows value can then be accessed by column index or by name.
Accessing Row Values By Index
When iterating with a for loop:
for await (const row of iModel.createQueryReader("SELECT ECInstanceId, ECClassId FROM bis.Element")) {
console.log(`ECInstanceId is ${row[0]}`);
console.log(`ECClassId is ${row[1]}`);
}
When iterating with step
:
const reader = iModel.createQueryReader("SELECT ECInstanceId, ECClassId FROM bis.Element");
while (await reader.step()) {
console.log(`ECInstanceId is ${reader.current[0]}`);
console.log(`ECClassId is ${reader.current[1]}`);
}
The
rowFormat
used does not matter when accessing by index; only the order of the selected columns does. The two queries below will return the ECInstanceId and ECClassId values as indexes 0,1 and 1,0 respectively.SELECT ECInstanceId, ECClassId FROM bis.Element SELECT ECClassId, ECInstanceId FROM bis.Element
Accessing Row Values By Name
When iterating with a for loop:
for await (const row of iModel.createQueryReader("SELECT ECInstanceId, ECClassId FROM bis.Element", undefined, { rowFormat: QueryRowFormat.UseECSqlPropertyNames })) {
console.log(`ECInstanceId is ${row.ECInstanceId}`);
console.log(`ECClassId is ${row.ECClassId}`);
}
When iterating with step
:
const reader = iModel.createQueryReader("SELECT ECInstanceId, ECClassId FROM bis.Element", undefined, { rowFormat: QueryRowFormat.UseECSqlPropertyNames });
while (await reader.step()) {
console.log(`ECInstanceId is ${reader.current.ECInstanceId}`);
console.log(`ECClassId is ${reader.current.ECClassId}`);
}
Using Types with the Row Results
Each ECSQL value has a corresponding TypeScript type which is described in ECSQL Parameter Types.
for await (const row of iModel.createQueryReader("SELECT ECInstanceId, ECClassId, Parent, LastMod FROM bis.Element WHERE Model.Id=?", QueryBinder.from(["0x10"]), { rowFormat: QueryRowFormat.UseJsPropertyNames })) {
const id: Id64String = row.id;
const className: string = row.className;
const parent: NavigationValue = row.parent;
const lastMod: string = row.lastMod;
}
Working with Rows as JavaScript Literals
Call .toRow()
on the row to convert it from a QueryRowProxy
object to a JavaScript literal. The format of the literal is dependent on the provided rowFormat
in the config
parameter. Check out ECSQL Row Formats for more details.
Note: With the deprecation of
.query
in 3.7 and the switch to using ECSqlReader to handle query results, rows were changed from being JavaScript literals toQueryRowProxy
s. Using.toRow()
may fix any issues that emerged due to this change.
When iterating with a for loop:
for await (const row of iModel.createQueryReader("SELECT * FROM bis.Element")) {
const jsRow: object = row.toRow(); // explicitly typed for example purposes
}
When iterating with step
:
const reader = iModel.createQueryReader("SELECT * FROM bis.Element");
while (await reader.step()) {
const jsRow: object = reader.current.toRow(); // explicitly typed for example purposes
}
When using toArray
:
const reader = iModel.createQueryReader("SELECT * FROM bis.Element");
const jsRows = await reader.toArray();
Specifying Row Formats
The format of of a row is dependent on the provided rowFormat
in the config
parameter of createQueryReader
. The row formats are specified by supplying a QueryRowProxy enum.
Check out ECSQL Row Formats for more details.
QueryRowFormat.UseECSqlPropertyIndexes
This is the default format when no rowFormat
is specified. Column values should refered to by an index which is ordered by the columns specified in the SELECT statement.
for await (const row of iModel.createQueryReader("SELECT ECInstanceId, ECClassId, Parent, LastMod FROM bis.Element WHERE Model.Id=?", QueryBinder.from(["0x10"]), { rowFormat: QueryRowFormat.UseJsPropertyNames })) {
console.log(`ECInstanceId is ${row[0]}`);
console.log(`ECClassId is ${row[1]}`);
console.log(`Parent is ${row[2]}`);
console.log(`LastMod is ${row[3]}`);
}
Here is an example using .toArray
:
const reader = iModel.createQueryReader("SELECT ECInstanceId,ECClassId,Parent,LastMod FROM bis.Element WHERE Model.Id=?", QueryBinder.from(["0x10"]), { rowFormat: QueryRowFormat.UseJsPropertyNames });
const jsRows = await reader.toArray();
console.log(jsRows);
Example Output:
Notice that the individual rows are returned as arrays.
[
[
'0x17',
'0x8d',
null,
'2017-07-25T20:44:59.711Z'
],
[
'0x18',
'0x67',
{ Id: '0x17', RelECClassId: '0x66' },
'2017-07-25T20:44:59.711Z'
],
...
]
QueryRowFormat.UseECSqlPropertyNames
Column values should refered to by their ECSQL property names.
for await (const row of iModel.createQueryReader("SELECT ECInstanceId, ECClassId, Parent, LastMod FROM bis.Element WHERE Model.Id=?", QueryBinder.from(["0x10"]), { rowFormat: QueryRowFormat.UseJsPropertyNames })) {
console.log(`ECInstanceId is ${row.ECInstanceId}`);
console.log(`ECClassId is ${row.ECClassId}`);
console.log(`Parent is ${row.Parent}`);
console.log(`LastMod is ${row.LastMod}`);
}
Here is an example using .toArray
:
const reader = iModel.createQueryReader("SELECT ECInstanceId,ECClassId,Parent,LastMod FROM bis.Element WHERE Model.Id=?", QueryBinder.from(["0x10"]), { rowFormat: QueryRowFormat.UseJsPropertyNames });
const jsRows = await reader.toArray();
console.log(jsRows);
Example Output:
[
{
ECInstanceId: '0x17',
ECClassId: '0x8d',
LastMod: '2017-07-25T20:44:59.711Z'
},
{
ECInstanceId: '0x18',
ECClassId: '0x67',
Parent:
{
Id: '0x17',
RelECClassId: '0x66'
},
LastMod: '2017-07-25T20:44:59.711Z'
},
...
]
QueryRowFormat.UseJsPropertyNames
Column values should be refered to by their JavaScript property names. The mapping from ECSQL property names to JavaScript property names is described in ECSQL Row Formats.
for await (const row of iModel.createQueryReader("SELECT ECInstanceId,ECClassId,Parent,LastMod FROM bis.Element WHERE Model.Id=?", QueryBinder.from(["0x10"]), { rowFormat: QueryRowFormat.UseJsPropertyNames })) {
console.log(`ECInstanceId is ${row.id}`);
console.log(`ECClassId is ${row.className}`);
console.log(`Parent is ${row.parent}`);
console.log(`LastMod is ${row.lastMod}`);
}
Here is an example using .toArray
:
const reader = iModel.createQueryReader("SELECT ECInstanceId,ECClassId,Parent,LastMod FROM bis.Element WHERE Model.Id=?", QueryBinder.from(["0x10"]), { rowFormat: QueryRowFormat.UseJsPropertyNames });
const jsRows = await reader.toArray();
console.log(jsRows);
Example Output:
[
{
id: '0x17',
className: 'BisCore.SpatialCategory',
lastMod: '2017-07-25T20:44:59.711Z'
},
{
id: '0x18',
className: 'BisCore.SubCategory',
parent:
{
id: '0x17',
relClassName: 'BisCore.CategoryOwnsSubCategories'
},
lastMod: '2017-07-25T20:44:59.711Z'
},
...
]
Notice how the keys in the above JSON are converted from ECProperty names to names that conform to JavaScript standards as described in ECSQL Row Formats. For example, "ECInstanceId" is mapped to "id".
Parameter Bindings
See ECSQL Parameter Types to learn which types to use for the parameters when binding.
Positional parameters
for await (const row of iModel.createQueryReader("SELECT ECInstanceId,ECClassId,Parent,LastMod FROM bis.Element WHERE CodeValue=? AND LastMod>=?",
QueryBinder.from(["MyCode", "2018-01-01T12:00:00Z"]), { rowFormat: QueryRowFormat.UseJsPropertyNames })) {
console.log(`${row.id}, ${row.className}, ${row.parent}, ${row.lastMod}`);
}
Named parameters
for await (const row of iModel.createQueryReader("SELECT ECInstanceId,ECClassId,Parent,LastMod FROM bis.Element WHERE CodeValue=:code AND LastMod>=:lastmod",
QueryBinder.from({ code: "MyCode", lastmod: "2018-01-01T12:00:00Z" }), { rowFormat: QueryRowFormat.UseJsPropertyNames })) {
console.log(`${row.id}, ${row.className}, ${row.parent}, ${row.lastMod}`);
}
Navigation properties
Navigation properties are structs made up of the Id of the related instance and the backing ECRelationshipClass. The NavigationBindingValue interface is used to bind values to navigation property parameters.
for await (const row of iModel.createQueryReader("SELECT ECInstanceId FROM bis.Element WHERE Parent=?", QueryBinder.from([{ id: "0x132" }]), { rowFormat: QueryRowFormat.UseJsPropertyNames })) {
console.log(`${row.id}`);
}
Because of the struct nature of navigation properties, you can also use its members in the ECSQL. The following example illustrates this by specifying the Id member of a navigation property.
for await (const row of iModel.createQueryReader("SELECT ECInstanceId FROM bis.Element WHERE Parent.Id=?", QueryBinder.from(["0x132"]), { rowFormat: QueryRowFormat.UseJsPropertyNames })) {
console.log(`${row.id}`);
}
Struct properties
You can either parameterize a struct property as a whole or parameterize individual members of the struct. See Struct properties in ECSQL for the ECSQL background.
The ECSQL examples used in this section refer to the sample ECSchema in Struct properties in ECSQL.
Binding structs as a whole
for await (const row of iModel.createQueryReader("SELECT Name FROM myschema.Company WHERE Location=?", QueryBinder.from([{ street: "7123 Main Street", zip: 30211 }]), { rowFormat: QueryRowFormat.UseJsPropertyNames })) {
console.log(`${row.name}`);
}
Binding to individual struct members
for await (const row of iModel.createQueryReader("SELECT Name FROM myschema.Company WHERE Location.Street=? AND Location.Zip=?", QueryBinder.from(["7123 Main Street", 32443]), { rowFormat: QueryRowFormat.UseJsPropertyNames })) {
console.log(`${row.name}`);
}
The two ECSQL examples used in this section amount to the same results.
Array properties
See Array properties in ECSQL for the ECSQL background.
The ECSQL examples used in this section refer to the sample ECSchema in Array properties in ECSQL.
for await (const row of iModel.createQueryReader("SELECT Name FROM myschema.Company WHERE PhoneNumbers=?", QueryBinder.from([["+16134584201", "+16134584202", "+16134584222"]]), { rowFormat: QueryRowFormat.UseJsPropertyNames })) {
console.log(`${row.name}`);
}
Last Updated: 13 November, 2024