diff --git a/DI/XDIHelperExtension.cs b/DI/XDIHelperExtension.cs index 2d9e142..5810d85 100644 --- a/DI/XDIHelperExtension.cs +++ b/DI/XDIHelperExtension.cs @@ -12,6 +12,7 @@ using xDataService.Constants; using xDataService.Extensions; using xDataService.Helpers; using xDataService.Models; +using xDataService.Providers; using xExceptions.Constants; namespace xDataService.DI @@ -189,6 +190,10 @@ namespace xDataService.DI return; } + // + // Register Handler Service ... + source.AddSingleton(); + // // Configure Database Descriptor ... descriptor.Configure(source); diff --git a/Db/XDbContext.cs b/Db/XDbContext.cs index 589672e..bd310de 100644 --- a/Db/XDbContext.cs +++ b/Db/XDbContext.cs @@ -1,5 +1,10 @@ +using System; +using System.Linq; using Microsoft.EntityFrameworkCore; +using xCommons.Extensions; using xDataService.Configuration; +using xDataService.Helpers; +using xDataService.Providers; namespace xDataService.Db { @@ -9,7 +14,13 @@ namespace xDataService.Db /// public abstract class XDbContext : DbContext { + // + #region Properties ... + private readonly string database; private readonly XDataServiceConfiguration config; + private readonly string[] dynamicEntityRegisterarsNames; + private readonly XEntityRegisterarHandlerService dynamicEntityHandlers; + #endregion // #region Entities ... @@ -18,10 +29,13 @@ namespace xDataService.Db #endregion // - // Constructor ... + #region Constructor ... public XDbContext( + string database, DbContextOptions options, - XDataServiceConfiguration config + XDataServiceConfiguration config, + XEntityRegisterarHandlerService dynamicEntityHandlers, + string[] dynamicEntityRegisterarsNames = null ) : base(options) { // @@ -32,11 +46,62 @@ namespace xDataService.Db } catch { } + // 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() + var method = typeof(XDbContext) + .GetMethod(nameof(XDbContext.Set), Type.EmptyTypes) + .MakeGenericMethod(entityType); + if (!method.IsNull()) + { + method.Invoke(this, null); + } + } + catch { } + } + } + } + #endregion + + /// + /// Extract Current Database Configuration ... + /// + /// + 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) { // @@ -46,15 +111,37 @@ namespace xDataService.Db // Automatically apply all IEntityTypeConfiguration in this 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, // we have to pass this to Building Identity Models ... base.OnModelCreating(modelBuilder); } - // - public abstract void OnXModelCreating(ModelBuilder modelBuilder); - // // Prepare Configuring ... protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) @@ -87,8 +174,24 @@ namespace xDataService.Db // base.OnConfiguring(optionsBuilder); } + #endregion + + // + #region Abstracts ... + // + public abstract void OnXModelCreating(ModelBuilder modelBuilder); // public abstract void OnXConfiguring(DbContextOptionsBuilder optionsBuilder); + #endregion + + // + #region Dynamic DbSet(s) ... + public override DbSet Set() + where TEntity : class + { + return base.Set(); + } + #endregion } } \ No newline at end of file diff --git a/Extensions/EntityExtensions.cs b/Extensions/EntityExtensions.cs index d566044..3e40e0c 100644 --- a/Extensions/EntityExtensions.cs +++ b/Extensions/EntityExtensions.cs @@ -63,5 +63,38 @@ namespace xDataService.Extensions { return result; } + /// + /// Check a Type is XBaseEntity or not ... + /// + /// + /// + 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; + } } } \ No newline at end of file diff --git a/Helpers/XDataServiceHelper.cs b/Helpers/XDataServiceHelper.cs index 0be05e5..a472f5d 100644 --- a/Helpers/XDataServiceHelper.cs +++ b/Helpers/XDataServiceHelper.cs @@ -1,4 +1,7 @@ using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; using GraphQL; using GraphQL.Server; using GraphQL.Server.Ui.Playground; @@ -1245,6 +1248,41 @@ namespace xDataService.Helpers } #endregion + // + #region Entity Assembly Detector ... + /// + /// Extract Assemblies which Contains Entity ... + /// + /// + public static List ExtractEntityAssemblies() + { + // + var result = new List(); + + // + 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 ... /// diff --git a/Interfaces/IXEntityRegisterar.cs b/Interfaces/IXEntityRegisterar.cs new file mode 100644 index 0000000..9c4da06 --- /dev/null +++ b/Interfaces/IXEntityRegisterar.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore; +using xModels.Base; + +namespace xDataService.Interfaces +{ + /// + /// an Interface to Intruduce Entities for Dynamic Registration ... + /// + public interface IXEntityRegisterar + { + /// + /// Database Name ... + /// + string Name { get; } + + /// + /// Returns all entity types this plugin wants registered in the DbContext + /// + IEnumerable GetEntityTypes(); + + /// + /// Optional: Apply additional per-entity configuration + /// + void ConfigureEntities(ModelBuilder modelBuilder); + + /// + /// Add XDataService Based Entity to List ... + /// + /// + /// + void AddEntity() + where TEntity : XBaseEntity; + } +} \ No newline at end of file diff --git a/Providers/XBaseEntityRegisterar.cs b/Providers/XBaseEntityRegisterar.cs new file mode 100644 index 0000000..afb40ed --- /dev/null +++ b/Providers/XBaseEntityRegisterar.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore; +using xDataService.Interfaces; +using xModels.Base; + +namespace xDataService.Providers +{ + /// + /// Base Entity Registerar ... + /// + public abstract class XBaseEntityRegisterar : IXEntityRegisterar + { + /// + /// Private Entity Types Collection ... + /// + private readonly List entityTypes = []; + + /// + /// Name ... + /// + public string Name { get; } + + protected XBaseEntityRegisterar(string name) + { + Name = name; + } + + /// + /// Add Specified Entity to Collection ... + /// + /// + /// + public void AddEntity() + where TEntity : XBaseEntity + { + entityTypes.Add(typeof(TEntity)); + } + + /// + /// Retrieve Registered Entity Types ... + /// + /// + public IEnumerable GetEntityTypes() + { + return entityTypes; + } + + /// + /// Configure Dynamic Entities ... + /// + /// + public virtual void ConfigureEntities(ModelBuilder modelBuilder) + { } + } +} \ No newline at end of file diff --git a/Providers/XEntityRegisterarHandlerService.cs b/Providers/XEntityRegisterarHandlerService.cs new file mode 100644 index 0000000..5888a8d --- /dev/null +++ b/Providers/XEntityRegisterarHandlerService.cs @@ -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 + { + /// + /// a Collection for Extract Registerars Services ... + /// + private readonly List registerars = []; + + /// + /// a Collection for Holding all Entity Types ... + /// + public IReadOnlyList AllEntityTypes { get; private set; } + + /// + /// Retrieve Specified Registerars Entity Types ... + /// + /// + /// + public IReadOnlyList GetEntityTypes(string[] names = null) + { + // + var items = new List(); + + // + if (!names.IsNull() && names.HasChild()) + { + // + items = [.. registerars + .Where(r => names.Contains(r.Name)) + .SelectMany(r => r.GetEntityTypes())]; + } + + // + return items.AsReadOnly(); + } + + /// + /// Load all Dynamically Registered Entities ... + /// + /// + public void HandleRegisteredEntities() + { + // + var entityTypes = new List(); + // 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(); + } + } + + /// + /// Configure all Dynamically Registered Entities ... + /// + /// + 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); + } + } + } +} \ No newline at end of file diff --git a/README.md b/README.md index b191808..f10132b 100644 --- a/README.md +++ b/README.md @@ -94,8 +94,18 @@ public class XSaherElmDbContext : XDbContext { public XSaherElmDbContext( DbContextOptions options, - XDataServiceConfiguration config - ) : base(options, config) { } + XDataServiceConfiguration 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) @@ -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 +{ + public override void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Strings"); + } +} + +public class XStringEntityRegisterar : XBaseEntityRegisterar, IXEntityRegisterar +{ + public XStringEntityRegisterar() + : base(nameof(XStringEntityRegisterar)) + { + // + // Add XString Entity ... + AddEntity(); + } + + /// + /// Configure Entities ... + /// + /// + public override void ConfigureEntities(ModelBuilder modelBuilder) + { + base.ConfigureEntities(modelBuilder); + } +} +``` + +by this mechanism, provided Entity Dynamically Registered on Application DbContext automatically. + #### Repository Pattern by this feature you can manipulate specified Entity using Repository Pttern implementation.