Last update: 2003-05-31 12:40 by fxjr
Category: External documentation
Intended Audience: Npgsql Users
Npgsql is a .Net Data Provider for Postgresql Database Server.
It allows a .Net client application (Console, WinForms, ASP.NET, Web Services...) to use a PostgreSQL server to send and receive data. It is developed using the guidelines specified in the .Net docs.
This section explains Npgsql usage in a .Net Application. If you are used to developing Data Access applications using the Sql Server, OleDB or ODBC.NET providers, using Npgsql is very similar.
Note: Npgsql is still under development. Only features currently supported will be demonstrated. As Npgsql matures, more functionality will be available to be used.
First, to use Npgsql objects more easily, you have to tell the compiler to use the Npgsql namespace. It will also be needed to use the System.Data namespace as a lot of classes used for data manipulation reside in it. To do that, in C#, you use the using directive:
using System.Data; using Npgsql;
To establish a connection to a server located at ip 127.0.0.1, port 5432, as the user joe, with the password secret, using the database joedata, you use the NpgsqlConnection as:
using System.Data; using Npgsql; public static class NpgsqlUserManual { public static void Main(String[] args) { NpgsqlConnection conn = new NpgsqlConnection("Server=127.0.0.1;Port=5432;User Id=joe;Password=secret;Database=joedata;"); conn.Open(); conn.Close(); } }
The previous example doesn't do anything useful. It only connects to the database and disconnects. If there is an error, a NpgsqlException is thrown. Now, suppose there is a table called table1 with two fields a and b both of type int. If you want to add the tuple (1, 1) in this table you could send the insert statement as follows:
using System; using System.Data; using Npgsql; public static class NpgsqlUserManual { public static void Main(String[] args) { NpgsqlConnection conn = new NpgsqlConnection("Server=127.0.0.1;Port=5432;User Id=joe;Password=secret;Database=joedata;"); conn.Open(); NpgsqlCommand command = new NpgsqlCommand("insert into table1 values(1, 1)", conn); Int32 rowsaffected; try { rowsaffected = command.ExecuteNonQuery(); } Console.WriteLine("It was added {0} lines in table table1", rowsaffected); finally { conn.Close(); } } }
To execute statements which return just one value you use the ExecuteScalar() method of the Command object:
using System; using System.Data; using Npgsql; public static class NpgsqlUserManual { public static void Main(String[] args) { NpgsqlConnection conn = new NpgsqlConnection("Server=127.0.0.1;Port=5432;User Id=joe;Password=secret;Database=joedata;"); conn.Open(); NpgsqlCommand command = new NpgsqlCommand("select version()", conn); String serverversion; try { serverversion = (String)command.ExecuteScalar(); } Console.WriteLine("PostgreSQL server version: {0}", serverversion); finally { conn.Close(); } } }Other queries could also be used, for example "select count(*) from table1". Also, queries that don't return single values could be used, but only the first column of the first row would be returned.
To execute statements which return full results from a query you use the ExecuteReader() method of the NpgsqlCommand object:
using System; using System.Data; using Npgsql; public static class NpgsqlUserManual { public static void Main(String[] args) { NpgsqlConnection conn = new NpgsqlConnection("Server=127.0.0.1;Port=5432;User Id=joe;Password=secret;Database=joedata;"); conn.Open(); NpgsqlCommand command = new NpgsqlCommand("select * from tablea", conn); try { NpgsqlDataReader dr = command.ExecuteReader(); while(dr.Read()) { for (i = 0; i < dr.FieldCount; i++) { Console.Write("{0} \t", dr[i]); } Console.WriteLine(); } } finally { conn.Close(); } } }This code assumes there is a table called tablea in the database.
You can also send a parameterized query to the server using NpgsqlParamenter and NpgsqlParamenterCollection objects. A parameter is a string prefixed by : in the query string. The example below uses a parameter called column1.
using System; using System.Data; using Npgsql; public static class NpgsqlUserManual { public static void Main(String[] args) { NpgsqlConnection conn = new NpgsqlConnection("Server=127.0.0.1;Port=5432;User Id=joe;Password=secret;Database=joedata;"); conn.Open(); // Declare the parameter in the query string NpgsqlCommand command = new NpgsqlCommand("select * from tablea where column1 = :column1", conn); // Now add the parameter to the parameter collection of the command specifying its type. command.Parameters.Add(new NpgsqlParameter("column1", DbType.Int32); // Now, add a value to it and later execute the command as usual. command.Parameters[0].Value = 4; try { NpgsqlDataReader dr = command.ExecuteReader(); while(dr.Read()) { for (i = 0; i < dr.FieldCount; i++) { Console.Write("{0} \t", dr[i]); } Console.WriteLine(); } } finally { conn.Close(); } } }This code assumes a table called tablea with at least a column named column1 of type int4.
The previous example can also be used to send prepared statements request to server. This way, the query plan is create just once and reused by subsequent calls. Note that this feature is only available in server 7.3+ versions. If you call it in a server which doesn't support it, Npgsql will silently ignore it. This is good as you can create code which calls it but it will only have effect when connected to the supported servers. To do it, you just call the Prepare() method of the command.
using System; using System.Data; using Npgsql; public static class NpgsqlUserManual { public static void Main(String[] args) { NpgsqlConnection conn = new NpgsqlConnection("Server=127.0.0.1;Port=5432;User Id=joe;Password=secret;Database=joedata;"); conn.Open(); // Declare the parameter in the query string NpgsqlCommand command = new NpgsqlCommand("select * from tablea where column1 = :column1", conn); // Now add the parameter to the parameter collection of the command specifying its type. command.Parameters.Add(new NpgsqlParameter("column1", DbType.Int32); // Now, prepare the statement. command.Prepare(); // Now, add a value to it and later execute the command as usual. command.Parameters[0].Value = 4; try { NpgsqlDataReader dr = command.ExecuteReader(); while(dr.Read()) { for (i = 0; i < dr.FieldCount; i++) { Console.Write("{0} \t", dr[i]); } Console.WriteLine(); } } finally { conn.Close(); } } }This code assumes a table called tablea with at least a column named column1 of type int4.
To call a function, you just set the CommandType property of the NpgsqlCommand object to CommandType.StoredProcedure and pass the name of the function you want to call as the query string.
using System; using System.Data; using Npgsql; // This example uses a function called funcC() with the following definition: // create function funcC() returns int8 as ' // select count(*) from tablea; // ' language 'sql'; // Note that the return type of select count(*) changed from int4 to int8 in 7.3+ versions. To use this function // in a 7.2 server, change the return type from int8 to int4. public static class NpgsqlUserManual { public static void Main(String[] args) { NpgsqlConnection conn = new NpgsqlConnection("Server=127.0.0.1;Port=5432;User Id=joe;Password=secret;Database=joedata;"); conn.Open(); try { NpgsqlCommand command = new NpgsqlCommand("funcC()", _conn); command.CommandType = CommandType.StoredProcedure; Object result = command.ExecuteScalar(); Console.WriteLine(result); } finally { conn.Close(); } } }
In the same way as clear queries, you can specify parameters to function calls.
using System; using System.Data; using Npgsql; // This example uses a function called funcC() with the following definition: // create function funcC(int4) returns int8 as ' // select count(*) from tablea where field_int4 = $1; // ' language 'sql'; public static class NpgsqlUserManual { public static void Main(String[] args) { NpgsqlConnection conn = new NpgsqlConnection("Server=127.0.0.1;Port=5432;User Id=joe;Password=secret;Database=joedata;"); conn.Open(); try { NpgsqlCommand command = new NpgsqlCommand("funcC(:a)", _conn); command.CommandType = CommandType.StoredProcedure; command.Parameters.Add(new NpgsqlParameter("a", DbType.Int32)); command.Parameters[0].Value = 4; Object result = command.ExecuteScalar(); Console.WriteLine(result); } finally { conn.Close(); } } }
This code assumes a table called tablea with at least a field called field_int4 of type int4.
You can use Npgsql when working with Datasets. This allows you to do changes in the dataset and have them propagated back to backend. The below example shows how to add a row using the Dataset:
// This method expects the following table in the backend: // // create table tableb(field_int2 int2, field_timestamp timestamp, field_numeric numeric); // // void AddWithDataSet(NpgsqlConnection conn) { conn.Open(); DataSet ds = new DataSet(); NpgsqlDataAdapter da = new NpgsqlDataAdapter("select * from tableb", conn); da.InsertCommand = new NpgsqlCommand("insert into tableb(field_int2, field_timestamp, field_numeric) " + " values (:a, :b, :c)", conn); da.InsertCommand.Parameters.Add(new NpgsqlParameter("a", DbType.Int16)); da.InsertCommand.Parameters.Add(new NpgsqlParameter("b", DbType.DateTime)); da.InsertCommand.Parameters.Add(new NpgsqlParameter("c", DbType.Decimal)); da.InsertCommand.Parameters[0].Direction = ParameterDirection.Input; da.InsertCommand.Parameters[1].Direction = ParameterDirection.Input; da.InsertCommand.Parameters[2].Direction = ParameterDirection.Input; da.InsertCommand.Parameters[0].SourceColumn = "field_int2"; da.InsertCommand.Parameters[1].SourceColumn = "field_timestamp"; da.InsertCommand.Parameters[2].SourceColumn = "field_numeric"; da.Fill(ds); DataTable dt = ds.Tables[0]; DataRow dr = dt.NewRow(); dr["field_int2"] = 4; dr["field_timestamp"] = new DateTime(2003, 03, 03, 14, 0, 0); dr["field_numeric"] = 7.3M; dt.Rows.Add(dr); DataSet ds2 = ds.GetChanges(); da.Update(ds2); ds.Merge(ds2); ds.AcceptChanges(); }
This example will show how to use a strong typed dataset generated with xsd. To do so, we need a .xsd file specifing the schema to the strong dataset. You can generate this file by hand, or you can use xsd to generate it for you. To have xsd generate the .xsd for you, you have to suppy it with a .xml file which it will infer the .xsd from. We can use a .xml file generated with a DataAdapter.WriteXml() method:
public void GenerateXmlFromDataSet(NpgsqlConnection conn) { conn.Open(); NpgsqlDataAdapter da = new NpgsqlDataAdapter("select * from tablea", conn); DataSet ds = new DataSet(); da.Fill(ds); ds.WriteXml("StrongDataSetFeed.xml"); }
This will give us a file which looks similar to this:
<?xml version="1.0" standalone="yes"?> <NewDataSet> <Table> <field_serial>1</field_serial> <field_text>Random text</field_text> </Table> <Table> <field_serial>2</field_serial> <field_int4>4</field_int4> </Table> <Table> <field_serial>3</field_serial> <field_int8>8</field_int8> </Table> <Table> <field_serial>4</field_serial> <field_bool>true</field_bool> </Table> <Table> <field_serial>5</field_serial> <field_text>Text with ' single quote</field_text> </Table> </NewDataSet>
This file will be used with xsd to generate the .xsd file with the following command:
xsd StrongDataSetFeed.xml
xsd will produce an xml schema which will have all types specified as string, we just change the xsd to use the correct types and have a .xsd file similar to this:
<?xml version="1.0" encoding="utf-8"?> <xs:schema id="NewDataSet" xmlns="" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> <xs:element name="NewDataSet" msdata:IsDataSet="true" msdata:Locale="pt-BR"> <xs:complexType> <xs:choice maxOccurs="unbounded"> <xs:element name="Table"> <xs:complexType> <xs:sequence> <xs:element name="field_serial" type="xs:int" minOccurs="0" /> <xs:element name="field_text" type="xs:string" minOccurs="0" /> <xs:element name="field_int4" type="xs:int" minOccurs="0" /> <xs:element name="field_int8" type="xs:long" minOccurs="0" /> <xs:element name="field_bool" type="xs:boolean" minOccurs="0" /> </xs:sequence> </xs:complexType> </xs:element> </xs:choice> </xs:complexType> </xs:element> </xs:schema>
With this file we issue the following command to have xsd generate the strong dataset:
xsd StrongDataSetFeed.xsd /dataset
Which will generate a file we can compile to get an assembly with the strong dataset. To use it, we do the following:
using System; using Npgsql; public class t { public static void Main(String[] args) { NpgsqlConnection conn = new NpgsqlConnection("Server=127.0.0.1;Port=5432;User Id=joe;Password=secret;Database=joedata;"); conn.Open(); NpgsqlDataAdapter da = new NpgsqlDataAdapter("Select * from tablea", conn); NewDataSet n = new NewDataSet(); da.Fill(n); foreach (NewDataSet._TableRow tr in n._Table) { Console.WriteLine(tr.field_serial); } } }
In order for the .Net Runtime to locate the Npgsql.dll library, this file must be put in the same directory the application is, unless you specify another directory as a path to private components through a configuration file (using the probing element). Please, see the .Net docs for information on how the runtime probes (locates) assemblies to be loaded. There is also a section called "Path to Private Components"
In ASP.NET and Web Services .Net Applications, there must be a directory called "bin" below the ASP.NET root application directory. So, for example the application directory is called "ASPNETApplication", Npgsql.dll must be placed in the "ASPNETApplication\bin" directory. If it's not, you will get a lot of compiling errors when trying to use Npgsql.
You can also put the Npgsql assembly in the Global Cache Assembly. Since 0.4 version, Npgsql is strongly signed which means you can use gacutil to install it. Issue the following command to do it:
gacutil -i Npgsql.dll
Please, refer to "Installing an Assembly in the Global Cache Assembly" section of MSDN docs for more help.
Sometimes it is necessary to trace Npgsql's behaviour to track errors. Npgsql can log messages to a specified file or to the console or both.
There are three levels of logging:
The following NpgsqlEventLog static properties may also be used:
The example below shows how to log to the console and a file using the level Debug:
using System.Data; using Npgsql; public static class NpgsqlUserManual { public static void Main(String[] args) { // Enable logging. NpgsqlEventLog.Level = LogLevel.Debug; NpgsqlEventLog.LogName = "NpgsqlTests.LogFile"; NpgsqlEventLog.EchoMessages = true; NpgsqlConnection conn = new NpgsqlConnection("Server=127.0.0.1;Port=5432;User Id=joe;Password=secret;Database=joedata;"); conn.Open(); conn.Close(); } }
Running this code gives the following output:
Set NpgsqlEventLog.EchoMessages = True Entering NpgsqlConnection.NpgsqlConnection() Entering NpgsqlConnection.ParseConnectionString() Connection string option: DATABASE = joedata Connection string option: SERVER = 127.0.0.1 Connection string option: USER ID = joe Connection string option: PASSWORD = secret Entering NpgsqlConnection.Open() Connected to: 127.0.0.1:5432 Entering NpgsqlConnection.WritestartupPacket() Entering NpgsqlStartupPacket.NpgsqlStartupPacket() Entering NpgsqlStartupPacket.WriteToStream() Entering PGUtil.WriteLimString() Entering PGUtil.WriteLimString() Entering PGUtil.WriteLimString() Entering PGUtil.WriteLimString() Entering PGUtil.WriteLimString() Entering NpgsqlConnection.HandleStartupPacketResponse() AuthenticationRequest message from Server Server requested cleartext password authentication. Entering NpgsqlPasswordPacket.NpgsqlPasswordPacket() Entering NpgsqlPasswordPacket.WriteToStream() Entering PGUtil.WriteString() Listening for next message AuthenticationRequest message from Server Listening for next message BackendKeyData message from Server Entering NpgsqlBackEndKeyData.ReadFromStream() Got ProcessID. Value: 3116 Got SecretKey. Value: -132883070 Listening for next message ReadyForQuery message from Server Listening for next message Connection completed Entering NpgsqlConnection.Close()
I used the Debug level to show that a lot of information can be obtained. Of course, using the Normal level would be less verbose. Also, this same information was added to the file NpgsqlTests.LogFile.