FileManager DataSource
FileDataSource is BeepDM's format-agnostic file data source. It handles CSV, TSV, JSON, Text, and any plugin format through a pluggable reader model — all behind the standard IDataSource interface.
Table of Contents
Overview
FileDataSource (FileManager/FileDataSource.cs) is the
single [AddinAttribute]-registered data source for all flat-file and
structured-file formats. It never parses bytes itself — every format is handled by
an IFileFormatReader that it selects and delegates to at connection-open
time.
- One
classHandler = "FileDataSource"connection type covers all formats. - Format is chosen by
DatasourceTypeon the connection — no code changes needed to switch from CSV to TSV. - Plugin formats (Parquet, ORC, …) are discovered at runtime and appear in the connection manager automatically.
- At runtime you can even switch which reader handles a live connection without recreating it.
Folder Structure
FileManager/
├── FileDataSource.cs ← IDataSource host, IFileDataSourceReaderHost
├── FileDataSource.Connection.cs
├── FileDataSource.Entity.cs
├── FileReaderFactory.cs ← static DataSourceType → IFileFormatReader registry
├── Readers/
│ ├── IFileFormatReader.cs ← reader contract
│ ├── CsvFileReader.cs
│ ├── TsvFileReader.cs
│ ├── JsonFileReader.cs
│ └── TextFileReader.cs
├── Registry/
│ ├── FileReaderRegistry.cs ← assembly-level discovery
│ ├── FileReaderDescriptor.cs
│ └── FileReaderAttribute.cs
└── Attributes/
└── FileReaderAttribute.cs ← [FileReader(...)] decoration
FileDataSource
Registration
// Handles CSV, TSV, JSON and Text out of the box.
// Plugin readers extend this list without changing the registration.
[AddinAttribute(
Category = DatasourceCategory.FILE,
DatasourceType = DataSourceType.CSV | DataSourceType.TSV
| DataSourceType.Json | DataSourceType.Text,
FileType = "csv,tsv,json,txt")]
public partial class FileDataSource : IDataSource, IFileDataSourceReaderHost { }
Add a connection and open it
dmeEditor.ConfigEditor.AddDataConnection(new ConnectionProperties
{
ConnectionName = "SalesData",
FilePath = @"C:\data",
FileName = "sales.csv",
DatabaseType = DataSourceType.CSV,
DriverName = "FileDataSource" // classHandler value
});
var fs = dmeEditor.GetDataSource("SalesData") as FileDataSource;
fs.Openconnection();
// Openconnection() sequence:
// 1. FileReaderFactory.RegisterDefaults() — ensures built-ins are available
// 2. activeReader = FileReaderFactory.GetReader(DatasourceType)
// 3. activeReader.Configure(connectionProperties)
// 4. HasHeader / ParseMode applied from connection props
Standard IDataSource CRUD
// All calls delegate to the active IFileFormatReader internally
EntityStructure entity = fs.GetEntityStructure("sales.csv", refresh: false);
object rows = fs.GetEntity("sales.csv", filters);
IErrorsInfo ins = fs.InsertEntity("sales.csv", newRow);
IErrorsInfo upd = fs.UpdateEntities("sales.csv", fs.ErrorObject, updatedRow);
IErrorsInfo del = fs.DeleteEntity("sales.csv", keyFilter);
fs.Closeconnection();
Every ConnectionDriversConfig for a file-based format must set
classHandler = "FileDataSource". BeepDM's driver resolver
instantiates FileDataSource and the correct reader is then
resolved from DatasourceType at connection-open time.
FileReaderFactory.BuildDriverConfig sets this automatically.
Built-in Readers
Four readers ship with the core assembly and are registered automatically when
FileReaderFactory.RegisterDefaults() is called (which happens inside
every Openconnection()).
| Reader class | DataSourceType | Extension | Notes |
|---|---|---|---|
CsvFileReader | CSV | csv | Configurable delimiter & quote char |
TsvFileReader | TSV | tsv | Tab-separated values |
JsonFileReader | Json | json | JSON arrays / newline-delimited JSON |
TextFileReader | Text | txt | Fixed-width or line-per-record text |
IFileFormatReader contract
public interface IFileFormatReader
{
// Identity
DataSourceType SupportedType { get; }
string GetDefaultExtension();
// Configuration
bool HasHeader { get; set; }
ParseMode ParseMode { get; set; } // Lenient | Strict
void Configure(IConnectionProperties props);
// Schema
string[] ReadHeaders(string filePath);
EntityStructure GetEntityStructure(string filePath);
// Read
IEnumerable<string[]> ReadRows(string filePath);
string InferFieldType(string current, string rawValue);
// Write
bool CreateFile (string filePath, IReadOnlyList<string> headers);
bool AppendRow (string filePath, IReadOnlyList<string> headers, IReadOnlyList<string> values);
bool RewriteFile (string filePath, IReadOnlyList<string> headers, IEnumerable<IReadOnlyList<string>> rows);
// Diagnostics
IReadOnlyList<RowDiagnostic> LastDiagnostics { get; }
void ClearDiagnostics();
}
public enum ParseMode { Lenient, Strict }
public record RowDiagnostic(int RowIndex, DiagnosticSeverity Severity, string Message, string? RawLine = null);
public enum DiagnosticSeverity { Info, Warning, Error }
Every built-in and custom reader is decorated with
[FileReaderAttribute] which carries display metadata used by
FileReaderFactory.BuildDriverConfig:
// Attribute properties
DataSourceType FormatType // routes to this reader
string DisplayName // shown in connection manager UI
string DefaultExtension // e.g. "csv"
string Description
string Author
string Version
string IconPath // defaults to "{ext}.svg" if empty
FileReaderFactory
FileReaderFactory is a static DataSourceType → IFileFormatReader
dictionary. It is the single source of truth consulted by FileDataSource
at connection-open time.
Core API
// Register a reader (replaces any existing entry for the same DataSourceType)
FileReaderFactory.Register(IFileFormatReader reader);
// Scan the core assembly for [FileReaderAttribute] classes — safe to call many times
FileReaderFactory.RegisterDefaults();
// Resolve; falls back to CSV reader for unknown types
IFileFormatReader r = FileReaderFactory.GetReader(DataSourceType.CSV);
// Introspection
bool supported = FileReaderFactory.IsSupported(DataSourceType.Json);
IEnumerable<DataSourceType> all = FileReaderFactory.SupportedTypes;
RegisterWithConfig — also add the format to the connection manager
// Registers in the factory AND upserts a ConnectionDriversConfig into
// configEditor.DataDriversClasses so the format appears in the connection manager.
// classHandler is always set to "FileDataSource".
// Idempotent: duplicate DataSourceType entries are skipped.
FileReaderFactory.RegisterWithConfig(reader, dmeEditor.ConfigEditor);
// Example — third-party Parquet reader registered at startup:
var parquet = new ThirdParty.ParquetFileReader();
FileReaderFactory.RegisterWithConfig(parquet, dmeEditor.ConfigEditor);
BuildDriverConfig — inspect the generated ConnectionDriversConfig
ConnectionDriversConfig cfg = FileReaderFactory.BuildDriverConfig(reader);
// cfg.classHandler = "FileDataSource"
// cfg.DatasourceCategory = DatasourceCategory.FILE
// cfg.DatasourceType = reader.SupportedType
// cfg.extensionstoHandle = attr.DefaultExtension ?? reader.GetDefaultExtension()
// cfg.GuidID = deterministic MD5 GUID from reader type name
// cfg.NeedDrivers = false
FileReaderRegistry & Plugin Readers
FileReaderRegistry bridges plugin assemblies (loaded by
AssemblyHandler) into the factory. Call Discover() once
at application startup after assemblies are loaded.
Discover — the startup call
// Scans editor.assemblyHandler.ConfigEditor.FileReaderClasses,
// instantiates each reader, and calls RegisterWithConfig so the format
// also surfaces as a connection type in the connection manager.
var registry = new FileReaderRegistry(dmeEditor);
registry.Discover(); // call once after assemblies are loaded
Other registry API
IFileFormatReader fresh = registry.Create(DataSourceType.CSV);
IReadOnlyList<FileReaderDescriptor> all = registry.GetAll();
bool hasCsv = registry.Contains(DataSourceType.CSV);
// Static — scan ONE assembly without ConfigEditor access (used by RegisterDefaults)
FileReaderRegistry.RegisterAttributedReadersFromAssembly(assembly);
RegisterDefaults()— core assembly only, no ConfigEditor, called automatically byOpenconnection()as a safety net.Discover()— all plugin assemblies, callsRegisterWithConfig, adds formats to the connection manager.
Feature: Reader Switching
FileDataSource also implements IFileDataSourceReaderHost,
which lets you inspect all registered readers and switch the active reader on a
live connection — without recreating the data-source object.
IFileDataSourceReaderHost interface
public interface IFileDataSourceReaderHost
{
// Enumerate
IReadOnlyList<FileReaderDescriptorInfo> GetAvailableReaderDescriptors(bool discover = false);
IReadOnlyList<DataSourceType> GetAvailableReaderTypes();
FileReaderDescriptorInfo? GetCurrentReaderDescriptor();
object? GetCurrentReader();
// Switch — all overloads return false + reason on failure
bool TrySwitchReader(DataSourceType targetType, bool reconfigure, out string? reason);
bool TrySwitchReader(string implementationTypeName, bool reconfigure, out string? reason);
bool TrySwitchReader(Guid readerId, bool reconfigure, out string? reason);
bool TrySwitchReaderByExtension(string extension, bool reconfigure, out string? reason);
void ResetReaderSelection();
}
How reader selection works during Openconnection()
// Priority order:
// 1. Override set by TrySwitchReader*() — if one was called since last open
// 2. DatasourceType default from ConnectionProperties
// After a successful switch, DatasourceType is updated automatically.
// Switch is blocked while an active transaction / ingestion is in progress.
Switch by DataSourceType
var host = dmeEditor.GetDataSource("SalesData") as IFileDataSourceReaderHost;
if (host.TrySwitchReader(DataSourceType.TSV, reconfigure: true, out var reason))
(host as FileDataSource).Openconnection();
else
Console.WriteLine(reason);
Switch by implementation name (deterministic when multiple readers share a type)
host.TrySwitchReader(
"DuckDBDataSourceCore.FileReaders.DuckDbCsvFileReader",
reconfigure: true,
out var reason);
Switch by ReaderId
var csvReaders = host.GetAvailableReaderDescriptors(discover: true)
.Where(r => r.FormatType == DataSourceType.CSV)
.ToList();
// e.g. present list to user, then switch to selected
host.TrySwitchReader(csvReaders[1].ReaderId, reconfigure: true, out _);
GetAvailableReaderDescriptors does not collapse by
FormatType. When two CSV readers are registered,
TrySwitchReader(DataSourceType.CSV, ...) picks whichever is
currently mapped. Use ImplementationTypeName or ReaderId
for deterministic selection.
Create a Custom Reader
Implement IFileFormatReader, decorate with [FileReader],
load the assembly, and run registry.Discover(). The format then appears
in the connection manager like any built-in.
1) Implement & decorate
using TheTechIdea.Beep.FileManager.Attributes;
using TheTechIdea.Beep.FileManager.Readers;
[FileReader(DataSourceType.CSV, "My Fast CSV Reader", "csv",
Description = "High-performance CSV parser",
Version = "1.0.0")]
public sealed class MyFastCsvReader : IFileFormatReader
{
public DataSourceType SupportedType => DataSourceType.CSV;
public bool HasHeader { get; set; } = true;
public ParseMode ParseMode { get; set; } = ParseMode.Lenient;
public IReadOnlyList<RowDiagnostic> LastDiagnostics { get; } = new List<RowDiagnostic>();
public string GetDefaultExtension() => "csv";
public void Configure(IConnectionProperties props) { /* delimiter / encoding */ }
public void ClearDiagnostics() { }
public string[] ReadHeaders(string filePath) => new[] { "Id", "Name" };
public EntityStructure GetEntityStructure(string filePath) => null;
public IEnumerable<string[]> ReadRows(string filePath) { yield break; }
public string InferFieldType(string current, string rawValue) => typeof(string).FullName;
public bool CreateFile(string fp, IReadOnlyList<string> h) => true;
public bool AppendRow (string fp, IReadOnlyList<string> h, IReadOnlyList<string> v) => true;
public bool RewriteFile(string fp, IReadOnlyList<string> h, IEnumerable<IReadOnlyList<string>> r) => true;
}
2) Register at startup
// Option A — manual (no assembly scanning needed)
FileReaderFactory.RegisterWithConfig(new MyFastCsvReader(), dmeEditor.ConfigEditor);
// Option B — automatic via registry (preferred for plugin assemblies)
var registry = new FileReaderRegistry(dmeEditor);
registry.Discover(); // picks up any [FileReader]-decorated class in loaded assemblies
3) Use it — switch to the new reader on an existing connection
var host = dmeEditor.GetDataSource("SalesData") as IFileDataSourceReaderHost;
var descriptor = host.GetAvailableReaderDescriptors(discover: false)
.First(r => r.ImplementationTypeName == typeof(MyFastCsvReader).FullName);
host.TrySwitchReader(descriptor.ReaderId, reconfigure: true, out var reason);
(host as FileDataSource).Openconnection();
Enterprise Contracts
Data Classification & Masking (Classification/)
public interface IDataClassificationEngine
{
Task<IReadOnlyList<ClassificationHit>> ClassifyColumnAsync(
string columnName, IEnumerable<string> sampleValues,
int sampleSize = 500, CancellationToken ct = default);
}
public sealed record ClassificationHit(
string PatternKey, string Category,
double Confidence, int MatchedSamples, int TotalSamples);
public enum MaskingStrategy
{ Redact, PartialMask, Tokenize, FormatPreservingEncrypt, Synthesize, Generalize, None }
public sealed class ColumnMaskingPolicy
{
public string PatternKey { get; init; }
public MaskingStrategy Strategy { get; init; }
public int PartialMaskRevealChars { get; init; } = 4;
public string TokenizationKeyId { get; init; }
public double GeneralizeRoundTo { get; init; }
}
public interface IDataMaskingEngine
{ string Mask(string value, string patternKey, ColumnMaskingPolicy policy); }
public interface IMaskingPolicyStore
{ ColumnMaskingPolicy GetPolicy(string key); void SetPolicy(string key, ColumnMaskingPolicy policy); }
Call ClassifyColumnAsync to detect PII patterns (SSN, credit cards, emails) in file columns, then apply ColumnMaskingPolicy to redact/mask/tokenize before export.
Governance (Governance/)
public interface IFileAccessPolicies
{
bool CanRead(string fileName, string user, string role);
bool CanWrite(string fileName, string user, string role);
}
public interface IFileAuditLogger
{
Task LogReadAsync(string fileName, string user, int records);
Task LogWriteAsync(string fileName, string user, int records);
}
Configure access policies per file and log all read/write operations for compliance.
Observability (Observability/)
public interface IFileReadTelemetry
{
Task RecordReadAsync(string fileName, string readerId,
long bytes, long records, TimeSpan elapsed);
Task RecordErrorAsync(string fileName, string readerId,
string error, Exception ex);
}
public class FileReadMetrics
{
public long TotalBytesRead { get; set; }
public long TotalRecordsRead { get; set; }
public int TotalErrors { get; set; }
public TimeSpan AccumulatedReadTime { get; set; }
}
Plus built-in per-row diagnostics via IFileFormatReader.LastDiagnostics and ParseMode (Lenient vs Strict).
Resilience (Resilience/)
public interface IFileReadRetryPolicy
{
Task<T> ExecuteAsync<T>(Func<Task<T>> operation,
string operationName, CancellationToken ct);
}
// Built-in: exponential backoff, 3 retries, jitter
Schema Inference (Schema/ & Contracts/)
public interface ISchemaInferenceStrategy
{
EntityStructure InferSchema(Stream stream, string fileName);
}
public interface IIngestionStateStore
{
Task SaveStateAsync(string fileKey, object state);
Task<T> LoadStateAsync<T>(string fileKey);
}
public class InMemoryIngestionStateStore : IIngestionStateStore { }
public static class TypeInferenceHelper
{
public static Type InferColumnType(IEnumerable<string> sampleValues,
string columnName);
}
public static class FileChecksumHelper
{
public static string ComputeHash(string filePath,
HashAlgorithm algorithm = HashAlgorithm.SHA256);
public static bool VerifyHash(string filePath, string expectedHash);
}
Full end-to-end with a plugin Parquet reader
// After assemblies are loaded
var registry = new FileReaderRegistry(dmeEditor);
registry.Discover(); // Parquet reader found, RegisterWithConfig called
// Format is now visible in the connection manager
var cfg = dmeEditor.ConfigEditor.DataDriversClasses
.First(d => d.DatasourceType == DataSourceType.Parquet);
// cfg.classHandler == "FileDataSource"
dmeEditor.ConfigEditor.AddDataConnection(new ConnectionProperties
{
ConnectionName = "warehouse",
DriverName = cfg.PackageName,
FilePath = @"C:\data\warehouse.parquet",
DatabaseType = DataSourceType.Parquet
});
var ds = dmeEditor.GetDataSource("warehouse");
ds.Openconnection(); // FileDataSource routes to ParquetFileReader
Examples
Open a CSV file and read rows
dmeEditor.ConfigEditor.AddDataConnection(new ConnectionProperties
{
ConnectionName = "sales",
FilePath = @"C:\data",
FileName = "sales.csv",
DatabaseType = DataSourceType.CSV,
DriverName = "FileDataSource"
});
var fs = dmeEditor.GetDataSource("sales") as FileDataSource;
fs.Openconnection();
var entity = fs.GetEntityStructure("sales.csv", false);
var rows = (IEnumerable<object>)fs.GetEntity("sales.csv", null);
fs.Closeconnection();
Inspect all registered readers
var host = fs as IFileDataSourceReaderHost;
var readers = host.GetAvailableReaderDescriptors(discover: true);
foreach (var r in readers)
Console.WriteLine($"{r.DisplayName} | {r.FormatType} | {r.DefaultExtension}");
Switch format without recreating the connection
// Currently CSV — switch to TSV reader
if (host.TrySwitchReaderByExtension("tsv", reconfigure: true, out var reason))
fs.Openconnection();
else
Console.WriteLine(reason);
Insert and update rows
fs.InsertEntity("sales.csv", new { OrderId = "ORD-001", Amount = 150.00 });
fs.UpdateEntities("sales.csv", fs.ErrorObject, new { OrderId = "ORD-001", Amount = 175.00 });
fs.DeleteEntity("sales.csv", new AppFilter { FieldName = "OrderId", Operator = "=", FilterValue = "ORD-001" });
fs.Closeconnection();