For example considering the Sample App at http://starcounter.io/tutorials/1-introduction-to-part-1/ InvoiceDemo
Following Database objects are declared
using Starcounter;
[Database]
public class Invoice {
public int InvoiceNo;
public string Name;
public decimal Total {
get {
return Db.SQL<decimal>(@"SELECT sum(r.Total)
FROM InvoiceRow r
WHERE r.Invoice = ?",
this).First;
}
}
public QueryResultRows<InvoiceRow> Items {
get {
return Db.SQL<InvoiceRow>(@"SELECT r
FROM InvoiceRow r
WHERE r.Invoice = ?",
this);
}
}
public Invoice() {
new InvoiceRow() {
Invoice = this
};
}
}
[Database]
public class InvoiceRow {
public Invoice Invoice;
public string Description;
public int Quantity;
public decimal Price;
public decimal Total {
get {
return Quantity * Price;
}
}
public InvoiceRow() {
Quantity = 1;
}
}
If I want to make sure I know the order of Invoice Rows that has been added, I would in standard SQL DB just use an Auto Increment ID. What is the best practice for this in Starcounter?
Starcounter does not provide auto-incremented user ids and I doubt it will due to different reasons. For example:
An example of when it is no obvious what order means and expected by application:
I need to be sure I can sort the rows in order by creation
Creation time can mean:
Since records can be added simultaneously in different transactions, the order of them can differ significantly between the options. Only application developer knows which order is meant and how it will affect user experience (UX).
Starcounter allocates counter-based object identity, so it is similar to auto-increment id, but for all objects/records in the database. However, it is important to remember:
Because of the last point using Starcounter's object identity for ordering record by the creation time is not good idea.
I guess auto-incremented ids in other databases have similar problems. So using them is not so safe if UX should be preserved or significant changes might be met in future (and usually cannot be foreseen).
My opinion is that the order is application specific and should be implemented in the application logic. I see several ways:
__sync_bool_compare_and_swap
or Interlocked.Increment
can be used, which is atomic operation and no locks required, but it is cache unfriendly. Note that it is important to access it outside re-triable transaction scope, i.e., with snapshot isolation. However, using it in such transaction will still work, but counter will be increased on each retry.SELECT MAX(InvoiceRowNo) FROM InvoiceRow
, and increment it. Add unique constraint by creating unique index to avoid the same ids in the case of concurrency. In the case of concurrent increment conflict between transactions will happen and one of the transactions will be retried, while the other will succeed. Note that on high load the conflict might happen often and some unlucky request will be delayed considerably or will never pass thru.