OrmLite​
This release saw a lot of effort towards adding new features to OrmLite:
Pluggable Complex Type Serializers​
One of the most requested features to enable pluggable serialization for complex types in OrmLite is now supported. This can be used to specify different serialization strategies for each available RDBMS provider, e.g:
//ServiceStack's JSON and JSV Format
SqliteDialect.Provider.StringSerializer = new JsvStringSerializer();
PostgreSqlDialect.Provider.StringSerializer = new JsonStringSerializer();
//.NET's XML and JSON DataContract serializers
SqlServerDialect.Provider.StringSerializer = new DataContractSerializer();
MySqlDialect.Provider.StringSerializer = new JsonDataContractSerializer();
//.NET XmlSerializer
OracleDialect.Provider.StringSerializer = new XmlSerializableSerializer();
You can also provide a custom serialization strategy by implementing IStringSerializer.
By default all dialects use the existing JsvStringSerializer, except for PostgreSQL which due to its built-in support for JSON, now uses the JSON format by default.
Breaking Change​
Using JSON as a default for PostgreSQL may cause issues if you already have complex types blobbed with the previous JSV Format. You can revert back to the old behavior by resetting it back to the JSV format with:
PostgreSqlDialect.Provider.StringSerializer = new JsvStringSerializer();
New Global Insert / Update Filters​
Similar to interceptors in some heavy ORM's, new Insert and Update filters were added which get fired just before any insert or update operation using OrmLite's typed API's (i.e. not dynamic SQL or partial updates using anon types). This functionality can be used for easily auto-maintaining Audit information for your POCO data models, e.g:
public interface IAudit
{
DateTime CreatedDate { get; set; }
DateTime ModifiedDate { get; set; }
string ModifiedBy { get; set; }
}
OrmLiteConfig.InsertFilter = (dbCmd, row) => {
var auditRow = row as IAudit;
if (auditRow != null)
auditRow.CreatedDate = auditRow.ModifiedDate = DateTime.UtcNow;
};
OrmLiteConfig.UpdateFilter = (dbCmd, row) => {
var auditRow = row as IAudit;
if (auditRow != null)
auditRow.ModifiedDate = DateTime.UtcNow;
};
Which will ensure that the CreatedDate
and ModifiedDate
fields are populated on every insert and update.
Validation​
The filters can also be used for validation where throwing an exception will prevent the operation and bubble the exception, e.g:
OrmLiteConfig.InsertFilter = OrmLiteConfig.UpdateFilter = (dbCmd, row) => {
var auditRow = row as IAudit;
if (auditRow != null && auditRow.ModifiedBy == null)
throw new ArgumentNullException("ModifiedBy");
};
try
{
db.Insert(new AuditTable());
}
catch (ArgumentNullException) {
//throws ArgumentNullException
}
db.Insert(new AuditTable { ModifiedBy = "Me!" }); //succeeds
Custom SQL Customizations​
A number of new hooks were added to provide more flexibility when creating and dropping your RDBMS tables.
Custom Field Declarations​
The new [CustomField]
can be used for specifying custom field declarations in the generated Create table DDL statements, e.g:
public class PocoTable
{
public int Id { get; set; }
[CustomField("CHAR(20)")]
public string CharColumn { get; set; }
[CustomField("DECIMAL(18,4)")]
public decimal? DecimalColumn { get; set; }
}
db.CreateTable<PocoTable>();
Generates and executes the following SQL:
CREATE TABLE "PocoTable"
(
"Id" INTEGER PRIMARY KEY,
"CharColumn" CHAR(20) NULL,
"DecimalColumn" DECIMAL(18,4) NULL
);
Pre / Post Custom SQL Hooks when Creating and Dropping tables​
A number of custom SQL hooks were added that allow you to inject custom SQL before and after tables are created or dropped, e.g:
[PostCreateTable("INSERT INTO TableWithSeedData (Name) VALUES ('Foo');" +
"INSERT INTO TableWithSeedData (Name) VALUES ('Bar');")]
public class TableWithSeedData
{
[AutoIncrement]
public int Id { get; set; }
public string Name { get; set; }
}
And just like other ServiceStack attributes, they can also be added dynamically, e.g:
typeof(TableWithSeedData)
.AddAttributes(new PostCreateTableAttribute(
"INSERT INTO TableWithSeedData (Name) VALUES ('Foo');" +
"INSERT INTO TableWithSeedData (Name) VALUES ('Bar');"));
Custom SQL Hooks are now available to execute custom SQL before and after a table has been created or dropped, i.e:
[PreCreateTable(runSqlBeforeTableCreated)]
[PostCreateTable(runSqlAfterTableCreated)]
[PreDropTable(runSqlBeforeTableDropped)]
[PostDropTable(runSqlAfterTableDropped)]
public class Table {}
Re-factoring OrmLite's SQLite NuGet Packages​
In their latest release, the SQLite dev team maintaining the core SQLite NuGet packages have added a dependency to Entity Framework on their existing Sqlite NuGet packages forcing the installation of Entity Framework for users of OrmLite Sqlite. This change also caused some users to see invalid web.config sections after applying the new web.config.transforms. After speaking to the maintainers they've created a new System.Data.SQLite.Core NuGet package without the entity framework dependency and the problematic web.config.transforms.
Unfortunately this was only added for their bundled x86/x64 NuGet package and not their other System.Data.SQLite.x86 and System.Data.SQLite.x64 which the team have indicated should be deprecated in favor of the x86/x64 bundled System.Data.SQLite.Core package.
As a result of this we're removing the dependency to the Sqlite NuGet packages in both architecture specific ServiceStack.OrmLite.Sqlite32 and ServiceStack.OrmLite.Sqlite64 packages and have instead embedded the Sqlite binaries directly, which will solve the current issues and shield them from any future changes/updates from the upstream Sqlite packages.
New ServiceStack.OrmLite.Sqlite.Windows NuGet package​
Both these arch-specific packages should now be deprecated in favour of a new Sqlite NuGet package supporting both x86/x64 architectures on Windows:
PM> Install-Package ServiceStack.OrmLite.Sqlite.Windows
Which should now be used for future (or existing) projects previously using the old OrmLite.Sqlite32 and OrmLite.Sqlite64 packages.
The Windows-specific package was added in addition to our existing Mono and Windows compatible release:
PM> Install-Package ServiceStack.OrmLite.Sqlite.Mono
Which works cross-platform on Windows and Linux/OSX with Mono should you need cross-platform support.
.NET Service Clients​
New async API's were added for requests marked with returning IReturnVoid
.
This provides a typed API for executing services with no response that was previously missing, e.g:
public class Request : IReturnVoid {}
await client.PostAsync(new Request());
The API's for all sync and async REST operations have been changed to return HttpWebResponse
which now lets you query the returned HTTP Response, e.g:
HttpWebResponse response = await client.PostAsync(new Request());
var api = response.Headers["X-Api"];
Authentication​
New IManageRoles API​
A new IManageRoles API was added that IAuthRepository's can implement in order to provide an alternative strategy for querying and managing Users' Roles and permissions.
This new API is being used in the OrmLiteAuthRepository
to provide an alternative way to store
Roles and Permission in their own distinct table rather than being blobbed with the rest of the User Auth data.
You can enable this new behavior by specifying UseDistinctRoleTables=true
when registering the OrmLiteAuthRepository, e.g:
container.Register<IAuthRepository>(c =>
new OrmLiteAuthRepository(c.Resolve<IDbConnectionFactory>()) {
UseDistinctRoleTables = true,
});
When enabled, roles and permissions are persisted in the distinct UserAuthRole table. This behavior is integrated with the rest of ServiceStack including the Users Session, RequiredRole/RequiredPermission attributes and the AssignRoles/UnAssignRoles authentication services. Examples of this can be seen in ManageRolesTests.cs.
Messaging​
Flexible Queue Name strategies​
There are now more flexible options for specifying the Queue Names used in ServiceStack's MQ Servers. You can categorize queue names or avoid conflicts with other MQ services by specifying a global prefix to be used for all Queue Names, e.g:
QueueNames.SetQueuePrefix("site1.");
QueueNames<Hello>.In //= site1.mq:Hello.inq
Or to gain complete control of each queue name used, provide a custom QueueName strategy, e.g:
QueueNames.ResolveQueueNameFn = (typeName, suffix) =>
$"SITE.{typeName.ToLower()}{suffix.ToUpper()}";
QueueNames<Hello>.In //= SITE.hello.INQ
Note: Custom QueueNames need to be declared on both MQ Client in addition to ServiceStack Hosts.