An extended and modernized Entity Framework Core provider for Progress OpenEdge databases, supporting EF Core 9
$ dotnet add package EntityFrameworkCore.OpenEdge.ExtendedEntityFrameworkCore.OpenEdge is an Entity Framework Core provider that allows you to use Entity Framework Core with Progress OpenEdge databases through ODBC connections. This provider supports EF Core 9.
SELECT, WHERE, ORDER BY, GROUP BY, SKIP/TAKE (paging), COUNT, SUM, FIRSTINNER JOIN, LEFT JOIN, for navigation properties, filtered includesIncludeINSERT, UPDATE, DELETE operations with OpenEdge-optimized SQL generationContains, StartsWith, EndsWith translated to optimized LIKE patternsLength property translated to LENGTH() functionYear, Month, Day, DayOfYear, DayOfWeekFromDateTime, AddDays, AddMonths, AddYears{ ts 'yyyy-MM-dd HH:mm:ss' } syntax? parametersInstall the NuGet package:
dotnet add package EntityFrameworkCore.OpenEdge.Extended --version 9.0.6public class MyDbContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseOpenEdge("Driver=Progress OpenEdge 11.7 Driver;HOST=localhost;port=10000;UID=<user>;PWD=<password>;DIL=1;Database=<database>");
}
}Create an ODBC DSN for your Progress OpenEdge database:
public class MyDbContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseOpenEdge("dsn=MyDb;password=mypassword");
}
}By default, the provider uses the "pub" schema. You can specify a different default schema:
public class MyDbContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
// Using custom schema (defaults to "pub" if not specified)
optionsBuilder.UseOpenEdge(
connectionString: "dsn=MyDb;password=mypassword",
defaultSchema: "myschema");
}
}Note: The schema parameter affects table name resolution when tables don't have explicitly defined schemas in your entity configurations.
Reverse engineer an existing OpenEdge database:
Scaffold-DbContext "dsn=MyDb;password=mypassword" EntityFrameworkCore.OpenEdge -OutputDir ModelsrowidOpenEdge databases don't have true primary keys. Primary indexes exist but are not required to be unique, which conflicts with EF Core's entity tracking requirements. The rowid is your best option for a reliable primary key:
[Key]
[Column("rowid")]
public string Rowid { get; set; }For composite primary keys using unique indexes:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Transaction>().HasKey("TransactionId", "ClientId", "SecondaryId");
}OpenEdge SQL requires explicit boolean comparisons (e.g., WHERE IsActive = 1). The provider automatically handles this:
// This C# code:
var activeUsers = context.Users.Where(u => u.IsActive).ToList();
// Becomes this SQL:
// SELECT * FROM "Users" WHERE "IsActive" = 1Skip() and Take()OpenEdge requires literal values for OFFSET and FETCH clauses. The provider automatically inlines these values rather than using parameters:
// This code:
var pagedResults = context.Users.Skip(10).Take(20).ToList();
// Generates:
// SELECT * FROM "Users" OFFSET 10 ROWS FETCH NEXT 20 ROWS ONLYEach INSERT, UPDATE, and DELETE operation executes individually. Multiple changes in a single SaveChanges() call result in multiple database round trips.
The provider uses positional ? parameters instead of named parameters, carefully managing parameter order to match SQL placeholders.
LINQ queries are translated into OpenEdge-compatible SQL with several key customizations:
WHERE "IsActive" = 1).Contains(), .StartsWith(), and .EndsWith() translate to SQL LIKE expressions using CONCAT functionsSkip() and Take() values are inlined as literals into OFFSET/FETCH clauses? parameters instead of named parametersThe update pipeline handles OpenEdge's specific requirements:
RETURNING Support: OpenEdge doesn't support RETURNING clauses, affecting concurrency detection and identity retrieval? placeholders in generated SQL{ ts 'yyyy-MM-dd HH:mm:ss' } for datetime and DateOnly literalsFor developers interested in the technical details of how LINQ queries become SQL:
OpenEdgeQueryableMethodTranslatingExpressionVisitor converts high-level operations (Where, OrderBy, Skip, Take) into relational expressionsOpenEdgeSqlTranslatingExpressionVisitor handles:
OpenEdgeQueryTranslationPostprocessor validates and optimizes the query treeOpenEdgeParameterBasedSqlProcessor converts OFFSET/FETCH parameters to literal valuesOpenEdgeSqlGenerator produces the final SQL with:
? parametersThese limitations stem from OpenEdge database architecture and SQL dialect, which the provider correctly handles:
? placeholders instead of named parameters like @param1For specific OpenEdge SQL capabilities, consult the OpenEdge SQL Reference.
This repository is a fork and modernization of an older OpenEdge EF Core provider originally written for .NET Framework 2.1. The current implementation represents a significant evolution with proper architecture, comprehensive type mappings, and OpenEdge-specific optimizations.
The repository maintains multiple branches for different EF Core versions:
| Branch | EF Core Version | Status | Description |
|---|---|---|---|
master | 9.x | ✅ Complete | Fully validated with all essential features |
efcore8 | 8.x | ⚠️ Needs Features | Basic migration complete, missing essential features |
efcore6 | 6.x | ⚠️ Needs Features | Basic migration complete, missing essential features |
efcore5 | 5.x | ⚠️ Needs Features | Basic migration complete, missing essential features |
efcore3.1 | 3.1.x | ⚠️ Needs Features | Basic migration complete, missing essential features |
The most critical contribution needed is backporting essential features from the master (EF Core 9) branch to older versions
Backporting Process:
efcore8)master branchQuery Translation Enhancements:
Performance & Reliability:
This project needs comprehensive test coverage following EF Core provider specifications:
Required Test Coverage:
Testing Resources: