tabs ↹ over ␣ ␣ ␣ spaces

by Jiří {x2} Činčura



FbNetExternalEngine is plugin for Firebird 3+ that allows you to write stored procedures, functions and triggers (not yet) in any .NET language (instead of PSQL). There’s no limitation on what you can or cannot do in the code. Full power of .NET and .NET eco-system is available for you.

Price and download

FbNetExternalEngine has a single price of €99, which gives you all the goodies described here and you can use it on as many servers as you have. Updates within major versions are included. There’s also a free version which is limited to only one concurrently running execution at any given time and does not support Integration interfaces and Management procedures (see below).

You can place the order here. If you’d like to support the work on FbNetExternalEngine even more – which would be greatly appreciated -, feel free to put your preferred amount into the note.


  1. Add these lines into plugins.conf.
	Module = $(dir_plugins)/FbNetExternalEnginePlugin
  1. Copy these files into plugins directory.
FbNetExternalEnginePlugin.pdb (optional)
FbNetExternalEngineManaged.pdb (optional)
FbNetExternalEngineIntegration.pdb (optional)
FbNetExternalEngineManagement.dll (optional)
FbNetExternalEngineManagement.pdb (optional)
Supported types (C# terminology)

int?, string, short?, long?, DateTime?, TimeSpan?, bool?, float?, double?, decimal?, byte[]

The mapping from/to database types should be self explanatory.

Database NULL maps to C# null.

Common requirements (C# terminology)

  • Method has to be static.
  • Input arguments have to be from set of supported types (see below).
  • No overload resolution (method names have to be unique).
SQL definition

The external name is in a form <assembly>!<namespace>...<class>.<method>, where the assembly can be absolute or relative path without extension (.dll). Relative path is resolved from the plugins directory.

Limitations on types (C# terminology)

Any exception thrown from the code is converted to Firebird’s FbException with status vector isc_arg_gds being isc_random and isc_arg_string being Exception.ToString() from .NET.

Other exceptions in managed code are or derive from ArgumentException.

Stored procedures

Requirements (C# terminology)
  • Return type has to be IEnumerator<(T1, T2, ..., Tn)> (or IEnumerator<ValueTuple<T1, T2, ..., Tn>>), where Tx is from set of supported types (see above). Or void.

C# code is compiled into Example.dll.

namespace Example
	public static class Procedures
		public static IEnumerator<(int?, int?)> IncrementInteger(int? i)
			yield return (i, i + 1);
create procedure increment_integer(input int)
returns (original int, new int)
external name 'Example!Example.Procedures.IncrementInteger'
engine FbNetExternalEngine;

Then you can call this procedure.

SQL> select * from increment_integer(-20);

    ORIGINAL          NEW
============ ============
         -20          -19

SQL> execute procedure increment_integer(6);

    ORIGINAL          NEW
============ ============
           6            7

More examples in Example.dll and Procedures.cs/Procedures.sql.


Requirements (C# terminology)
  • Return type has to be T, where T is from set of supported types (see above).

C# code is compiled into Example.dll.

namespace Example
	public static class Functions
		public static int? IncrementInteger(int? i)
			return i + 1;
create function increment_integer(input int)
returns int
external name 'Example!Example.Functions.IncrementInteger'
engine FbNetExternalEngine;

Then you can call this function.

SQL> select increment_integer(-20) from rdb$database;


SQL> select increment_integer(6) from rdb$database;


More examples in Example.dll and Functions.cs/Functions.sql.


Not yet supported.

Integration interfaces

The extra FbNetExternalEngineIntegration.dll contains interfaces to integrate with the FbNetExternalEngine.


The last parameter of the procedure or function can be of type IExecutionContext. If so, such instance is provided. The interface contains multiple overloads of Execute method that allows executing SQL commands within the context (transaction) of currently running procedure or function.

public static long? FullSelectFunction(IExecutionContext context)
	var data = context.Execute<int?, string>("select mon$attachment_id, mon$remote_process from mon$attachments").ToList();
	return data.LongCount();
recreate function full_select_function
returns bigint
external name 'Example!Example.ExecutionContext.FullSelectFunction'
engine FbNetExternalEngine;

At the moment input parameters are not supported (values have to be hardcoded) and at most 32 columns can be selected with Execute method.

More examples in Example.dll and ExecutionContext.cs/ExecutionContext.sql.

Management procedures

The extra FbNetExternalEngineManagement.dll (and ManagementProcedures.sql companion) assembly contains useful helpers for managing the plugin.


Allows hot swapping of assemblies from SQL without restarting the server. Calling this procedure with new assembly data in data parameter will replace it on the disk and invalidate internal caches. It can be safely called while other FbNetExternalEngine pieces are executing code.

The assembly is not locked on disk, thus you can replace it directly manually as well. Then call the procedure with data set to null.


Dummy procedure call is about 2,55× slower compared to PSQL (the plugin infrastructure in Firebird adds about 1,4× slowdown). That’s about 6,0 μs per call on my machine. The fetch from stored procedure’s result set is about 1,18× slower compared to PSQL.

Dummy function call is about 2,19× slower compared to PSQL (the plugin infrastructure in Firebird adds about 1,2× slowdown). That’s about 2,5 μs per call on my machine.

As the procedure or function in .NET becomes more complex the perfomance goes in favor of FbNetExternalEngine.

Next steps

These ideas, in no particular order, is what I (or people/companies supporting the plugin) have in mind for the future.

  • Explore posibilities of using .NET Core.
    • Why: Because that would allow using FbNetExternalEngine on Linux servers as well.
  • Add support for CHARACTER SET OCTETS.
    • Why: Because it might be useful for certain scenarios.
  • Add support for async methods.
    • Why: Because it will be convenient.
  • Automatic registration based on metadata using i.e. net$register.
    • Why: Because the metadata in .NET is quite rich and it will be convenient.

Notable sponsors