Possible bug with Sync Framework

Discussion of open issues, suggestions and bugs regarding ADO.NET provider for PostgreSQL
Post Reply
kuco
Posts: 3
Joined: Fri 30 May 2014 08:07

Possible bug with Sync Framework

Post by kuco » Fri 30 May 2014 08:35

I am trying to setup synchronization and run into problems.

http://www.devart.com/dotconnect/postgr ... ework.html has a tutorial sample.

Created two databases dbserver and dbclient on same PostgreSQL instance.

Server provisioning is working with this code

Code: Select all

using (var serverConnection = new PgSqlConnection(ConnectionString))
{
    var scopeDesc = new DbSyncScopeDescription("ProductsScope");
    var tableDesc = PgSqlSyncDescriptionBuilder.GetDescriptionForTable("Products", serverConnection);
    scopeDesc.Tables.Add(tableDesc);
    var serverProvision = new PgSqlSyncScopeProvisioning(serverConnection, scopeDesc);
    serverProvision.Apply();
}
Code runs without problems and synchronization tables/triggers are created.

Client side code is straight from tutorial

Code: Select all

using (var serverConn = new PgSqlConnection(ServerConnectionString))
{
    using (var clientConn = new PgSqlConnection(ClientConnectionString))
    {
        var scopeDescription = PgSqlSyncDescriptionBuilder.GetDescriptionForScope("ProductsScope", serverConn);
        SyncScopeProvisioning productionProvisioning = new PgSqlSyncScopeProvisioning(clientConn, scopeDescription);
        productionProvisioning.Apply();
    }
}
productionProvisioning.Apply() throws an error with message zero-length delimited identifier at or near """"


with productionProvisioning.Script() I can see what sql is tried to execute and it contains strange double quotes

Code: Select all

CREATE SEQUENCE "seq_timestamp"
  INCREMENT 1
  MINVALUE 1
  MAXVALUE 9223372036854775807
  START 1;

CREATE TABLE "scopeinfo" (
  scope_local_id SERIAL,
  scope_id UUID NOT NULL, 	
  scope_name VARCHAR(100) NOT NULL,
  scope_sync_knowledge BYTEA NULL,
  scope_tombstone_cleanup_knowledge BYTEA NULL,
  scope_timestamp BIGINT NULL,
  scope_cleanup_timestamp BIGINT NULL,
  scope_user_comment VARCHAR(200) NULL,
  scope_config_id UUID NULL,
  scope_restore_count INT default 0,
  PRIMARY KEY (scope_local_id),
  UNIQUE (scope_name)
);

CREATE TABLE "schemainfo" (
  schema_major_version INT NOT NULL,
  schema_minor_version INT NOT NULL,
  schema_extended_info varchar(100) NOT NULL,
  PRIMARY KEY (schema_major_version,schema_minor_version)  
)


;

INSERT INTO "schemainfo" (schema_major_version, schema_minor_version, schema_extended_info ) VALUES ('2','1','');

CREATE TABLE "scopeconfig" (
  config_id UUID NOT NULL,
  config_data TEXT,
  scope_status char(1) NULL,
  PRIMARY KEY (config_id)
);

CREATE TABLE "scope_parameters" (
  sync_scope_name VARCHAR(100) NOT NULL,
  parameter_data TEXT NOT NULL,
  CONSTRAINT PK_scope_parameters PRIMARY KEY (sync_scope_name)
);

CREATE TABLE "scope_templates" (
  template_id SERIAL,
  template_name VARCHAR(100) NOT NULL,
  template_config_id uuid NOT NULL,
  template_description VARCHAR(100) NOT NULL,
  CONSTRAINT PK_scope_templates PRIMARY KEY (template_id),
  UNIQUE (template_name)
);

CREATE FUNCTION "update_scopeinfo" (
  p_sync_scope_name varchar,
  p_sync_scope_knowledge bytea,
  p_sync_scope_cleanup_knowledge bytea,
  p_sync_check_concurrency INT,
  p_sync_scope_timestamp BIGINT,
  OUT sync_row_count int
)
RETURNS int AS $$
BEGIN
  update "scopeinfo" set
   scope_sync_knowledge = p_sync_scope_knowledge,
    scope_tombstone_cleanup_knowledge = p_sync_scope_cleanup_knowledge
	 where scope_name = p_sync_scope_name
 AND (p_sync_check_concurrency = 0 OR scope_timestamp = p_sync_scope_timestamp);


  GET DIAGNOSTICS sync_row_count = ROW_COUNT;
END;
$$ LANGUAGE plpgsql;

CREATE FUNCTION "get_newtimestamp" (
  OUT sync_new_timestamp int
)
RETURNS int AS $$
BEGIN
  sync_new_timestamp := nextval('"seq_timestamp"');
END;
$$ LANGUAGE plpgsql;

INSERT INTO "scopeconfig" (config_id, config_data, scope_status) VALUES('6a38519d-3847-4061-ad19-799bbdc371ee', '<SyncProviderScopeConfiguration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" IsTemplate="false"><Adapter DelTrig="""products"_on_delete"" GlobalName="products" InsTrig="""products"_on_insert"" UpdTrig="""products"_on_update"" Name="""products""" TrackingTable="""products"_tracking"" SelChngProc="""products"_selectchanges"" SelRowProc="""products"_selectrow"" InsMetaProc="""products"_insertmetadata"" UpdMetaProc="""products"_updatemetadata"" DelMetaProc="""products"_deletemetadata"" InsProc="""products"_insert"" UpdProc="""products"_update"" DelProc="""products"_delete""><Col name="id" type="int4" prec="32" scale="0" size="-1" pk="true" /><Col name="name" type="varchar" prec="0" scale="0" size="100" null="true" /><Col name="category_id" type="int4" prec="32" scale="0" size="-1" null="true" /><Col name="is_available" type="bool" prec="0" scale="0" size="-1" null="true" /></Adapter></SyncProviderScopeConfiguration>', 'P');

INSERT INTO "scopeinfo" (scope_id, scope_name, scope_config_id, scope_user_comment, scope_timestamp) VALUES('60c5fa02-5ef8-4bdd-b098-60c034dfc659', 'ProductsScope', '6a38519d-3847-4061-ad19-799bbdc371ee', '', nextval('"seq_timestamp"'));

CREATE TABLE [b]""[/b]products[b]""[/b] (
  "id" int4 NOT NULL,
  "name" varchar(100),
  "category_id" int4,
  "is_available" bool,
  PRIMARY KEY ("id")  
);

CREATE TABLE [b]""products"[/b]_tracking" (
  "id" int4 NOT NULL,
  update_scope_local_id INT NULL, 
  scope_update_peer_key INT,
  scope_update_peer_timestamp BIGINT NULL,
  local_update_peer_key INT,
  local_update_peer_timestamp BIGINT,
  create_scope_local_id INT NULL,
  scope_create_peer_key INT,
  scope_create_peer_timestamp BIGINT,
  local_create_peer_key INT,
  local_create_peer_timestamp INT,
  sync_row_is_tombstone INT,
  restore_timestamp INT, 
  last_change_datetime TIMESTAMP NULL,
  
  PRIMARY KEY ("id")  
);

INSERT INTO [b]""products"_tracking"[/b] ("id", create_scope_local_id, local_create_peer_key, local_create_peer_timestamp, update_scope_local_id, local_update_peer_key, restore_timestamp, sync_row_is_tombstone, local_update_peer_timestamp)
SELECT m."id", null, 0, nextval('"seq_timestamp"'), NULL, 0, NULL, 0, nextval('"seq_timestamp"')
FROM [b]""[/b]products[b]""[/b] m
LEFT OUTER JOIN ""products"_tracking" t ON m."id" = t."id"
WHERE t."id" IS NULL;

CREATE FUNCTION ""products"_on_insert_func"() RETURNS trigger AS $$
BEGIN
    UPDATE ""products"_tracking"
    SET sync_row_is_tombstone = 0, local_update_peer_key = 0, local_update_peer_timestamp = nextval('"seq_timestamp"'), update_scope_local_id = NULL,
      last_change_datetime = 'NOW'
    WHERE "id" = new."id";

    IF NOT FOUND THEN
      INSERT INTO ""products"_tracking" (
        "id", 
        create_scope_local_id, 
        local_create_peer_key, 
        local_create_peer_timestamp, 
        update_scope_local_id, 
        local_update_peer_key, 
        local_update_peer_timestamp,
		sync_row_is_tombstone,
		last_change_datetime)
      VALUES (
        new."id",
        NULL, 
        0, 
        nextval('"seq_timestamp"'), 
        NULL, 
        0, 
        nextval('"seq_timestamp"'), 
        0, 
        'NOW');
    END IF;
    RETURN NEW;
END;     
$$ LANGUAGE plpgsql;

CREATE TRIGGER ""products"_on_insert" AFTER INSERT ON ""products""
FOR EACH ROW EXECUTE PROCEDURE ""products"_on_insert_func"();

CREATE OR REPLACE FUNCTION ""products"_on_update_func"() RETURNS trigger AS $$
BEGIN
  UPDATE ""products"_tracking" SET
    update_scope_local_id = NULL, local_update_peer_key = 0, 
    local_update_peer_timestamp = nextval('"seq_timestamp"'), last_change_datetime = 'NOW'
  WHERE "id" = old."id";
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER ""products"_on_update" AFTER UPDATE ON ""products""
FOR EACH ROW EXECUTE PROCEDURE ""products"_on_update_func"();

CREATE OR REPLACE FUNCTION ""products"_on_delete_func"() RETURNS trigger AS $$
BEGIN
  UPDATE ""products"_tracking" SET
    sync_row_is_tombstone = 1,
	update_scope_local_id = NULL,
	local_update_peer_key = 0,
	local_update_peer_timestamp = nextval('"seq_timestamp"'),
    last_change_datetime = 'NOW' 
  WHERE "id" = old."id";
  RETURN OLD;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER ""products"_on_delete" AFTER DELETE ON ""products""
FOR EACH ROW EXECUTE PROCEDURE ""products"_on_delete_func"();

CREATE FUNCTION ""products"_selectchanges" (
  p_sync_min_timestamp bigint,
  p_sync_metadata_only int,
  p_sync_scope_local_id int,
  p_sync_initialize int)
RETURNS refcursor AS $$
DECLARE
  changes refcursor;
BEGIN
  OPEN changes FOR SELECT
    t."id",
    m."name",
    m."category_id",
    m."is_available",
    t.sync_row_is_tombstone,
    t.local_update_peer_timestamp as sync_row_timestamp,
    CASE
    WHEN (t.update_scope_local_id is null or t.update_scope_local_id <> p_sync_scope_local_id)
        THEN
        CASE
            WHEN (t.restore_timestamp is null) THEN t.local_update_peer_timestamp
            ELSE t.restore_timestamp
        END
    ELSE t.scope_update_peer_timestamp
    END AS sync_update_peer_timestamp,

    CASE
    WHEN (t.update_scope_local_id is null or t.update_scope_local_id <> p_sync_scope_local_id) THEN t.local_update_peer_key
    ELSE t.scope_update_peer_key
    END AS sync_update_peer_key,
    
    CASE
    WHEN (t.create_scope_local_id is null or t.create_scope_local_id <> p_sync_scope_local_id) THEN t.local_create_peer_timestamp
    ELSE t.scope_create_peer_timestamp
    END AS sync_create_peer_timestamp,

    CASE
    WHEN (t.create_scope_local_id is null or t.create_scope_local_id <> p_sync_scope_local_id) THEN t.local_create_peer_key
    ELSE t.scope_create_peer_key
    END AS sync_create_peer_key
  FROM ""products"" m
  RIGHT JOIN ""products"_tracking" t
  ON m."id" = t."id"
  WHERE (t.update_scope_local_id is NULL OR t.update_scope_local_id <> p_sync_scope_local_id OR
  (t.update_scope_local_id = p_sync_scope_local_id AND t.scope_update_peer_key <> CASE WHEN (t.update_scope_local_id is null or t.update_scope_local_id <> p_sync_scope_local_id)
  THEN t.local_update_peer_key ELSE t.scope_update_peer_key END)) 
  AND t.local_update_peer_timestamp > p_sync_min_timestamp;

  RETURN changes;
END;
$$ LANGUAGE plpgsql;

CREATE FUNCTION ""products"_selectrow" (
  p_id INT4,
  p_sync_scope_local_id INT
)
RETURNS refcursor AS $$
DECLARE
  row refcursor;
BEGIN 
  OPEN row FOR SELECT * FROM (SELECT
    t."id",
    m."name",
    m."category_id",
    m."is_available",
    t.sync_row_is_tombstone,
    t.local_update_peer_timestamp as sync_row_timestamp,
    CASE
    WHEN (t.update_scope_local_id is null or t.update_scope_local_id <> p_sync_scope_local_id)
        THEN
        CASE
            WHEN (t.restore_timestamp is null) THEN t.local_update_peer_timestamp
            ELSE t.restore_timestamp
        END
    ELSE t.scope_update_peer_timestamp
    END AS sync_update_peer_timestamp,

    CASE
    WHEN (t.update_scope_local_id is null or t.update_scope_local_id <> p_sync_scope_local_id) THEN t.local_update_peer_key
    ELSE t.scope_update_peer_key
    END AS sync_update_peer_key,
    
    CASE
    WHEN (t.create_scope_local_id is null or t.create_scope_local_id <> p_sync_scope_local_id) THEN t.local_create_peer_timestamp
    ELSE t.scope_create_peer_timestamp
    END AS sync_create_peer_timestamp,

    CASE
    WHEN (t.create_scope_local_id is null or t.create_scope_local_id <> p_sync_scope_local_id) THEN t.local_create_peer_key
    ELSE t.scope_create_peer_key
    END AS sync_create_peer_key
 FROM ""products"" m
 RIGHT JOIN ""products"_tracking" t
 ON m."id" = t."id") t
 WHERE t."id" = p_id;

 RETURN row;
END;
$$ LANGUAGE plpgsql;

CREATE FUNCTION ""products"_insert" (
  p_id INT4,
  p_name VARCHAR,
  p_category_id INT4,
  p_is_available BOOL,
  OUT sync_row_count int)
RETURNS int AS $$
DECLARE  
  rowexist INTEGER;
BEGIN

  sync_row_count = 0;
  SELECT INTO rowexist COUNT(*) FROM ""products"_tracking" t WHERE t."id" = p_id;

  IF rowexist = 0 THEN
    INSERT INTO ""products"" ("id", "name", "category_id", "is_available")
    VALUES (p_id, p_name, p_category_id, p_is_available);
	GET DIAGNOSTICS sync_row_count = ROW_COUNT;
  END IF;

 
END;
$$ LANGUAGE plpgsql;

CREATE FUNCTION ""products"_update" (									
  p_id INT4,
  p_name VARCHAR,
  p_category_id INT4,
  p_is_available BOOL,
  p_sync_min_timestamp bigint,  
  p_sync_force_write int,
  OUT sync_row_count int
) RETURNS int AS $$
BEGIN
  UPDATE ""products""
  SET "name" = p_name, "category_id" = p_category_id, "is_available" = p_is_available
  WHERE "id" = p_id AND
  EXISTS (SELECT * FROM ""products"_tracking" t WHERE (t.local_update_peer_timestamp <= p_sync_min_timestamp OR p_sync_force_write = 1) AND 
  t."id" = p_id);

  GET DIAGNOSTICS sync_row_count = ROW_COUNT;
END;
$$ LANGUAGE plpgsql;

CREATE FUNCTION ""products"_delete" (
  p_id INT4,
  p_sync_min_timestamp bigint,
  p_sync_force_write int,
  OUT sync_row_count int
)
RETURNS int AS $$
BEGIN
  DELETE FROM ""products""
  WHERE "id" = p_id AND
  EXISTS (SELECT * FROM ""products"_tracking" t WHERE (t.local_update_peer_timestamp <= p_sync_min_timestamp OR p_sync_force_write = 1) AND 
  t."id" = p_id);

  GET DIAGNOSTICS sync_row_count = ROW_COUNT;
END;
$$ LANGUAGE plpgsql;

CREATE FUNCTION ""products"_insertmetadata" (
  p_id INT4,
  p_sync_scope_local_id int,
  p_sync_row_is_tombstone int,
  p_sync_create_peer_key int,
  p_sync_create_peer_timestamp bigint,
  p_sync_update_peer_key INT,
  p_sync_update_peer_timestamp bigint,
  p_sync_check_concurrency int,
  p_sync_row_timestamp bigint,
  OUT sync_row_count int)
RETURNS int AS $$
BEGIN
  UPDATE ""products"_tracking" SET 
    create_scope_local_id = p_sync_scope_local_id, 
    scope_create_peer_key = p_sync_create_peer_key,
    scope_create_peer_timestamp = p_sync_create_peer_timestamp,
    local_create_peer_key = 0,
    local_create_peer_timestamp = nextval('"seq_timestamp"'),
    update_scope_local_id = p_sync_scope_local_id,
    scope_update_peer_key = p_sync_update_peer_key,
    scope_update_peer_timestamp = p_sync_update_peer_timestamp,
    local_update_peer_key = 0,
    restore_timestamp = NULL,
    sync_row_is_tombstone = p_sync_row_is_tombstone 
    WHERE "id" = p_id
    AND (p_sync_check_concurrency = 0 OR local_update_peer_timestamp = p_sync_row_timestamp);
	
  IF NOT FOUND THEN	
    INSERT into ""products"_tracking"
    VALUES (
      p_id,
      p_sync_scope_local_id, 
      p_sync_create_peer_key,
      p_sync_create_peer_timestamp,
      0,
      nextval('"seq_timestamp"'),
      p_sync_scope_local_id,
      p_sync_update_peer_key,
      p_sync_update_peer_timestamp,
      0,
      NULL,
      p_sync_row_is_tombstone);    
  END IF;
  GET DIAGNOSTICS sync_row_count = ROW_COUNT;
END;
$$ LANGUAGE plpgsql;

CREATE FUNCTION ""products"_updatemetadata" (
  p_id INT4,
  p_sync_scope_local_id int,
  p_sync_row_is_tombstone int,
  p_sync_create_peer_key int,
  p_sync_create_peer_timestamp bigint,
  p_sync_update_peer_key int,
  p_sync_update_peer_timestamp bigint,
  p_sync_row_timestamp bigint,
  p_sync_check_concurrency int,
  OUT sync_row_count int
)
RETURNS int AS $$
DECLARE
  was_tombstone int;
BEGIN
	SELECT sync_row_is_tombstone INTO was_tombstone FROM ""products"_tracking" t
	WHERE t."id" = p_id;
	
	IF (was_tombstone IS NOT NULL AND was_tombstone=1 AND p_sync_row_is_tombstone=0) THEN
		UPDATE ""products"_tracking" SET
			update_scope_local_id = p_sync_scope_local_id, 
			scope_update_peer_key = p_sync_update_peer_key,
			scope_update_peer_timestamp = p_sync_update_peer_timestamp,
			local_update_peer_key = 0,
			restore_timestamp = NULL,
			create_scope_local_id = p_sync_scope_local_id, 
			scope_create_peer_key = p_sync_create_peer_key, 
			scope_create_peer_timestamp = p_sync_create_peer_timestamp, 
			sync_row_is_tombstone = p_sync_row_is_tombstone,
			local_create_peer_timestamp = nextval('"seq_timestamp"') 						
		WHERE "id" = p_id
		AND (p_sync_check_concurrency = 0 OR local_update_peer_timestamp = p_sync_row_timestamp);
	ELSE
		UPDATE ""products"_tracking" SET
			update_scope_local_id = p_sync_scope_local_id, 
			scope_update_peer_key = p_sync_update_peer_key,
			scope_update_peer_timestamp = p_sync_update_peer_timestamp,
			local_update_peer_key = 0,
			restore_timestamp = NULL,
			sync_row_is_tombstone = p_sync_row_is_tombstone 						
		WHERE "id" = p_id
		AND (p_sync_check_concurrency = 0 OR local_update_peer_timestamp = p_sync_row_timestamp);
	END IF;
	GET DIAGNOSTICS sync_row_count = ROW_COUNT;
END;
$$ LANGUAGE plpgsql
 ;

CREATE FUNCTION ""products"_deletemetadata" (
  p_id INT4,
  p_sync_row_timestamp bigint,
  p_sync_check_concurrency int,
  OUT sync_row_count int
)
RETURNS int AS $$
BEGIN
  DELETE FROM ""products"_tracking"
  WHERE "id" = p_id
  AND (p_sync_check_concurrency = 0 OR local_update_peer_timestamp = p_sync_row_timestamp);

  GET DIAGNOSTICS sync_row_count = ROW_COUNT;
END;
$$ LANGUAGE plpgsql;

UPDATE "scopeconfig" SET scope_status = 'C' WHERE config_id = '6a38519d-3847-4061-ad19-799bbdc371ee';

kuco
Posts: 3
Joined: Fri 30 May 2014 08:07

Re: Possible bug with Sync Framework

Post by kuco » Fri 30 May 2014 08:44

I changed script described above to fix sql syntax errors and tried to synchronize and got error message Function "products"_selectchanges not found.

Stacktrace:

Code: Select all


   at Devart.Data.PostgreSql.PgSqlCommand.DescribeProcedure(String name)
   at Devart.Common.DbCommandBase.CreateParameters()
   at Devart.Data.PostgreSql.Synchronization.PgSqlSyncConnection.DeriveParameters(IDbCommand command)
   at Devart.Data.Synchronization.SyncConnection.CreateDbCommandForStroredProcedure(String name)
   at Devart.Data.Synchronization.DbSyncProviderBase.ConfigurateAdapter(SyncProviderAdapterConfiguration configuration)
   at Devart.Data.Synchronization.DbSyncProviderBase.Configurate()
   at Devart.Data.Synchronization.DbSyncProviderBase.BeginSession(SyncProviderPosition position, SyncSessionContext syncSessionContext)
   at Microsoft.Synchronization.CoreInterop.ISyncSession.Start(CONFLICT_RESOLUTION_POLICY resolutionPolicy, _SYNC_SESSION_STATISTICS& pSyncSessionStatistics)
   at Microsoft.Synchronization.KnowledgeSyncOrchestrator.DoOneWaySyncHelper(SyncIdFormatGroup sourceIdFormats, SyncIdFormatGroup destinationIdFormats, KnowledgeSyncProviderConfiguration destinationConfiguration, SyncCallbacks DestinationCallbacks, ISyncProvider sourceProxy, ISyncProvider destinationProxy, ChangeDataAdapter callbackChangeDataAdapter, SyncDataConverter conflictDataConverter, Int32& changesApplied, Int32& changesFailed)
   at Microsoft.Synchronization.KnowledgeSyncOrchestrator.DoOneWayKnowledgeSync(SyncDataConverter sourceConverter, SyncDataConverter destinationConverter, SyncProvider sourceProvider, SyncProvider destinationProvider, Int32& changesApplied, Int32& changesFailed)
   at Microsoft.Synchronization.KnowledgeSyncOrchestrator.Synchronize()
   at Microsoft.Synchronization.SyncOrchestrator.Synchronize()

kuco
Posts: 3
Joined: Fri 30 May 2014 08:07

Re: Possible bug with Sync Framework

Post by kuco » Fri 30 May 2014 08:47

Additional information

Devart.Data.PostgreSql, 7.3.171.0
Devart.Data.PostgreSql.Synchronization, 7.3.171.0
Devart.Data.Synchronization, 5.0.963.0
Microsoft.Synchronization, 2.1.0.0
Microsoft.Synchronization.Data, 3.1.0.0

Visual Studio 2013, .NET 4.5.1, Target platform x64

PostgreSQL

Code: Select all

# select version();
                           version
-------------------------------------------------------------
 PostgreSQL 9.3.3, compiled by Visual C++ build 1600, 64-bit

Shalex
Site Admin
Posts: 9543
Joined: Thu 14 Aug 2008 12:44

Re: Possible bug with Sync Framework

Post by Shalex » Mon 02 Jun 2014 13:41

Thank you for your report. We have reproduced the issue and are investigating it. We will notify you about the result as soon as possible.

Shalex
Site Admin
Posts: 9543
Joined: Thu 14 Aug 2008 12:44

Re: Possible bug with Sync Framework

Post by Shalex » Tue 10 Jun 2014 12:37

The bug with quoting the tables names in the DbSyncScopeDescription.Tables collection returned by the PgSqlSyncDescriptionBuilder.GetDescriptionForScope method is fixed. We will notify you when the corresponding build of dotConnect for PostgreSQL is available for download.

Shalex
Site Admin
Posts: 9543
Joined: Thu 14 Aug 2008 12:44

Re: Possible bug with Sync Framework

Post by Shalex » Thu 12 Jun 2014 15:05

New build of dotConnect for PostgreSQL 7.3.181 is available for download!
It can be downloaded from http://www.devart.com/dotconnect/postgr ... nload.html (trial version) or from Registered Users' Area (for users with active subscription only).
For more information, please refer to http://forums.devart.com/viewtopic.php?f=3&t=29779 .

Post Reply