MicroLite 5.0 – New Features

The last few posts have covered some of the changes coming in MicroLite 5.0, now that the major ones are covered let’s take a look at at some of the new features:

ObjectDelta

The ObjectDelta class and the ISession.Advanced.Update(ObjectDelta) add a neat way to perform partial updates against a database row.

// Firstly we need to instantiate a new ObjectDelta instance and 
// provide it with the class the update is targeted against and 
// the identifier value of the instance to update.
var objectDelta = new ObjectDelta(
    type: typeof(Customer), identifier: 12823);

// Then we can add a number of updates.
objectDelta.AddChange(propertyName: "Locked", newValue: 0);
...

using (var session = sessionFactory.OpenSession())
{
    using (var transaction = session.BeginTransaction())
    {
        // Apply the update and confirm if a record was updated.
        bool wasUpdated = session.Advanced.Update(objectDelta);

        transaction.Commit();
    }
}

UriTypeConverter

The type converter support has been increased by adding support for the System.Uri class. As before with the XDocument support in MicroLite 3.0.3, it’s as simple as setting the property type:

public class Thing
{
    public Uri Website { get; set; }
}

The conversion is managed by the MicroLite.TypeConverters.UriTypeConverter and is registered in the TypeConverter.Converters collection by default.

ConnectionScope

We have added finer control over when the connection to the database is opened and closed. The default behaviour matches MicroLite 4.0 and earlier which is to open a connection when a transaction is started and closed it when the transaction is completed.

However there may be times where you want to open the connection and close it when a session is opened and disposed (e.g. performing multiple transactions in a single session where it is more performant to hold a connection open or where you use SQLite in memory databases where the database is deleted when the connection is closed).

In order to use it, call the overload on ISessionFactory when opening a session:

var session = sessionFactory.OpenSession(ConnectionScope.PerSession);

MicroLite 5.0 – Changes to Convention Mapping

In MicroLite 5.0, we are making further enhancements to the convention based mapping which provide even greater flexibility.

Convention Mapping History:

The changes in MicroLite 5.0 are as follows:

ResolveIdentifierStrategy

We have replaced the IdentifierStrategy property with the ResolveIdentifierStrategy method. This means that you no longer have to use the same IdentifierStrategy for every class (or that you can migrate from attribute mapping to convention mapping if this was preventing you from doing so).

By default it will return IdentifierStrategy.DbGenerated but you can override it if you want to specify a different strategy or calculate it based upon the type:

Configure.Extensions().WithConventionBasedMapping(
    new ConventionMappingSettings
    {
        ResolveIdentifierStrategy = (Type type) => 
        {
            if (type == typeof(MySpecialType))
            {
                return IdentifierStrategy.Assigned;
            }

            return IdentifierStrategy.DbGenerated; 
        }
    });

ResolveTableSchema

We have replaced the TableSchema property with the ResolveTableSchema method. This means that you no longer have to use the same schema for every class (or that you can migrate from attribute mapping to convention mapping if this was preventing you from doing so).

By default it will return null but you can override it if you want to specify a specific schema or calculate it based upon the type:

Configure.Extensions().WithConventionBasedMapping(
    new ConventionMappingSettings
    {
        ResolveTableSchema = (Type type) => 
        {
            if (type == typeof(MySpecialType))
            {
                return "MySpecialSchema";
            }

            return "dbo"; 
        }
    });

MicroLite 5.0 – Changes to Type Converters

In MicroLite 5.0, we have enhanced the Type Converters which we introduced in MicroLite 3.1.

The following method has been added to the interface:

object ConvertFromDbValue(IDataReader reader, int index, Type type);

This allows you to read the value from the IDataReader at the given index in the most efficient way (e.g. as an Int32 without the value being boxed).

From MicroLite 5.0, the full interface now looks like this:

ITypeConverter MicroLite 5.0

The ConvertFromDbValue(IDataReader, int, Type) is called when building an object from the results of a query, the ConvertFromDbValue(object, Type) method still exists but is only used when using ISession.Advanced.ExecuteScalar as there is no data reader used for that operation.

MicroLite 5.0 – Changes to Logging

In MicroLite 5.0, we have updated the logging API. The information in this post supersedes the information in the Using Logging in Custom Listeners post.

The extension methods which have been removed and the LogManager has been updated so that it will return an empty logger if none is configured. This means that as long as you call MicroLite.Logging.LogManager.GetCurrentClassLog(); you will be returned with an ILog instance.

The ILog interface has been updated with the following properties which allow you to check whether the log is configured at a given level before calling the corresponding log method. This can save on unnecessary calls to resource files and string formatting etc if the log isn’t capturing certain levels of data.

bool IsDebug { get; }
bool IsInfo { get; }
bool IsWarn { get; }
bool IsError { get; }
bool IsFatal { get; }

The ValidationListener in the Using Logging in Custom Listeners post would be updated as follows:

using MicroLite.Logging;

public class ValidationListener : MicroLite.Listeners.Listener
{
    private static ILog log = LogManager.GetCurrentClassLog();

    protected override BeforeInsert(object instance)
    {
        // This ensures that we only pay the cost of calling
        // .GetType() if we want to log the value anyway.
        if (log.IsDebug)
        {
            log.Debug(
                "Validating an {0} before insert",
                instance.GetType().Name);
        }

        ValidateObject(instance);
    }

    protected override BeforeUpdate(object instance)
    {
        // This ensures that we only pay the cost of calling
        // .GetType() if we want to log the value anyway.
        if (log.IsDebug)
        {
            log.Debug(
                "Validating an {0} before update",
                instance.GetType().Name);
        }

        ValidateObject(instance);
    }

    private void ValidateObject(object instance)
    {
        var warnings = Validator.GetWarnings(instance);
        var errors = Validator.GetErrors(instance);

        foreach (var warning in warnings)
        {
            if (log.IsWarn) { log.Warn(warning); }
        }

        foreach (var error in errors)
        {
            if (log.IsError) { log.Error(error); }
        }

        if (warnings.Count > 0 || errors.Count > 0)
        {
            throw new ValidationException(warnings, errors);
        }
    }
}

MicroLite 5.0 – Removal of methods from IListener

In MicroLite 5.0, the mutability of the SqlQuery object has been reduced. This change whilst helping improve the performance of MicroLite and reduce the memory footprint of the framework, does mean that the following methods on IListener can no longer be supported:

void BeforeDelete(object instance, SqlQuery sqlQuery);
void BeforeInsert(object instance, SqlQuery sqlQuery);
void BeforeUpdate(object instance, SqlQuery sqlQuery);

They were originally added in MicroLite 1.2.0 as an extensible point in the pipeline but the purpose of them was never that clear and they were never actually used within the framework itself.

Will this affect you at all? probably not.

  • If you have created a custom listener by inheriting from MicroLite.Listeners.Listener and have not overridden any of the methods mentioned then you won’t have a problem.
  • If you instead implemented MicroLite.Listeners.IListener yourself then if you didn’t actually perform any work in the methods mentioned then you can just delete them and you won’t have a problem.

If you think this change might affect you, move your code into the Before(object instance) method. If you were mutating the SqlQuery, let us know what you were doing and we will let you know the best way to handle it in 5.0.

MicroLite 5.0 – Reduced Mutability of SqlQuery

In MicroLite 5.0, the mutability of the SqlQuery object has been reduced. Once set via the constructor, the command text can now no longer be changed and the arguments cannot be added to or removed from. This allows us to make some performance improvements within the framework since we know the object cannot change.

The class now looks like this:

public sealed class SqlQuery : IEquatable<SqlQuery>
{
    private readonly object[] arguments;
    private readonly string commandText;

    public SqlQuery(string commandText, params object[] arguments)
    {
        this.commandText = commandText;
        this.arguments = arguments ?? new object[0];
    }

    public string CommandText { get { return this.commandText; } }
}

MicroLite 5.0 – Removal of InsertOrUpdate

Another of the changes to the API in MicroLite 5.0 is the removal of the InsertOrUpdate(object) method on ISession.

The reason for the removal is that it only worked correctly if you used the IdentifierStrategy.DbGenerated. In the interest of keeping the API small and not having methods which only work in specific scenarios it has been removed.

If you use the IdentifierStrategy.DbGenerated and wish to keep using this method, you can use the following extension method:

public static void InsertOrUpdate(this ISession session, object instance)
{
    var objectInfo = ObjectInfo.For(instance.GetType());

    if (objectInfo.HasDefaultIdentifierValue(instance))
    {
        session.Insert(instance);
    }
    else
    {
        session.Update(instance);
    }
}