Migration Guide: UODOTNET to U2 Toolkit for .NET
This guide documents how to migrate PSI code from the deprecated UODOTNET.dll (via PSI.UniSessionManager) to the modern U2.Data.Client.dll (U2 Toolkit for .NET).
Why Migrate?
- UODOTNET.dll is deprecated and in “maintenance mode”
- U2 Toolkit provides built-in connection pooling
- Better .NET 6.0 and .NET Standard 2.0 support
- Officially supported by Rocket Software
Assembly Locations
Recommended (Network Share):
S:\APPS\Approved\DotNet\U2Toolkit\
├── netstandard2.0\U2.Data.Client.dll (for .NET Framework 4.8, AnyCPU)
├── net6.0\U2.Data.Client.dll (for .NET 6+ projects)
└── README.txt
Alternative (Local Install):
C:\Program Files\Rocket Software\U2 Toolkit for .NET\U2 Database Provider\bin\
├── netstandard2.0\U2.Data.Client.dll
└── net6.0\U2.Data.Client.dll
Important: Use the netstandard2.0 version for .NET Framework 4.8 projects to avoid architecture mismatch warnings.
Project Reference
Add to your .csproj:
<Reference Include="U2.Data.Client">
<HintPath>S:\APPS\Approved\DotNet\U2Toolkit\netstandard2.0\U2.Data.Client.dll</HintPath>
</Reference>Or if using local install:
<Reference Include="U2.Data.Client">
<HintPath>C:\Program Files\Rocket Software\U2 Toolkit for .NET\U2 Database Provider\bin\netstandard2.0\U2.Data.Client.dll</HintPath>
</Reference>Namespace Changes
| UODOTNET | U2 Toolkit |
|---|---|
using IBMU2.UODOTNET; | using U2.Data.Client; |
using PSI.UniSessionManager; | using U2.Data.Client.UO; |
Connection Setup
UODOTNET (Old Way)
using IBMU2.UODOTNET;
using PSI.UniSessionManager;
// Via UDSessionController (handles session management internally)
var session = UDSessionController.FindMySession(userName, password, out Guid sessionGuid);
// Or via PSISessionManager directly
var session = UniObjects.OpenSession(server, userName, password, account, service);U2 Toolkit (New Way)
using U2.Data.Client;
using U2.Data.Client.UO;
var connStr = new U2ConnectionStringBuilder
{
UserID = userName,
Password = password,
Server = server, // e.g., "MRP-PROD" or EnvironmentConstants.UniDataHost
Database = "/home/pro3", // Account path
ServerType = "UNIDATA", // or "UNIVERSE"
AccessMode = "Native", // Required for UniSubroutine calls
RpcServiceType = "udcs", // UniData connection service
Pooling = false, // See "Connection Pooling" section below
Connect_Timeout = 30
};
var connection = new U2Connection();
connection.ConnectionString = connStr.ToString();
connection.Open();
// Get the UniSession for subroutine calls
UniSession session = connection.UniSession;CRITICAL: Credential Decryption
This is the most important part. The PSI codebase stores credentials in encrypted form. The legacy UDSessionController.FindMySession() decrypts them internally, but with U2 Toolkit you must decrypt them yourself.
using PSI.Common.Models; // Contains EncryptionService
private const string SharedSecret = "8bGToMI7xUt2H1d";
// Get encrypted credentials from AD helper
var encryptedUser = ActiveDirectoryHelper.GetLoggedInUserName();
var encryptedPassword = ActiveDirectoryHelper.GetLoggedInPassword();
// MUST decrypt before using with U2 Toolkit
var userName = EncryptionService.DecryptStringAES(encryptedUser, SharedSecret);
var password = EncryptionService.DecryptStringAES(encryptedPassword, SharedSecret);If you skip decryption, you’ll get error 80011: “The user name or password provided is incorrect”
Server Address
Use EnvironmentConstants.UniDataHost to get the correct server address from PSI.GlobalSettings:
using PSI.Common.Constants;
string server = EnvironmentConstants.UniDataHost; // Returns "MRP-PROD" or similarFor the UniData API specifically, the server is read from UniData:Server in appsettings.json. Override to PS-MRPSANDBOX in appsettings.Development.json to test write operations safely without touching MRP-PROD:
{
"UniData": {
"Server": "PS-MRPSANDBOX"
}
}PS-MRPSANDBOX.ad.ptihome.com uses the same account path (/home/pro3) and AD credentials as MRP-PROD. See UniData API — PS-MRPSANDBOX for full details.
Calling Subroutines
UODOTNET (Old Way)
// Via UDSessionController.ReadData() - handles parsing automatically
var parameterList = new List<string> { "1", "JOB", "1", "0", "0", partNumber, "1", wbs, "", "" };
var getValueArrayList = new List<UDSessionModel>
{
new UDSessionModel
{
Model = new BillOfMaterialModel(),
ArrayIndex = 8,
MultiValued = true,
ModelList = new List<IModel>()
}
};
UDSessionController.ReadData(_session, "VB_BOMX.REV1", parameterList, getValueArrayList, _sessionGuid);
var results = getValueArrayList[0].ModelList.Cast<BillOfMaterialModel>().ToList();U2 Toolkit (New Way)
// Create subroutine with argument count
var sub = session.CreateUniSubroutine("VB_BOMX.REV1", 10);
// Set arguments (0-indexed)
sub.SetArg(0, "1"); // Company
sub.SetArg(1, "JOB"); // Reqtype
sub.SetArg(2, "1"); // includeparts
sub.SetArg(3, "0"); // mlevel
sub.SetArg(4, "0"); // lds
sub.SetArg(5, partNumber); // PartNumber
sub.SetArg(6, "1"); // qty
sub.SetArg(7, wbs); // wbs
sub.SetArg(8, ""); // rawData (output)
sub.SetArg(9, ""); // messageinfo (output)
// Execute
sub.Call();
// Get output as UniDynArray
UniDynArray rawData = sub.GetArgDynArray(8);Parsing UniDynArray Results
U2 Toolkit returns raw UniDynArray objects. You need to parse them manually:
public static List<BillOfMaterialModel> ParseBomResults(UniDynArray rawData)
{
var results = new List<BillOfMaterialModel>();
if (rawData == null || string.IsNullOrEmpty(rawData.ToString()))
return results;
// Count items using first field
int itemCount = rawData.Dcount(1);
for (int i = 1; i <= itemCount; i++) // 1-indexed!
{
var values = new List<string>();
// Extract each field for this item
for (int field = 1; field <= 20; field++) // BOM has 20 fields
{
var value = rawData.Extract(field, i).ToString();
values.Add(value ?? string.Empty);
}
var model = new BillOfMaterialModel();
model.UpdateModelData(values);
results.Add(model);
}
return results;
}Important: UniDynArray uses 1-based indexing, not 0-based!
Connection Pooling
Disable connection pooling unless you have the appropriate UniData server license. Error 39134 “user limit has been reached” occurs when pooling is enabled without the proper license - this is separate from regular connection licenses.
var connStr = new U2ConnectionStringBuilder
{
// ... other settings ...
Pooling = false, // Requires specific server license
};Performance is still excellent without pooling (~1300 parts/sec vs ~700 with UODOTNET), so this isn’t a significant limitation for most use cases.
Connection Cleanup
Always dispose connections properly:
public void Dispose()
{
if (_connection != null)
{
try
{
if (_connection.State == System.Data.ConnectionState.Open)
{
_connection.Close();
}
_connection.Dispose();
}
catch
{
// Ignore cleanup errors
}
}
_session = null;
_connection = null;
}Complete Example
See PSI.DataExport.CLI\Services\U2ToolkitBomExporter.cs for a complete working implementation.
Quick Reference: Error Codes
| Error Code | Meaning | Solution |
|---|---|---|
| 80011 | Invalid username/password | Decrypt credentials with EncryptionService.DecryptStringAES() |
| 39134 | User/pool limit reached | Disable pooling (Pooling = false) - pooling requires specific server license |
| 39207 | Can’t connect / license issue | Check server availability and network connectivity |
Migration Checklist
- Add U2.Data.Client.dll reference from
S:\APPS\Approved\DotNet\U2Toolkit\netstandard2.0\ - Add
using U2.Data.Client;andusing U2.Data.Client.UO; - Add
using PSI.Common.Models;for EncryptionService - Decrypt credentials before use (SharedSecret = “8bGToMI7xUt2H1d”)
- Use
EnvironmentConstants.UniDataHostfor server address - Set
AccessMode = "Native"for subroutine calls - Set
Pooling = false(pooling requires specific server license) - Update subroutine calls to use
CreateUniSubroutine()pattern - Add UniDynArray parsing logic (1-based indexing!)
- Implement proper
IDisposablecleanup - Test output matches legacy implementation
Performance Notes
In our testing, U2 Toolkit was approximately 2x faster than UODOTNET:
- UODOTNET: ~700 parts/second
- U2 Toolkit: ~1300 parts/second
This improvement comes from the more modern connection handling in U2 Toolkit.