EDK4Net2 4.1.0

dotnet add package EDK4Net2 --version 4.1.0
                    
NuGet\Install-Package EDK4Net2 -Version 4.1.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="EDK4Net2" Version="4.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="EDK4Net2" Version="4.1.0" />
                    
Directory.Packages.props
<PackageReference Include="EDK4Net2" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add EDK4Net2 --version 4.1.0
                    
#r "nuget: EDK4Net2, 4.1.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package EDK4Net2@4.1.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=EDK4Net2&version=4.1.0
                    
Install as a Cake Addin
#tool nuget:?package=EDK4Net2&version=4.1.0
                    
Install as a Cake Tool

EDK4Net2

EDK4Net2 is a framework designed to simplify the development of applications and libraries in C# / VB.NET, targeting .NET 10.0.

NuGet License

💡 Tip: Instead of writing classes by hand, use the EDK4Net Code Generator to generate them automatically from your database schema.


Features

  • ORM – Active Record pattern via BusinessObject<T> with automatic change tracking (IsNew, IsDirty, IsDeleted)
  • Query Language – LINQ-style fluent queries and a Classic SQL-like engine, both always available
  • Validation – Imperative CheckRules() / CheckDeleteRules() override with BrokenRuleException
  • Transactions (CNWrapper) – Atomic multi-object, multi-database transactions managed automatically by Save()
  • Auditing – Per-property change recording with old/new values, user ID, and timestamp
  • Object Locking – Pessimistic record locking (LockExclusive / RemoveLock) for multi-user environments
  • Logging – File-based logger with email delivery, buffering and Microsoft.Extensions.Logging integration
  • Code Generator – Windows WPF tool that generates all BusinessObject<T> classes from your DB schema

Supported Databases

Database Provider
SQL Server Microsoft.Data.SqlClient
MySQL MySqlConnector

Installation

dotnet add package EDK4Net2

Or via Package Manager Console in Visual Studio:

Install-Package EDK4Net2

Quick Start

1 – Configuration

Set the connection string and provider in Program.cs:

// Program.cs (.NET 10)
var builder = WebApplication.CreateBuilder(args);

EDK4NetConfig.DefaultCnString = builder.Configuration
    .GetConnectionString("DefaultConnection");
EDK4NetConfig.DefaultDataProvider = DataProviderType.SqlServer;
EDK4NetConfig.EnableAudit = true;
// appsettings.json
{
  "ConnectionStrings": {
    "DefaultConnection": "Server=.;Database=MyDb;Trusted_Connection=True;"
  }
}

2 – Define a Business Object

Every class inherits from BusinessObject<T>. Properties are automatically tracked.

using EDK4Net.Core;
using EDK4Net.Data;

public class Customer : BusinessObject<Customer>
{
    [PrimaryKey]
    public int    CustomerId { get; set; }
    public string FirstName  { get; set; } = "";
    public string LastName   { get; set; } = "";
    public string Email      { get; set; } = "";
    public bool   IsActive   { get; set; } = true;

    // Factory methods called by the DataPortal
    public static Customer GetObject(int id)
        => DataPortal.Fetch<Customer>(id);

    public static Customer NewObject()
        => DataPortal.Create<Customer>();
}

// Collection class
public class CustomerCollection : SortableCollectionBase<Customer>
{
    public static CustomerCollection GetCollection()
        => DataPortal.FetchCollection<CustomerCollection, Customer>();
}

3 – CRUD Operations

// --- CREATE ---
var customer = Customer.NewObject();
customer.FirstName = "Mario";
customer.LastName  = "Rossi";
customer.Email     = "mario.rossi@example.com";

if (customer.IsValid)
    customer.Save();  // automatic INSERT

Console.WriteLine($"Created with ID: {customer.CustomerId}");

// --- READ ---
var single = Customer.GetObject(1);
Console.WriteLine($"{single.FirstName} {single.LastName}");

var all = CustomerCollection.GetCollection();
Console.WriteLine($"Total customers: {all.Count}");

// --- UPDATE ---
single.Email = "new@example.com";
Console.WriteLine($"Modified: {single.IsDirty}");  // true
single.Save();  // automatic UPDATE

// --- DELETE ---
single.Delete();
single.Save();  // automatic DELETE

4 – Validation Rules

Override CheckRules() to define save conditions and CheckDeleteRules() for delete conditions. Save() calls CheckRules() automatically and throws BrokenRuleException on violations.

public class Customer : BusinessObject<Customer>
{
    // ... properties ...

    protected override void CheckRules()
    {
        if (string.IsNullOrEmpty(FirstName))
            BrokenRules.Add(new BrokenRule(this,
                "First name is required", FirstName));

        if (LastName?.Length > 50)
            BrokenRules.Add(new BrokenRule(this,
                "Last name cannot exceed 50 characters", LastName));
    }

    protected override void CheckDeleteRules()
    {
        if (Orders.Count > 0)
            BrokenRules.Add(new BrokenRule(this,
                "Cannot delete: associated orders exist", Orders.Count));
    }
}

// Usage
try
{
    customer.Save();
}
catch (BrokenRuleException ex)
{
    foreach (var rule in ex.BrokenRules)
        Console.WriteLine(rule.RuleName);
    // Output: "First name is required"
}

5 – Transactions (CNWrapper)

CNWrapper is the framework's internal connection and transaction coordinator — never instantiated directly. It is created and managed automatically by Save(). All objects involved are committed together or rolled back atomically. It also supports multi-database scenarios: objects on different databases are coordinated in the same atomic transaction.

public class Order : BusinessObject<Order>
{
    // PreSaveMethod — called before the DB write; prepare related objects
    protected override void PreSaveMethod(CNWrapper cnWrapper)
    {
        if (IsDeleted)
        {
            foreach (var item in Items)
            {
                item.Delete();
                CurrentCNWrapper.AddObject(item);
            }
        }
    }

    // PostSaveMethod — called after the parent write, before commit; save children
    protected override void PostSaveMethod(CNWrapper cnWrapper, SavingType saveMethod)
    {
        if (saveMethod == SavingType.Insert)
        {
            foreach (var note in newNotes)
            {
                note.OrderId = OrderId; // propagate the new parent ID
                note.Save(cnWrapper);   // same transaction as the parent
            }
            newCommissions.Save(cnWrapper);
        }

        Lines.Save(cnWrapper); // always save lines in the same transaction
    }

    // AfterSaveMethod — called after commit; CNWrapper is already closed
    protected override void AfterSaveMethod(bool saved, SavingType saveMethod)
    {
        if (saved)
            Cache.Invalidate(this);
    }

    // Add a related object to the current transaction from a business method
    public void Approve()
    {
        relatedAsset.TotalAvailable += Amount;
        CurrentCNWrapper.AddObject(relatedAsset);
        Save();
    }
}

6 – Query Language

Two query engines are always available.

// --- LINQ Query (recommended) ---

// Simple filter + ordering + pagination
var query = Order.Query()
    .Where(o => o.Status == "Active")
    .OrderByDescending(o => o.OrderDate)
    .Take(20)
    .ToSelectQuery();

var orders = new OrderCollection();
orders.GetBySelectQuery(query);

// OR conditions
var query2 = Order.Query()
    .Where(o => o.Status == "Pending")
    .WhereOr(o => o.Status == "Processing")
    .ToSelectQuery();

// Join between entities with filters on both
var query3 = Order.Query()
    .Join<OrderLine, int>(o => o.Id, l => l.OrderId)
    .Where(o => o.CustomerId == 42)
    .Where<OrderLine>(l => l.Quantity > 0)
    .Distinct()
    .ToSelectQuery();

// Pagination
var page = Order.Query()
    .Where(o => o.Status == "Active")
    .OrderBy(o => o.Id)
    .Skip(20).Take(10)
    .ToSelectQuery();

// ToSelectQuery() converts to the classic engine:
// use with GetBySelectQuery() to load collections,
// or with GetCustomQueryDV() to get a DataView.

// --- Classic QueryLanguage (always available) ---
// More complete, SQL-like syntax.
// Supports SelectQuery, CustomQuery, DeleteQuery,
// aggregate functions (TOP, COUNT, MAX, MIN, SUM, AVG)
// and direct access to Properties and Operators.

7 – EDK4Net Code Generator

The EDK4Net Code Generator is a Windows WPF (.NET 10) application that automatically generates all BusinessObject<T> classes and their collections directly from your database schema (SQL Server or MySQL), eliminating all boilerplate code. It will soon also be available as a Visual Studio add-on.

Typical workflow:

  1. Configure the DB connection (DB Options)
  2. Reload the schema (Reload)
  3. Customise class names, namespaces, and property types
  4. Generate the code (Generate Code) — .cs files are written to disk
  5. Add your business logic in the separate partial files
// File BASE (auto-generated — never edit this file)
[Serializable]
[ObjectMetaData(TableName = "Customers")]
public partial class Customer : BusinessObject<Customer>
{
    [PrimaryKey, AutoKey]
    public int    CustomerId { get; set; }
    public string FirstName  { get; set; } = "";
    public string LastName   { get; set; } = "";
    public string Email      { get; set; } = "";
    public bool   IsActive   { get; set; }

    protected override object GetIdValue() => CustomerId;
    protected override string GetDefaultCNString()
        => EDK4NetConfig.DefaultCnString;
    // ... FetchMethod, InsertMethod, UpdateMethod, DeleteMethod generated here
}

// File PARTIAL (your logic — never overwritten by the generator)
public partial class Customer
{
    protected override void CheckRules()
    {
        if (string.IsNullOrEmpty(FirstName))
            BrokenRules.Add(new BrokenRule(this,
                "First name is required", FirstName));
    }

    public static Customer GetCustomer(int id)
    {
        var e = CreateExampleObject();
        e.CustomerId = id;
        return GetByExample(e);
    }
}

Key features:

  • TreeView with table, class, or namespace view
  • Live search in the tables panel
  • Inline editing: class name, namespace, column type
  • Configurable [Obsolete] attribute per column
  • DB schema comparison (detects added/removed tables)
  • Import of legacy projects (.e4ncg.e4nproj)
  • Coming soon: Visual Studio add-on

⬇ Download the Code Generator


8 – Auditing

Enable auditing on a class by overriding AuditEnabled() and KindOfAudit(). Every property change is automatically recorded as a DbChange entry (old value, new value, user, timestamp).

public class Product : BusinessObject<Product>
{
    protected override bool AuditEnabled() => true;

    protected override AuditKind KindOfAudit()
        => AuditKind.All; // Insert + Update + Delete
}

// Read the change history
var product = Product.GetObject(1);
var history = product.AuditHistory;

foreach (var change in history)
{
    Console.WriteLine($"{change.ChangeDate}: {change.UserName}");
    foreach (var prop in change.Properties)
        Console.WriteLine($"  {prop.PropertyName}: {prop.OldValue} -> {prop.NewValue}");
}

9 – Object Locking

Pessimistic locking for multi-user environments. Locks are managed in the audit database.

var document = Document.GetObject(documentId);

if (document.IsLocked())
{
    Console.WriteLine("Document is being edited by another user!");
    return;
}

var lockReceipt = document.LockExclusive();

if (!string.IsNullOrEmpty(lockReceipt.Ticket))
{
    try
    {
        document.Title = "New Title";
        document.Save();
    }
    finally
    {
        document.RemoveLock(lockReceipt.Ticket);
    }
}

10 – Logging

using EDK4Net.Log;

var log = Logger.NewLogger("MyComponent");
log.SubPath   = "AppLogs\\";
log.Logformat = Logger.LogFormat.Frequent;
log.WriteLine("Operation completed.");
log.Save();

To integrate with Microsoft.Extensions.Logging:

// Program.cs
Logger.ConfigureLoggerFactory(loggerFactory);

Configuration Reference

Key Type Default Description
DefaultCnString string Default database connection string
DefaultDataProvider SqlServer | MySql SqlServer Database provider
EnableAudit bool false Enable automatic change auditing
EnableAuthorizationRules bool false Enable authorization rule checks
DBLogging bool false Log generated SQL statements to file
LockDuration int (seconds) 20 Duration of a pessimistic object lock
CacheDuration int (minutes) 20 In-memory object cache duration
LongQueryTimeout int (seconds) 120 Command timeout for long-running queries
ReadOnly bool false Prevent all write operations
logPath string app dir Base path for log files
EnvironmentType Production | Test | Demo Production Runtime environment tag
DebugMode bool false Enable verbose debug output

Requirements

  • .NET 10.0
  • SQL Server 2012+ or MySQL 5.7+


License

Copyright © Edika 2003–2025. All rights reserved.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
4.1.0 54 4/18/2026

Net 10 public release