Author Archives: trev

Looking ahead to MicroLite 7.0

You may have noticed that there hasn’t been a lot going on recently with MicroLite, we’ve had a few bug fixes since version 6.2 was released but nothing big. There are 2 main reasons behind that, the first is that I’ve been busy with some other things and the second is that I’ve been thinking about where to take MicroLite next.

There’s nothing concrete at the moment, but the main things I would like to do are:

  • Move to .NET Core
  • Support multiple mapping conventions (similar to how the attribute and convention routing works in ASP.NET Web API)
  • Support immutable and private types
  • Go async only
  • Move the other database providers into separate assemblies which can reference the 3rd party libraries (instead of everything working against DbCommand and DbParameter)
  • Allow custom instance factories
  • Support out parameters from stored procedures
  • Replace the MVC and WebApi extensions with a single AspNet extension for ASP.NET 5 (vNext)

That’s by no means a complete list, if there’s anything you would like to see added or any concerns with anything on that list, please comment here or raise an issue on the GitHub site.

Upgrading to MicroLite 6.2

MicroLite 6.2.0 has been released on NuGet.

The 2 main changes in this release are:

  • The session re-uses the same command during its lifecycle so multiple operations via a session are more efficient – especially multiple operations of the same type (e.g. multiple inserts).
  • Improved the handling of TimeSpan – beware that this is a breaking change (see below)

Prior to MicroLite 6.2, MicroLite was able to map a TimeSpan to a MS SQL Time column – however it turns out there are a number of problems with the Time type:

  1. It has a maximum value of 24:00:00 since it is actually designed to represent the time of day
  2. It cannot be used in some aggregate functions such as SUM and AVERAGE which makes summarising data diffucult

In order to mitigate these issues, MicroLite 6.2 now maps TimeSpan to a BIGINT by persisting the tick count of the TimeSpan – this has the added benefit of also enabling TimeSpan to be used by any database supported by MicroLite.

If you are updating to MicroLite 6.2 and you are currently using TimeSpan -> TIME you have 2 options:

1) – Update your database schema to use a BIGINT (add a new column, populate it by casting the time as the tick count, remove the old column).

2) – Configure MicroLite 6.2 to behave like MicroLite 6.1:

// In startup code (before calling Configure.Fluently()...):

// 1. Remove the new TimeSpanTypeConverter:
var timeSpanTypeConverter = TypeConverter.Converters.OfType<TimeSpanTypeConverter>().Single();
TypeConverter.Converters.Remove(timeSpanTypeConverter);

// 2. Reset the DbType mapping for TimeSpan:
TypeConverter.RegisterTypeMapping(typeof(TimeSpan), System.Data.DbType.Time);
TypeConverter.RegisterTypeMapping(typeof(TimeSpan?), System.Data.DbType.Time);

Understanding InvalidCastException

We can sometimes get an InvalidCastException thrown when reading a record from the database, the usual reason for this is that the database column does not match the property type on the class. For example, we have a column defined in the database as:

PhoneNumber int not null

and in the class we have

public string PhoneNumber { get; set; }

The reason this will fail is that MicroLite will read the value using the most efficient method and since the property is a string and IDataReader has a method GetString that will be called, essentially doing this:

obj.PhoneNumber = dataReader.GetString(idx);

However since the actual returned value in the IDataReader is an int which cannot be implicitly cast to a string we get an InvalidCastException. To resolve this, change the property type and database column to be compatible types (e.g. change the database column to string or change the class property to int).

MicroLite 6.0 Logging/Debugging

One of the things we focused on in MicroLite 6.0 is making the logging and debugging process even nicer for developers.

Since very early on MicroLite has had support for writing to your application log via the logging extension packages. However it has never been easy to see how a class has been mapped. To make this easier to understand, we have created an extension method for IObjectInfo which can be used to write the mapping to a TextWriter. We also write this to the log if the log is capturing Debug statements.

It’s as easy as this:

// Resolve the IObjectInfo for the mapped class.
var objectInfo = ObjectInfo.For(typeof(Customer));

// Write the mappings to the console
objectInfo.EmitMappingsToConsole();
// or write the mappings to a string
var stringWriter = new StringWriter()

// Write mappings to a TextWriter
objectInfo.EmitMappings(stringWriter);
var mappingDetails = stringWriter.ToString();

For the following class using the default conventions:

public class Customer
{
    public int Id { get; set; }
    public string Forename { get; set; }
    public string Surname { get; set; }
    public DateTime DateOfBirth { get; set; }
    public CustomerStatus Status { get; set; }
}

It will emit the following:

MicroLite Mapping:
——————
Class ‘MyApplication.Customer’ mapped to Table ‘Customers’

Property ‘DateOfBirth (System.DateTime)’ mapped to Column ‘DateOfBirth (DbType.DateTime)’
Allow Insert: True
Allow Update: True
Is Identifier: False

Property ‘Forename (System.String)’ mapped to Column ‘Forename (DbType.String)’
Allow Insert: True
Allow Update: True
Is Identifier: False

Property ‘Id (System.Int32)’ mapped to Column ‘Id (DbType.Int32)’
Allow Insert: False
Allow Update: False
Is Identifier: True
Identifier Strategy: DbGenerated

Property ‘Status (ConsoleApplication5.CustomerStatus)’ mapped to Column ‘CustomerStatusId (DbType.Int32)’
Allow Insert: True
Allow Update: True
Is Identifier: False

Property ‘Surname (System.String)’ mapped to Column ‘Surname (DbType.String)’
Allow Insert: True
Allow Update: True
Is Identifier: False

MicroLite OData update

The latest release of MicroLite.Extensions.WebApi (6.1) and Net.Http.WebApi.OData (3.1) now provide support for nearly the entire OData 3 spec that can be supported by MicroLite.

It is now possible to compare 2 properties:

$filter=FirstName eq LastName

And call nested functions:

$filter=concat(concat(City, ', '), Country) eq 'Berlin, Germany'

Decimal/Double and Single values don’t require .0 for whole values:

$filter=Price eq 5M can now be used as well as $filter=Price eq 5.0M

Method parameters no longer require a space after the comma:

$filter=endswith(CompanyName,'Futterkiste’) can now be used as well as
$filter=endswith(CompanyName, 'Futterkiste')

Literal strings (text wrapped in single quotes) can now contain a single quote by escaping it with another single quote:

$filter=LastName eq ‘O’’Brien’

Grouping is now supported in queries:

$filter=LastName eq 'Smith' and (Title eq 'Mr' or Title eq 'Mrs')

Without the grouping, the query would be translated as:

LastName = Smith and Title = Mr
Or
Title = Mrs

There is no concept of an “in list” in OData so a grouped set of “or”s is the way to achieve the same result.

endswith, startswith, and substringof now no longer require eq true at the end:

startswith(CompanyName,’Alfr’) is the same as startswith(CompanyName,’Alfr’) eq true

The not operator has also been implemented which means you can negate function calls:

not startswith(CompanyName,’Alfr’)

MicroLite.Logging.Serilog

Since very early on MicroLite has had support for writing to your application log via the MicroLite.Logging.Log4Net or MicroLite.Logging.NLog.

With the release of MicroLite 6.1 we have added support for Serilog via the MicroLite.Logging.Serilog package.

You can see the details of how to configure it on the GitHub site.

Please note that whilst this will enable MicroLite to write to your log, it doesn’t support the usual Serilog format log messages so the statements will be formatted strings.

For example, you would see

Beginning a new Transaction with the IsolationLevel 'ReadCommitted'

In the log rather than

Beginning a new Transaction with the IsolationLevel '{isolationLevel}'

This is because MicroLite does not know which if any logging library it will be writing to at runtime so it has to use standard .NET format strings for its log messages.

Upgrading to MicroLite 6.1

There are a couple of changes in MicroLite 6.1 which you need to be aware of when updating from a previous version.

Listeners

In MicroLite 6.1, we have split the IListener interface into 3:

IDeleteListener containing the following methods:

void AfterDelete(object instance, int rowsAffected);
void BeforeDelete(object instance);

IInsertListener containing the following methods:

void AfterInsert(object instance, object executeScalarResult);
void BeforeInsert(object instance);

IUpdateListener containing the following methods:

void AfterUpdate(object instance, int rowsAffected);
void BeforeUpdate(object instance);

You can create as many listeners as you like implementing all or some of the above interfaces and register them in the appropriate collections.

Example – Create a listener which only cares about insert operations:

public class InsertOnlyListener : IInsertListener
{
   public void AfterInsert(object instance, int rowsAffected) { ... }
   public void BeforeInsert(object instance) { ... }
}

Listener.InsertListeners.Add(new MyInsertListener());

Example – Create a listener which cares about insert and update operations:

public class AuditListener : IInsertListener, IUpdateListener
{
   public void AfterInsert(object instance, int rowsAffected) { ... }
   public void AfterUpdate(object instance, int rowsAffected) { ... }
   public void BeforeInsert(object instance) { ... }
   public void BeforeUpdate(object instance) { ... }
}

var auditListener = new AuditListener();
Listener.InsertListeners.Add(auditListener);
Listener.UpdateListeners.Add(auditListener);

SQL Builder

The “write” builders (SqlBuilder.Delete and SqlBuilder.Update) now offer the same functionality for single columns as the read builder (SqlBuilder.Select):

  • Between(object lower, object upper);
  • In(params object[] args);
  • In(params SqlQuery[] subQueries);
  • In(SqlQuery subQuery);
  • IsEqualTo(object comparisonValue);
  • IsGreaterThan(object comparisonValue);
  • IsGreaterThanOrEqualTo(object comparisonValue);
  • IsLessThan(object comparisonValue);
  • IsLessThanOrEqualTo(object comparisonValue);
  • IsLike(object comparisonValue);
  • IsNotEqualTo(object comparisonValue);
  • IsNotLike(object comparisonValue);
  • IsNotNull();
  • IsNull();
  • NotBetween(object lower, object upper);
  • NotIn(params object[] args);
  • NotIn(params SqlQuery[] subQueries);
  • NotIn(SqlQuery subQuery);

MicroLite 6.0 – Specifying the DbType

In MicroLite 6.0, we have exposed the ability to control the mapping between a .NET Type and the Database Type (DbType).

If not explicitly configured, the types will be mapped using the default mappings – you can see the default mappings here

If you use the Attribute based mapping and want to specify the DbType for a specific property, you can set it in the ColumnAttribute applied to that property:

[Column(&quot;DoB&quot;, DbType.DateTime2)]
public DateTime DateOfBirth { get; set; }

If you use convention based mapping and want to specify the DbType for a specific property, you can define that in your own convention:

        new ConventionMappingSettings
        {
            ResolveDbType = (PropertyInfo propertyInfo) =&gt;
            {
                if (propertyInfo.PropertyType == typeof(DateTime) &amp;&amp; propertyInfo.ReflectedType == typeof(Customer))
                {
                     return DbType.DateTime2;
                }

                // Use the default
                return TypeConverter.ResolveDbType(propertyInfo.PropertyType);
            }

If you always want to use a specific DbType for a .NET Type, you can set it via the TypeConverter class:

// Always use DbType.DateTime2 instead of DbType.DateTime for System.DateTime
TypeConverter.RegisterTypeMapping(typeof(DateTime), DbType.DateTime2);

MicroLite 6.0 – Dynamic Connection Strings

Prior to MicroLite 6.0, in order to create a session factory you had to specify the connection name as defined in the connection strings section of your application configuration. This is generally a good idea as the connection string is a configuration value which should not be baked into the code as you would need a different value if you have separate Dev/Test/Production environments.

We did have a request from a MicroLite user to be able to specify the connection string at runtime as there is a requirement for that in his application so we have added functionality for doing just that.

The ForConnection methods now have an additional overload:

ForMsSql2005Connection(string connectionName, string connectionString, string providerName)

The reason that the new overload needs a connection name is to still allow support for the MVC and WebApi extensions which are dependent on a named connection to resolve the correct session factory.

The provider name needs to be the same as you would set in the connection string in the application config, this is used by MicroLite to resolve the correct DbProviderFactory.

This gives you two ways to configure a connection in MicroLite 6.0:

Option 1 (the recommended approach for most use cases)

Define the connection string in the application configuration

<connectionStrings>
    <add name="NorthwindDB"
         connectionString="Data Source=localhost;Initial Catalog=Northwind;Persist Security Info=False;Integrated Security=SSPI;"
         providerName="System.Data.SqlClient" />
</connectionStrings>
// Create the session factory for the named connection
var sessionFactory = Configure
        .Fluently()
        .ForMsSql2005Connection("NorthwindDB")
        .CreateSessionFactory();

Option 2 (if you really really need to)

// Create the session factory with the specified name, connection string and provider name
var sessionFactory = Configure
        .Fluently()
        .ForMsSql2005Connection(
               "NorthwindDB",
               "Data Source=localhost;Initial Catalog=Northwind;Persist Security Info=False;Integrated Security=SSPI;",
               "System.Data.SqlClient")
        .CreateSessionFactory();

Upgrading to MicroLite 6.0

There are a few changes in MicroLite 6.0 and some of the extensions which you need to be aware of when updating from a previous version.

The .NET 4.5 build now contains an Async version of the session api (IAsyncSession and IAsyncReadOnlySession). If you are using a .NET 4.5 app, I suggest you update to make full use of the async version. The MVC and WebApi extensions already exclusively make use of it (see further below for more details).

Here’s the .NET 4.5 async version of each session method:

.NET 4.0 API .NET 4.5 API
Read API
Session.Fetch(SqlQuery) await Session.FetchAsync(SqlQuery)
Session.Paged(SqlQuery) await Session.PagedAsync(SqlQuery)
Session.Single(obj) await Session.SingleAsync(obj)
Session.Single(SqlQuery) await Session.SingleAsync(SqlQuery)
Write API
Session.Delete(obj) await Session.DeleteAsync(obj)
Session.Insert(obj) await Session.InsertAsync(obj)
Session.Update(obj) await Session.UpdateAsync(obj)
Session.Advanced.Delete(Type, obj) await Session.Advanced.DeleteAsync(Type, obj)
Session.Advanced.Execute(SqlQuery) await Session.Advanced.ExecuteAsync(SqlQuery)
Session.Advanced.ExecuteScalar(SqlQuery) await Session.Advanced.ExecuteScalarAsync(SqlQuery)
Session.Advanced.Update(ObjectDelta) await Session.Advanced.UpdateAsync(ObjectDelta)

Attribute Mapping

If you use the attribute based mapping, the attributes are now in the MicroLite.Mapping.Attributes namespace rather than MicroLite.Mapping.

DbEncryptedString

The DbEncryptedString class and associated classes has moved to the new MicroLite.Extensions.Cryptography project

SQL Builder

The Delete and Update Builder WhereEquals method has been replaced with Where().IsEqualTo() to align it with the Select Builder

MicroLite.Extensions.Mvc

Method signature needs to change from:

public ActionResult MethodName(args)

to

public async Task<ActionResult> MethodName(args)

MicroLite.Extensions.WebApi

Method signature needs to change from:

public HttpResponseMessage Get(int id)

to

public Task<HttpResponseMessage> Get(int id)

example:

public Task<HttpResponseMessage> Get(int id)
{
    return this.GetEntityResponseAsync(id);
}