add support for Dynamic Entity Registration in DbContext by Provides IXEntityRegisterar interface and XBaseEntityRegisterar as an abstract Implementation ...

all other requirements handled inside Module.
This commit is contained in:
2026-05-26 22:06:38 +03:30
parent 3d274c347f
commit 1dbda46d4c
8 changed files with 474 additions and 8 deletions
+5
View File
@@ -12,6 +12,7 @@ using xDataService.Constants;
using xDataService.Extensions; using xDataService.Extensions;
using xDataService.Helpers; using xDataService.Helpers;
using xDataService.Models; using xDataService.Models;
using xDataService.Providers;
using xExceptions.Constants; using xExceptions.Constants;
namespace xDataService.DI namespace xDataService.DI
@@ -189,6 +190,10 @@ namespace xDataService.DI
return; return;
} }
//
// Register Handler Service ...
source.AddSingleton<XEntityRegisterarHandlerService>();
// //
// Configure Database Descriptor ... // Configure Database Descriptor ...
descriptor.Configure(source); descriptor.Configure(source);
+109 -6
View File
@@ -1,5 +1,10 @@
using System;
using System.Linq;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using xCommons.Extensions;
using xDataService.Configuration; using xDataService.Configuration;
using xDataService.Helpers;
using xDataService.Providers;
namespace xDataService.Db namespace xDataService.Db
{ {
@@ -9,7 +14,13 @@ namespace xDataService.Db
/// </summary> /// </summary>
public abstract class XDbContext : DbContext public abstract class XDbContext : DbContext
{ {
//
#region Properties ...
private readonly string database;
private readonly XDataServiceConfiguration config; private readonly XDataServiceConfiguration config;
private readonly string[] dynamicEntityRegisterarsNames;
private readonly XEntityRegisterarHandlerService dynamicEntityHandlers;
#endregion
// //
#region Entities ... #region Entities ...
@@ -18,10 +29,13 @@ namespace xDataService.Db
#endregion #endregion
// //
// Constructor ... #region Constructor ...
public XDbContext( public XDbContext(
string database,
DbContextOptions options, DbContextOptions options,
XDataServiceConfiguration config XDataServiceConfiguration config,
XEntityRegisterarHandlerService dynamicEntityHandlers,
string[] dynamicEntityRegisterarsNames = null
) : base(options) ) : base(options)
{ {
// //
@@ -32,11 +46,62 @@ namespace xDataService.Db
} }
catch { } catch { }
//
this.config = config; this.config = config;
this.database = database;
this.dynamicEntityHandlers = dynamicEntityHandlers;
this.dynamicEntityRegisterarsNames = dynamicEntityRegisterarsNames;
//
// Handling DbSets of Dynamic Entities ...
if (!dynamicEntityHandlers.IsNull())
{
//
// Configure XEntityRegisterarHandlerService ...
dynamicEntityHandlers.HandleRegisteredEntities();
//
// Loop through Entity Types ...
foreach (var entityType in dynamicEntityHandlers.GetEntityTypes(dynamicEntityRegisterarsNames))
{
//
try
{
//
// Use reflection to call Set<T>()
var method = typeof(XDbContext)
.GetMethod(nameof(XDbContext.Set), Type.EmptyTypes)
.MakeGenericMethod(entityType);
if (!method.IsNull())
{
method.Invoke(this, null);
}
}
catch { }
}
}
}
#endregion
/// <summary>
/// Extract Current Database Configuration ...
/// </summary>
/// <returns></returns>
public XDataBaseConfiguration GetDatabaseConfiguration()
{
//
XDataBaseConfiguration result =
config.Databases.FirstOrDefault(db => string
.Equals(db.Key, database, StringComparison.InvariantCultureIgnoreCase)).Value;
//
return result;
} }
// //
// Prepare Fluent API ... #region Overrides ...
//
// Prepare Model Configs ...
protected override void OnModelCreating(ModelBuilder modelBuilder) protected override void OnModelCreating(ModelBuilder modelBuilder)
{ {
// //
@@ -46,15 +111,37 @@ namespace xDataService.Db
// Automatically apply all IEntityTypeConfiguration<T> in this assembly // Automatically apply all IEntityTypeConfiguration<T> in this assembly
modelBuilder.ApplyConfigurationsFromAssembly(typeof(XDbContext).Assembly); modelBuilder.ApplyConfigurationsFromAssembly(typeof(XDbContext).Assembly);
//
// Extract Entity Container Assemblies ...
var entityAssemblies = XDataServiceHelper.ExtractEntityAssemblies();
foreach(var assembly in entityAssemblies)
{
modelBuilder.ApplyConfigurationsFromAssembly(assembly);
}
//
// Check Dynamic Registered Service is Exists or not ...
if (!dynamicEntityHandlers.IsNull())
{
//
// Set Entity of Dynamically Registered Entities ...
foreach (var entityType in dynamicEntityHandlers.GetEntityTypes(dynamicEntityRegisterarsNames))
{
modelBuilder.Entity(entityType);
}
//
// Configure Entity using Provided Registerar ...
dynamicEntityHandlers
.HandleApplyRegisteredEntitiesConfigurations(modelBuilder, dynamicEntityRegisterarsNames);
}
// //
// Since this class inherit from Identity Db Context, // Since this class inherit from Identity Db Context,
// we have to pass this to Building Identity Models ... // we have to pass this to Building Identity Models ...
base.OnModelCreating(modelBuilder); base.OnModelCreating(modelBuilder);
} }
//
public abstract void OnXModelCreating(ModelBuilder modelBuilder);
// //
// Prepare Configuring ... // Prepare Configuring ...
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
@@ -87,8 +174,24 @@ namespace xDataService.Db
// //
base.OnConfiguring(optionsBuilder); base.OnConfiguring(optionsBuilder);
} }
#endregion
//
#region Abstracts ...
//
public abstract void OnXModelCreating(ModelBuilder modelBuilder);
// //
public abstract void OnXConfiguring(DbContextOptionsBuilder optionsBuilder); public abstract void OnXConfiguring(DbContextOptionsBuilder optionsBuilder);
#endregion
//
#region Dynamic DbSet(s) ...
public override DbSet<TEntity> Set<TEntity>()
where TEntity : class
{
return base.Set<TEntity>();
}
#endregion
} }
} }
+33
View File
@@ -63,5 +63,38 @@ namespace xDataService.Extensions {
return result; return result;
} }
/// <summary>
/// Check a Type is XBaseEntity or not ...
/// </summary>
/// <param name="source"></param>
/// <returns></returns>
public static bool IsXEntity(this Type source)
{
//
var result = !source.IsNull();
if (!result)
{
return result;
}
//
var baseType = source.BaseType;
while(!baseType.IsNull() && baseType != typeof(object))
{
//
if (baseType.IsGenericType &&
baseType.GetGenericTypeDefinition() == typeof(XBaseEntity<>))
{
result = true;
break;
}
//
baseType = baseType.BaseType;
}
//
return result;
}
} }
} }
+38
View File
@@ -1,4 +1,7 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using GraphQL; using GraphQL;
using GraphQL.Server; using GraphQL.Server;
using GraphQL.Server.Ui.Playground; using GraphQL.Server.Ui.Playground;
@@ -1245,6 +1248,41 @@ namespace xDataService.Helpers
} }
#endregion #endregion
//
#region Entity Assembly Detector ...
/// <summary>
/// Extract Assemblies which Contains Entity ...
/// </summary>
/// <returns></returns>
public static List<Assembly> ExtractEntityAssemblies()
{
//
var result = new List<Assembly>();
//
var allAssemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (var assembly in allAssemblies)
{
//
try
{
//
bool hasEntity = assembly
.GetExportedTypes()
.Any(t => t.IsXEntity());
if (hasEntity)
{
result.Add(assembly);
}
}
catch { }
}
//
return result;
}
#endregion
// //
#region Private ... #region Private ...
/// <summary> /// <summary>
+36
View File
@@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;
using xModels.Base;
namespace xDataService.Interfaces
{
/// <summary>
/// an Interface to Intruduce Entities for Dynamic Registration ...
/// </summary>
public interface IXEntityRegisterar
{
/// <summary>
/// Database Name ...
/// </summary>
string Name { get; }
/// <summary>
/// Returns all entity types this plugin wants registered in the DbContext
/// </summary>
IEnumerable<Type> GetEntityTypes();
/// <summary>
/// Optional: Apply additional per-entity configuration
/// </summary>
void ConfigureEntities(ModelBuilder modelBuilder);
/// <summary>
/// Add XDataService Based Entity to List ...
/// </summary>
/// <typeparam name="TEntity"></typeparam>
/// <typeparam name="TKey"></typeparam>
void AddEntity<TEntity, TKey>()
where TEntity : XBaseEntity<TKey>;
}
}
+56
View File
@@ -0,0 +1,56 @@
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;
using xDataService.Interfaces;
using xModels.Base;
namespace xDataService.Providers
{
/// <summary>
/// Base Entity Registerar ...
/// </summary>
public abstract class XBaseEntityRegisterar : IXEntityRegisterar
{
/// <summary>
/// Private Entity Types Collection ...
/// </summary>
private readonly List<Type> entityTypes = [];
/// <summary>
/// Name ...
/// </summary>
public string Name { get; }
protected XBaseEntityRegisterar(string name)
{
Name = name;
}
/// <summary>
/// Add Specified Entity to Collection ...
/// </summary>
/// <typeparam name="TEntity"></typeparam>
/// <typeparam name="TKey"></typeparam>
public void AddEntity<TEntity, TKey>()
where TEntity : XBaseEntity<TKey>
{
entityTypes.Add(typeof(TEntity));
}
/// <summary>
/// Retrieve Registered Entity Types ...
/// </summary>
/// <returns></returns>
public IEnumerable<Type> GetEntityTypes()
{
return entityTypes;
}
/// <summary>
/// Configure Dynamic Entities ...
/// </summary>
/// <param name="modelBuilder"></param>
public virtual void ConfigureEntities(ModelBuilder modelBuilder)
{ }
}
}
@@ -0,0 +1,121 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using Microsoft.EntityFrameworkCore;
using xCommons.Extensions;
using xDataService.Interfaces;
namespace xDataService.Providers
{
public class XEntityRegisterarHandlerService
{
/// <summary>
/// a Collection for Extract Registerars Services ...
/// </summary>
private readonly List<IXEntityRegisterar> registerars = [];
/// <summary>
/// a Collection for Holding all Entity Types ...
/// </summary>
public IReadOnlyList<Type> AllEntityTypes { get; private set; }
/// <summary>
/// Retrieve Specified Registerars Entity Types ...
/// </summary>
/// <param name="names"></param>
/// <returns></returns>
public IReadOnlyList<Type> GetEntityTypes(string[] names = null)
{
//
var items = new List<Type>();
//
if (!names.IsNull() && names.HasChild())
{
//
items = [.. registerars
.Where(r => names.Contains(r.Name))
.SelectMany(r => r.GetEntityTypes())];
}
//
return items.AsReadOnly();
}
/// <summary>
/// Load all Dynamically Registered Entities ...
/// </summary>
/// <param name="path"></param>
public void HandleRegisteredEntities()
{
//
var entityTypes = new List<Type>();
// path = Path.Combine (Directory.GetCurrentDirectory ());
var path = Path.Combine (AppContext.BaseDirectory);
var files = Directory.GetFiles(path, "*.dll");
foreach (var dll in files)
{
//
// Retrieve DLL Assembly ...
var assembly = Assembly.LoadFrom(dll);
//
// Extract Registerar Types ...
var registerarTypes = assembly
.GetExportedTypes()
.Where(t =>
!t.IsAbstract &&
typeof(IXEntityRegisterar).IsAssignableFrom(t));
foreach (var registerarType in registerarTypes)
{
//
// Create Instance of Registerar Type ...
var registerar = (IXEntityRegisterar)Activator.CreateInstance(registerarType);
if (!registerar.IsNull())
{
//
// Add Registerar Instance to Collection ...
registerars.Add(registerar);
//
// Add Registerar Provided Entities to Collection ...
entityTypes.AddRange(registerar.GetEntityTypes());
}
}
//
// Converts to Readonly List ...
AllEntityTypes = entityTypes.AsReadOnly();
}
}
/// <summary>
/// Configure all Dynamically Registered Entities ...
/// </summary>
/// <param name="modelBuilder"></param>
public void HandleApplyRegisteredEntitiesConfigurations(
ModelBuilder modelBuilder,
string[] names = null
)
{
//
var selectedRegisterars =
!names.IsNull() &&
names.HasChild()
? registerars
.Where(r => names.Contains(r.Name))
: registerars;
//
// Loop Through Extracted Registerars ...
foreach(var registerar in selectedRegisterars)
{
//
// Configure Specified Registerars Entities ...
registerar.ConfigureEntities(modelBuilder);
}
}
}
}
+76 -2
View File
@@ -94,8 +94,18 @@ public class XSaherElmDbContext : XDbContext {
public XSaherElmDbContext( public XSaherElmDbContext(
DbContextOptions options, DbContextOptions options,
XDataServiceConfiguration config XDataServiceConfiguration config,
) : base(options, config) { } XEntityRegisterarHandlerService dynamicEntityHandlers
) : base(
// Database nae in Data Service Configurations ...
"xApiDb",
options,
config,
dynamicEntityHandlers,
// a List allowed Dynamic Entity Registerars in this Context ...
new string[] { nameof(XStringEntityRegisterar) }
)
{ }
// //
public override void OnXModelCreating(ModelBuilder modelBuilder) public override void OnXModelCreating(ModelBuilder modelBuilder)
@@ -107,6 +117,70 @@ public class XSaherElmDbContext : XDbContext {
} }
``` ```
#### Dynamic Entities
**xDataService** provides a solution for Dynamic Entity Registration in it's Contexts out of box.
this is useful when some modules needs to presist data.
**IXEntityRegisterar**: an interface for Describe how to Provides Dynamic Entities.
**XBaseEntityRegisterar**: ab abstract of Implementing Dynamic Entity Providers.
```C#
public interface IXEntityRegisterar
{}
public abstract class XBaseEntityRegisterar : IXEntityRegisterar
{}
```
for exampe we have **xStringService** which it is a module for Provides String resources in an Application. this module required to has Data Persist using Specified Entities in Implemented Application Contexts.
```C#
public class XString : XBaseIntIDEntity
{
[Required]
[StringLength (10)]
public string Language { get; set; }
[Required]
[StringLength (255)]
public string ResourceTitle { get; set; }
[Required]
public string TranslatedValue { get; set; }
}
public class XStringEntityConfiguration : XBaseEntityTypeConfiguration<XString, int>
{
public override void Configure(EntityTypeBuilder<XString> builder)
{
builder.ToTable("Strings");
}
}
public class XStringEntityRegisterar : XBaseEntityRegisterar, IXEntityRegisterar
{
public XStringEntityRegisterar()
: base(nameof(XStringEntityRegisterar))
{
//
// Add XString Entity ...
AddEntity<XString, int>();
}
/// <summary>
/// Configure Entities ...
/// </summary>
/// <param name="modelBuilder"></param>
public override void ConfigureEntities(ModelBuilder modelBuilder)
{
base.ConfigureEntities(modelBuilder);
}
}
```
by this mechanism, provided Entity Dynamically Registered on Application DbContext automatically.
#### Repository Pattern #### Repository Pattern
by this feature you can manipulate specified Entity using Repository Pttern implementation. by this feature you can manipulate specified Entity using Repository Pttern implementation.