Version 4 Migration - linq2db/linq2db Wiki

If you cannot find answer to your migration-related question here, ask it here and we will update document with missing details.

T4 Changes

Due to changes to DataConnection API existing T4 models could produce compilation errors if stored procedures were generated for model. To resolve it you need to re-generate model using T4 templates.

Migration to Interceptors

In v4 release we replace multiple existing events, delegates, properties and static settings that piled up along time with iterceptors infrastructure. Interceptor is an instance of class, that implements one or multiple interceptor interfaces with events. It is registered in Linq To DB (usually in DataConnection or DataContext instance) and called when one of events, defined on interceptor is triggered.

Supporting interceptors on DataContext level also allows user to use events, previously available only on DataConnection level.

For basic information and list of implemented interceptors check release notes frist.

Functionality, replaced by interceptors

Note that some functionality, provided by interceptors previously was available only on specific implementation (usually DataConnection). With interceptors applied to all types of contexts now you can use it also with other contexts like DataConext.

Removed Replacement Notes
DataConnection.OnBeforeConnectionOpen event IConnectionInterceptor.ConnectionOpening interceptor
DataConnection.OnBeforeConnectionOpenAsync event IConnectionInterceptor.ConnectionOpeningAsync interceptor
DataConnection.OnConnectionOpened event IConnectionInterceptor.ConnectionOpened interceptor
DataConnection.OnConnectionOpenedAsync event IConnectionInterceptor.ConnectionOpenedAsync interceptor
DataConnection.OnClosed event IDataContextInterceptor.OnClosed interceptorIDataContextInterceptor.OnClosedAsync interceptor
IDataContext.OnClosing event IDataContextInterceptor.OnClosing interceptorIDataContextInterceptor.OnClosingAsync interceptor
DataConnection.LastParameters field ICommandInterceptor.CommandInitialized interceptoror for one-time useOnNextCommandInitialized method As this field used excessively by T4 for stored procedure wrappers, T4 model requires regeneration
DataConnection.Command field ICommandInterceptor.CommandInitialized interceptoror for one-time useOnNextCommandInitialized method
IDbCommandProcessor interface+DbCommandProcessorExtensions class ICommandInterceptor interceptor
IEntityServices interface +EntityCreatedEventArgs class IDataContextInterceptor.EntityCreated interceptor

Migration from ADO.NET interfaces

Linq To DB replaced support of ADO.NET interfaces (listed below) with corresponding asbstract classes. Technically, even in v3 you can work with those interfaces only if they belong to class, inherited from corresponding abstract class.

In most of cases compiler will give you error if you try to pass interface instead of class, but there are several cases where you need to change your code.

MiniProfiler configuration

If you used MiniProfiler and confired unwrap expressions accoording to these notes you need to replace interfaces to classes in SetConvertExpression calls:

// V3
MappingSchema.SetConvertExpression<ProfiledDbConnection,  IDbConnection> (db => db.WrappedConnection);
MappingSchema.SetConvertExpression<ProfiledDbDataReader,  IDataReader>   (db => db.WrappedReader);
MappingSchema.SetConvertExpression<ProfiledDbTransaction, IDbTransaction>(db => db.WrappedTransaction);
MappingSchema.SetConvertExpression<ProfiledDbCommand,     IDbCommand>    (db => db.InternalCommand);

// V4
MappingSchema.SetConvertExpression<ProfiledDbConnection,  DbConnection> (db => db.WrappedConnection);
MappingSchema.SetConvertExpression<ProfiledDbDataReader,  DataReader>   (db => db.WrappedReader);
MappingSchema.SetConvertExpression<ProfiledDbTransaction, DbTransaction>(db => db.WrappedTransaction);
MappingSchema.SetConvertExpression<ProfiledDbCommand,     DbCommand>    (db => db.InternalCommand);
Custom data reader expressions

If you had custom data reader expressions configured on data provider, you need to replace uses of IDataReader with DbDataReader

// V3
provider.SetProviderField<IDataReader, TimeSpan,DateTime>(
    (r,i) => r.GetDateTime(i) - new DateTime(1970, 1, 1));
// V4
provider.SetProviderField<DbDataReader, TimeSpan,DateTime>(
    (r,i) => r.GetDateTime(i) - new DateTime(1970, 1, 1));

// V3
provider.SetToType<IDataReader,sbyte,int>("INTEGER", (r, i) => unchecked((sbyte)r.GetInt32(i)));
// V4
provider.SetToType<DbDataReader,sbyte,int>("INTEGER", (r, i) => unchecked((sbyte)r.GetInt32(i)));

// V3
provider.SetField<IDataReader,long>((r,i) => r.GetInt64(i));
// V4
provider.SetField<DbDataReader,long>((r,i) => r.GetInt64(i));

Code removals

Removal of BulkCopy helpers on <DB>Tools classes

Those methods were just opionated shortcuts to DataConnection.BulkCopy method and to fix your code you just need to call it instead.

// AccessTools.MultipleRowsCopy =>
// FirebirdTools.MultipleRowsCopy =>
// MySqlTools.MultipleRowsCopy =>
// OracleTools.MultipleRowsCopy =>
// PostgreSQLTools.MultipleRowsCopy =>
// SQLiteTools.MultipleRowsCopy =>
// SqlCeTools.MultipleRowsCopy =>
// SybaseTools.MultipleRowsCopy =>
dataConnection.BulkCopy(
  new BulkCopyOptions
  {
    BulkCopyType       = BulkCopyType.MultipleRows,
    MaxBatchSize       = maxBatchSize, // default was 1000
    RowsCopiedCallback = rowsCopiedCallback, // default was null
  }, source);

// DB2Tools.MultipleRowsCopy =>
// InformixTools.MultipleRowsCopy =>
dataConnection.BulkCopy(
  new BulkCopyOptions
  {
    BulkCopyType       = BulkCopyType.ProviderSpecific, // right, ProviderSpecific used
    MaxBatchSize       = maxBatchSize, // default was 1000
    RowsCopiedCallback = rowsCopiedCallback, // default was null
  }, source);

// DB2Tools.ProviderSpecificBulkCopy =>
dataConnection.BulkCopy(
  new BulkCopyOptions
  {
    BulkCopyType       = BulkCopyType.ProviderSpecific,
    BulkCopyTimeout    = bulkCopyTimeout, // default was null
    KeepIdentity       = keepIdentity, // default was false
    NotifyAfter        = notifyAfter, // default was 0
    RowsCopiedCallback = rowsCopiedCallback, // default was null
  }, source);

// OracleTools.ProviderSpecificBulkCopy =>
dataConnection.BulkCopy(
  new BulkCopyOptions
  {
    BulkCopyType       = BulkCopyType.ProviderSpecific,
    MaxBatchSize       = maxBatchSize, // default was null
    BulkCopyTimeout    = bulkCopyTimeout, // default was null
    NotifyAfter        = notifyAfter, // default was 0
    RowsCopiedCallback = rowsCopiedCallback, // default was null
  }, source);

// SqlServerTools.ProviderSpecificBulkCopy =>
dataConnection.BulkCopy(
  new BulkCopyOptions
  {
    BulkCopyType       = BulkCopyType.ProviderSpecific,
    MaxBatchSize       = maxBatchSize, // default was null
    BulkCopyTimeout    = bulkCopyTimeout, // default was null
    KeepIdentity       = keepIdentity, // default was false
    CheckConstraints   = checkConstraints, // default was false
    NotifyAfter        = notifyAfter, // default was 0
    RowsCopiedCallback = rowsCopiedCallback, // default was null
  }, source);

Provider-Specific Changes

Firebird

Guid to UUID default mapping

V4 introduces default mapping of Guid type to UUID type (represented by CHAR(16) CHARACTER SET OCTETS type). It will affect existing applications that used Guid-typed columns, mapped to CHAR(38), especially in queries with inlined parameters (as SQL literals), as it will start to generate binary literal instead.

To update your application you have several options

Specify DataType for string-types Guid columns

You need to specify DataType = DataType.Char or DataType = DataType.NChar in column mapping:

// specify explicit DataType using attributes
[Table]
public class MyTable
{
    // map to fixed-length text database type
    [Column(DataType = DataType.Char)]
    public Guid GuidAsStringColumn1;

    // map to fixed-length text database type
    [Column(DataType = DataType.NChar)]
    public Guid GuidAsStringColumn2;

    // explicit mapping to UUID
    [Column(DataType = DataType.Guid)]
    public Guid GuidAsUUID1;

    // default mapping to UUID
    [Column]
    public Guid GuidAsUUID2;
}

// or using fluent mapping
ms.GetFluentMappingBuilder()
.Entity<MyTable>()
    .Property(e => e.GuidAsStringColumn1).HasDataType(DataType.Char)
    .Property(e => e.GuidAsStringColumn2).HasDataType(DataType.NChar)
    .Property(e => e.GuidAsUUID1).HasDataType(DataType.Guid)
    .Property(e => e.GuidAsUUID2)

If your model generated using T4 templates and doesn't have DataType generated, you need to enable following T4 option and re-generate your model:

GenerateDataTypes = true;
Restore old behavior for all Guid columns by default

Another option is to change default Guid DataType using following code:

// remap Guid to Char (or NChar)
ms.SetDataType(typeof(Guid), DataType.Char);
ms.SetDataType(typeof(Guid?), DataType.Char);

With this change Linq To DB will not use UUID type for Guid if you not specify DataType.Guid for column explicitly.