Starcounter.Linq is a LINQ to SQL Provider for Starcounter. It uses standard LINQ syntax for queries.
It is available for downloading as Starcounter.Linq NuGet package.
| Starcounter.Linq versions | Supported Starcounter version |
|---|---|
| 1.* | 2.3.1, 2.3.2 |
| 2.* | 2.4 |
| 3.* | Nova |
First, in your Starcounter app, add a reference to Starcounter.Linq.dll through NuGet: Install-Package Starcounter.Linq.
DbLinq static class defined in Starcounter.Linq namespace should be used as starting point when you want to build LINQ queries. It contains two important methods: Objects<T> and CompileQuery<T> (many overloaded ones) which are used for two different approaches. For example:
// ad-hoc request
var johns = DbLinq.Objects<Person>().Where(p => p.Name == "John");
// request using compiled query
var peopleByNameQuery = DbLinq.CompileQuery((string name) =>
DbLinq.Objects<Person>().Where(p => p.Name == name));
var jennifers = peopleByNameQuery("Jennifer");With this approach, you build a LINQ expression and obtain data as usual when an application needs it.
DbLinq.Objects<T>() returns a data context Queryable<T> which can be used for obtaining data.
The LINQ expression is translated to SQL every time it's called if DbLinq.Objects<T>() is called. This is an expensive operation. Thus, don't use DbLinq.Objects<T>() in places where it's executed many times.
Example:
// this method is rarely used, so we can use ad-hoc requests here
void Handle(Input.DeleteGroupTrigger action)
{
Db.Transact(() =>
{
var surfaces = DbLinq.Objects<WebTemplate>().Where(x => x.WebTemplateGroup == this.Data);
foreach (WebTemplate surface in surfaces)
{
surface.WebTemplateGroup = null;
}
this.Data.Delete();
});
}Compiled query lets you build a LINQ expression with translated SQL once and use it many times.
DbLinq.CompileQuery<T> returns a delegate Func<IEnumerable<T>> which can be invoked to execute the compiled query. It has many overloads and it supports passing parameters.
Use DbLinq.CompileQuery<T>() in places where the query will be executed many times since it only translates the LINQ statement to SQL one time which makes subsequent calls fast.
Example:
partial class SurfacePage : Json
{
private static readonly Func<IEnumerable<WebTemplate>> SurfaceGroupsQuery = DbLinq.CompileQuery(() =>
DbLinq.Objects<WebTemplateGroup>().Where(x => !x.Deleted));
private static readonly Func<WebTemplate, IEnumerable<WebUrl>> RoutesQuery =
DbLinq.CompileQuery((WebTemplate surface) =>
DbLinq.Objects<WebUrl>()
.Where(x => !x.Deleted && !x.Hidden)
.Where(x => x.Template == surface)
.OrderBy(x => x.SortNumber));
/*
...
*/
// this method is often called, so we should use compiled queries
public void RefreshData()
{
this.SurfaceGroups.Data = SurfaceGroupsQuery();
this.Routes.Data = RoutesQuery(this.Data);
}
}Compiled queries are more restricted than ad-hoc requests since it represents a pre-translated SQL and should support passing parameters. Read more about the restrictions below.
Starcounter.Linq only supports database properties. It is not possible to get access to fields.
Example:
// throws System.MissingFieldException since Name is a field
DbLinq.Objects<WebTemplateGroup>().OrderBy(x => x.Name);
// works well since SortNumber is defined as property
DbLinq.Objects<WebTemplateGroup>().OrderBy(x => x.SortNumber);The exception will be thrown when calling the method which contains the query definition, which means that the exception will not be thrown from Starcounter.Linq code.
Starcounter.Linq uses literal values for FETCH and OFFSET clauses for performance reason, it means that you cannot pass the value when executing a compiled query.
Example:
// works well
var people = DbLinq.Objects<Person>().Take(10).Skip(20).ToList();
// does not work
var query = DbLinq.CompileQuery((int take, int skip) => DbLinq.Objects<Person>().Take(take).Skip(skip));
people = query(10, 20);
// works well
var query = DbLinq.CompileQuery(() => DbLinq.Objects<Person>().Take(10).Skip(20));
people = query();Since comparisons with null values are translated to IS NULL form in SQL, there is no possibility to pass such values with parameters into compiled queries. Starcounter.Linq throws an exception in such cases.
Example:
Office office = GetOffice(); // can be null
Office noOffice = null;
// ad-hoc requests works well without restrictions
var employee1 = DbLinq.Objects<Employee>().FirstOrDefault(p => p.Office != null);
var employee2 = DbLinq.Objects<Employee>().FirstOrDefault(p => p.Office == office);
// works well
var withoutOfficeQuery = DbLinq.CompileQuery(() =>
DbLinq.Objects<Employee>().FirstOrDefault(p => p.Office != null));
var withoutOfficeQuery2 = DbLinq.CompileQuery(() =>
DbLinq.Objects<Employee>().FirstOrDefault(p => p.Office == noOffice));
var withOfficeQuery = DbLinq.CompileQuery((notNullOffice) =>
DbLinq.Objects<Employee>().FirstOrDefault(p => p.Office == notNullOffice));
// it does not work because the SQL query has been translated and IS NULL cannot be inserted
employee1 = withOfficeQuery(null); // ArgumentNullException will be thrown
// that should be written in the following way
employee1 = office == null ? withoutOfficeQuery() : withOfficeQuery(office);The Contains method is supported by ad-hoc requests but not by compiled queries.
Example:
var ages = new[] { 41, 42, 43 };
var person = Objects<Person>().FirstOrDefault(p => ages.Contains(p.Age));Deleting data is supported by ad-hoc requests but not by compiled queries.
Example:
Objects<Person>().Delete(x => x.Age > 40);
Objects<Person>().DeleteAll();SELECTclause, also by usingIQueryable.SelectmethodWHEREclause by using methods:IQueryable.WhereIQueryable.FirstIQueryable.FirstOrDefaultIQueryable.SingleIQueryable.SingleOrDefaultIQueryable.CountIQueryable.Any
ORDER BYclause by using methods:IQueryable.OrderByIQueryable.OrderByDescendingIQueryable.ThenByIQueryable.ThenByDescending
GROUP BYclause by usingIQueryable.GroupBymethod. Aggregation is required by using one of the following methods inside ofIQueryable.Selectone:IEnumerable.CountIEnumerable.LongCountIEnumerable.AverageIEnumerable.MinIEnumerable.MaxIEnumerable.Sum
- Logical operators
AND,OR,NOTby using&&,||,!operators in LINQ queries - Comparison by using:
- operators
=,>,>=,<,<=,<> - method
Object.Equals
- operators
LIKEoperator by using methods:String.ContainsString.EndsWith
STARTS WITHoperator by usingString.StartsWithmethodIEnumerable.Containsmethod in LINQ queriesDbHelper.GetObjectNomethod in LINQ queriesISoperator by usingisoperator in LINQ queriesOFFSETclause by usingIQueryable.SkipmethodFETCHclause by using methods:IQueryable.TakeIQueryable.FirstOrDefaultIQueryable.FirstIQueryable.SingleIQueryable.SingleOrDefault
COUNTfunction by using methodsIQueryable.CountandIQueryable.LongCountAVGfunction by using methodIQueryable.AverageMINfunction by using methodIQueryable.MinMAXfunction by using methodIQueryable.MaxSUMfunction by using methodIQueryable.Sum- Calculating expression values in LINQ queries
DELETE FROMstatement by using methods:Starcounter.Linq.Queriable.DeleteStarcounter.Linq.Queriable.DeleteAll
- Using of generic types for a query context
- Overridden
IQueryable.ToStringfor providing of generated SQL queries
IQueryable.Allmethod - #31- Querying a specific set of data properties (currently supports only single property querying) - #60
For the latest news, look at the Starcounter Blog.