Skip to content

Language Reference

Jezz Santos edited this page Aug 25, 2023 · 14 revisions

The following is a description of the QueryAny language, used by developers in their domain logic, to access data from any repository implementation.

From

A query must start by defining the primary entity being queried.

Example:

Query.From<CustomerEntity>()

The From<TEntity> method alone does not define any useful query, it must be combined with a Join() or 'Where() method call.

TEntity must implement INamedEntity that defines the name of the entity collection in the repository.

Naming Rule: If no name is defined (ie.INamedEntity.EntityName returns null) QueryAny will derive the name of the entity collection from the class name. The convention is to name these classes XXXEntity, and if that convention is used, then the entity collection name will be derived as XXX. Otherwise, the entity collection name will be determined as the whole class name (in lowercase).

Join

The From<TEntity> method defines a primary entity in the repository, but you can also filter that instance of the entity with entities in other containers, or aggregate that instance of the entity with fields from other entities defined in the repository (typically, in other logical containers).

Join is similar in notion to a SQL JOIN except for the fact that you can only LEFT or INNER join on secondary entities, since RIGHT and OUTER joins

(a) have no net effect when filtering.

(b) do not guarantee that an instance of the primary entity (with a valid Id field) will be available in the result set when aggregating.

Once the primary entity is joined to other entities, use SelectFromJoin to define any aggregations.

Constraint Only supports the logical joins (defined by set theory): Inner (default), Left.

Constraint: You can only join on another entity once.

Note: Referential Integrity between entities in a repository is a concern of the repository implementation - QueryAny does not understand, assume nor enforce referential integrity between entities.

Example: single join to get another entity (in a one-to-one relationship)

Query.From<CustomerEntity>()
	.Join<ProfileEntity, string>(customer => customer.Id, profile => profile.CustomerId)

Example: multiple joins to get other entities (in a one-to-one relationships)

Query.From<CustomerEntity>()
	.Join<ProfileEntity, string>(customer => customer.Id, profile => profile.CustomerId)
	.AndJoin<PreferencesEntity, string>(customer => customer.Id, preferences => preferences.CustomerId, JoinType.Outer)

Where

The Where methods creates conditions for the query.

QueryAny supports multiple where statements that can be logically ANDed and logically ORed together, and even supports nested where statements for supporting complex conditions.

Any datatype of any field in the entity can be used in the evaluation of the condition.

Several evaluation operators are supported: EqualTo, GreaterThan, GreaterThanEqualTo, LessThan, LessThanEqualTo, NotEqualTo, Like

Constraint: there must be at least one Where statement, or one and only one WhereAll statement in the query.

Example: simple logical conditional

Query.From<CustomerEntity>()
	.Where(customer => customer.Id, EqualTo, "25")
	.OrWhere(customer => customer.Id, EqualTo, "75")

Example: complex nested logical conditionals

Query.From<CustomerEntity>()
	.Where(customer => customer.Id, EqualTo, "25")
	.OrWhere(subQuery =>
		subQuery.Where(customer => customer.Id, NotEqualTo, "25")
		.AndWhere(customer => customer.CreatedDateUc, GreaterThan, now))

WhereAll method allows you to define that there will be no Where statements to make. It implies no filters at all. Thus, you cannot use Where, or AndWhere/OrWhere or WhereAll or WhereNoOp methods after using this method.

WhereNoOp is a convenience method that allows you to define that there are no Where statements to make yet. It is used in complex conditional statements. It implies no filters yet, but they may be added later (using AndWhere/OrWhere). Thus, you cannot use Where, or WhereAll or WhereNoOp methods after using this method.

Select (Restriction)

By default, all fields from the primary entity will be returned in query results. The use of the Select method is optional.

Use Select method to restrict the fields returned in the query results, all other fields are returned as null (or default values).

Example: restrict returned fields of primary entity

Query.From<CustomerEntity>()
	.Where(customer => customer.Id, EqualTo, "25")
	.Select(customer => customer.Id)
	.Select(customer => customer.Name)

Result:

{
  "Id": "25",
  "Name": "Contoso"
}

SelectFromJoin (Aggregation)

If another entity is joined (using Join) to the primary entity, it is possible to project any field from the joined entity into the value of a field of the primary entity. This is one way aggregate entities can be constructed.

Use the SelectFromJoin method to project a joined field into a field of the primary entity.

Note: If you define any SelectFromJoin clauses, you must define all Select clauses for all other fields that you require in the result set.

Constraint: If the joined row does not exist, then the value of the joined field remains the value read for the primary entity.

Example: project a joined field into a field of the primary entity

Query.From<CustomerEntity>()
        .Join<ProfileEntity, string>(customer => customer.Id, profile => profile.CustomerId)
	.Where(customer => customer.Id, EqualTo, "25")
	.Select(customer => customer.Id)
	.SelectFromJoin<ProfileEntity>(customer => customer.AvatarPicture, profile => profile.Avatar)

Result:

{
  "Id": "25",
  "AvatarPicture": "http://server.com/avatars/25"
}

Take and Skip

You can paginate returned results by specifying Take and Skip

Example: requesting first page of results

Query.From<CustomerEntity>()
        .Join<ProfileEntity, string>(customer => customer.Id, profile => profile.CustomerId)
	.Where(customer => customer.Id, EqualTo, "25")
	.Take(100)

Example: requesting 3rd page of results

Query.From<CustomerEntity>()
        .Join<ProfileEntity, string>(customer => customer.Id, profile => profile.CustomerId)
	.Where(customer => customer.Id, EqualTo, "25")
	.Skip(200).Take(100)

OrderBy

You can order returned results by specifying OrderBy()

Constraint: You can only order on fields of the primary entity.

Constraint: You cannot order on fields that are not included in Select or SelectFromJoin lists (if any of these are defined)

Example:

Query.From<CustomerEntity>()
       .Join<ProfileEntity, string>(customer => customer.Id, profile => profile.CustomerId)
   .Where(customer => customer.Id, EqualTo, "25")
   .OrderBy(customer => customer.Name)

Other Notes

Get Aggregations

If you decide that an instance of your implementation of IStorage<TEntity> should be creating an entity from an aggregate of collections of other entities from a specific repository, then you may want to consider using a QueryAny.QueryClause in your implementation of IStorage<TEntity>.Get(string id).

For example, executing this against your repository:

var query = Query.From<CustomerEntity>()
                .Join<ProfileEntity, string>(customer => customer.Id, profile => profile.CustomerId)
                .Where(customer => customer.Id, EqualTo, id)

That way you can return an aggregate entity from the repository instead of just part of an entity from one collection in the repository.

Clone this wiki locally