2017-10-31 10 views
1

Ich habe mit meinem aktuellen Projekt ein bisschen eine Mauer erreicht.Ist es möglich, eine Datenbank zu verwenden, um den ConnectionString eines anderen dynamisch zu definieren?

Ich habe drei normalisierte Datenbanken, von denen ich eine dynamisch verbinden möchte; diese sind:

  • Konten: Für den sicheren Kontoinformationen, Spanning Kunden
  • Konfiguration: Für unsere Kunden
  • Client-Verwaltung: Welche für jeden unserer Kunden & Atom werden alle ihre Informationen halten

Ich brauche Daten in der "Configuration" -Datenbank zu ändern, die ConnectionString, die verwendet werden, um eine Verbindung mit der "Client" -Datenbank verwendet werden, aber das ist das Bit, ich bin fest.

Bisher habe ich die Entitäten aus den Datenbanken in ein Projekt generiert, indem ich EntityFrameWorkCore-Tools angeschlossen habe und den "Scaffold-DbContext" -Befehl & kann einfache Look-ups um sicherzustellen, dass die Datenbanken mit OK verbunden sind .

Jetzt versuche ich die Datenbanken zu registrieren, indem sie an die ServiceCollection Zugabe, ich habe sie in der StartUp Klasse wie folgt hinzugefügt:

// This method gets called by the runtime. Use this method to add services to the container. 
    public void ConfigureServices(IServiceCollection services) 
    { 
     services.AddMvc(); 
     services.Configure<MvcOptions>(options => 
     { 
      options.Filters.Add(new RequireHttpsAttribute()); 
     }); 
     services.AddDbContext<Accounts>(options => 
      options.UseSqlServer(Configuration.GetConnectionString("Accounts")) 
     ); 
     services.AddDbContext<Support>(options => 
      options.UseSqlServer(Configuration.GetConnectionString("Configuration")) 
     ); 

     // Erm? 
     SelectClientDatabase(services); 
    } 

Offensichtlich ist die nächste Stufe ist in einzutauchen die "Configuration" -Datenbank, also habe ich versucht, das in "SelectClientDatabase()" enthaltene zu behalten, das nur die IServiceCollection als Parameter nimmt und für alle Absichten und Zwecke für jetzt leer ist. In den letzten Tagen habe ich einige ausgezeichnete Berichte über EFC gefunden und ich bin gerade dabei, eine CustomConfigurationProvider als mögliche Route zu erkunden, aber ich muss zugeben, dass ich beim Start in ASP.Net Core etwas verloren bin.

Ist es möglich, den frisch hinzugefügten DbContext innerhalb der ConfigureServices Methode einhängen? Oder kann/muss ich diese Datenbank zu einem späteren Zeitpunkt der Servicesammlung hinzufügen?

Danke!

Edit 1:

Ich habe gerade this Post, die erwähnt, dass ein DbContext nicht innerhalb OnConfiguring verwendet werden, da es nach wie vor ist konfiguriert ist; Das macht sehr viel Sinn. Ich frage mich nun, ob ich alle drei DbContexte in eine benutzerdefinierte Middleware packen kann, um die Verbindungen zu kapseln, zu konfigurieren und verfügbar zu machen; etwas Neues für die Forschung.

Edit 2: Ich habe eine andere Stelle gefunden, die beschreibt, wie zu "Inject DbContext when database name is only know when the controller action is called", die wie ein vielversprechender Ausgangspunkt sieht; Dies ist jedoch für eine ältere Version von ASP.Net Core, nach https://docs.microsoft.com "DbContextFactory" wurde umbenannt, so arbeite ich jetzt, um das Beispiel in eine mögliche Lösung zu aktualisieren.

Antwort

1

Also habe ich endlich alles ausgearbeitet.Ich gab die Fabrikidee auf, weil ich mich nicht wohl fühle mit , um Zeit damit zu verbringen, es auszuarbeiten & Ich rase kopfüber in eine Frist, so dass die schnelleren Optionen jetzt die besseren sind und ich immer Zeit finde um zu refaktorieren der Code später (lol).

Meine appsettings.json Datei enthält derzeit nur die folgenden (das entsprechende Bit von appsettings.Developments.json identisch ist):

{ 
    "ConnectionStrings" : { 
     "Accounts": "Server=testserver;Database=Accounts;Trusted_Connection=True;", 
     "Client": "Server=testserver;Database={CLIENT_DB};Trusted_Connection=True;", 
     "Configuration": "Server=testserver;Database=Configuration;Trusted_Connection=True;" 
    }, 
    "Logging": { 
     "IncludeScopes": false, 
     "Debug": { 
      "LogLevel": { 
       "Default": "Warning" 
      } 
     }, 
     "Console": { 
      "LogLevel": { 
       "Default": "Warning" 
      } 
     } 
    } 
} 

ich entschieden habe, die beiden statischen Datenbanken in den ConfigureServices Methode von StartUp, zu konfigurieren Diese sollten konfiguriert und einsatzbereit sein, wenn die Anwendung dazu übergeht, etwas zu tun. Der Code dort ist nett & sauber.

public void ConfigureServices(IServiceCollection services) 
{ 
    services.AddMvc(); 
    services.Configure<MvcOptions>(options => 
    { 
     //options.Filters.Add(new RequireHttpsAttribute()); 
    }); 
    services.AddDbContext<AccountsContext>(options => 
     options.UseSqlServer(Configuration.GetConnectionString("Accounts")) 
    ); 
    services.AddDbContext<ConfigContext>(options => 
     options.UseSqlServer(Configuration.GetConnectionString("Configuration")) 
    ); 
    services.AddSingleton(
     Configuration.GetSection("ConnectionStrings").Get<ConnectionStrings>() 
    ); 
} 

Es stellt sich heraus, dass man die Qual der Wahl werden kann, wie in der appsettings.json gesetzt über den Zugriff auf die Konfigurationsoptionen zu gehen, ich bin derzeit zu arbeiten versuchen, wie ich habe es geschafft, es zu dem wechseln zu erhalten Release-Version statt der Entwicklungsversion. Ich kann nicht denken, was ich getan habe, um das zu schalten ...

Um die Platzhalter-Konfigurationseinstellung zu erhalten, verwende ich einen Singleton, um den Zeichenfolgenwert zu halten. Dies taucht nur in die Gruppe "ConnectionStrings" ein und stopft diesen Json in das Objekt "ClientConnection" (siehe unten).

services.AddSingleton(
     Configuration.GetSection("ConnectionStrings").Get<ClientConnection>() 
    ); 

Welche der folgenden Struktur auffüllt (die ich gerade in einer eigenen Datei aus bunged haben):

[DataContract(Name = "ConnectionStrings")] 
public class ClientConnection 
{ 
    [DataMember] 
    public string Client { get; set; } 
} 

nur ich diese die Verbindungszeichenfolge für die dynamisch zugewiesene Datenbank halten wollen, so ist es nicht zu jazzig. Der "Client" DataMember ist, was den richtigen Schlüssel in der JSON auswählt, wenn ich einen anderen benannten Knoten in der JSON wollte, würde ich ihn zum Beispiel in "Konten" umbenennen.

Noch ein paar Optionen, die ich getestet, bevor sie auf die Option Singleton Absetzen, sind:

services.Configure<ConnectionStrings>(Configuration.GetSection("ConnectionStrings")); 

und

var derp = Configuration.GetSection("ConnectionStrings:Client"); 

Was ich diskontiert, aber es lohnt sich andere Optionen zu kennen (sie werden wahrscheinlich nützlich sein, um später andere Konfigurationsoptionen zu laden.

Ich bin nicht scharf darauf, wie die Controller-Abhängigkeiten in ASP.Net Core 2 arbeiten, ich hatte gehofft, dass ich sie in einem BaseController verstecken könnte, damit sie nicht in jedem einzelnen Controller angegeben werden müssten Ich knock out, aber ich habe keinen Weg gefunden, dies zu tun ja. Die Abhängigkeiten, die in den Controllern benötigt werden, werden im Konstruktor übergeben, diese haben mich für eine Weile irritiert, weil sie automatisch magisch injiziert werden.

My Basecontroller eingerichtet ist, wie folgt:

using Microsoft.AspNetCore.Mvc; 
using Microsoft.AspNetCore.Mvc.Filters; 
using Microsoft.EntityFrameworkCore.Internal; 
using ServiceLayer.Entities; 
using System; 
using System.Collections.Generic; 
using System.Linq; 

namespace ServiceLayer.Controllers 
{ 
    public class BaseController : Controller 
    { 
     private readonly ClientConnection connectionStrings; 
     private readonly AccountsContext accountsContext; 
     private readonly ConfigurationContext configContext; 
     public ClientTemplateContext clientContext; 

     private DbContextServices DbContextServices { get; set; } 

     public BaseController(AccountsContext accounts, ConfigContext config, ClientConnection connection) : base() 
     { 
      accountsContext = accounts; 
      configContext = config; 
      connectionStrings = connection; 
     } 

     public override void OnActionExecuting(ActionExecutingContext context) 
     { 
      base.OnActionExecuting(context); 
     } 
    } 
} 

Der Code für die Auswahl der Datenbank geht dann in den "OnActionExecuting()" Methode; dies erwies sich auch ein wenig nervig sein, um sicherzustellen versuchen, dass die richtig, ich ließ sich in dem Ende eingesetzt wurde:

using System; 
using System.Collections.Generic; 
using System.Linq; 

namespace ServiceLayer.Controllers 
{ 
    public class BaseController : Controller 
    { 
     private readonly ClientConnection connectionStrings; 
     private readonly AccountsContext accountsContext; 
     private readonly ConfigurationContext configContext; 
     public ClientTemplateContext clientContext; 

     private DbContextServices DbContextServices { get; set; } 

     public BaseController(AccountsContext accounts, ConfigurationContext config, ClientConnection connection) : base() 
     { 
      accountsContext = accounts; 
      configContext= config; 
      connectionStrings = connection; 
     } 

     public override void OnActionExecuting(ActionExecutingContext context) 
     { 
      // Temporary selection identifier for the company 
      Guid cack = Guid.Parse("827F79C5-821B-4819-ABB8-819CBD76372F"); 

      var dataSource = (from c in configContext.Clients 
           where c.Cack == cack 
           join ds in configContext.DataStorage on c.CompanyId equals ds.CompanyId 
           select ds.Name).FirstOrDefault(); 

      // Proto-connection string 
      var cs = connectionStrings.Client; 

      if (!string.IsNullOrEmpty(cs) && !string.IsNullOrEmpty(dataSource)) 
      { 
       // Populated ConnectionString 
       cs = cs.Replace("{CLIENT_DB}", dataSource); 

       clientContext = new ClientTemplateContext().Initialise(cs); 
      } 

      base.OnActionExecuting(context); 
     } 
    } 
} 

new ClientTemplateContext().Initialise() ein bisschen chaotisch, aber ich werde es aufzuräumen, wenn Ich refaktoriere alles andere. "ClientTemplateContext" ist die generierte Klasse, die alle generierten Entitäten miteinander verbindet. Ich habe der Klasse den folgenden Code hinzugefügt (ich habe versucht, sie in eine separate Datei zu schreiben, aber das funktioniert nicht, also bleibt sie dort für den Moment) ...

public ClientTemplateContext() {} 

private ClientTemplateContext(DbContextOptions options) : base(options) {} 

public ClientTemplateContext Initialise(string connectionString) 
{ 
    return new ClientTemplateContext().CreateDbContext(new[] { connectionString }); 
} 

public ClientTemplateContext CreateDbContext(string[] args) 
{ 
    if (args == null && !args.Any()) 
    { 
     //Log error. 
     return null; 
    } 

    var optionsBuilder = new DbContextOptionsBuilder<ClientTemplateContext>(); 

    optionsBuilder.UseSqlServer(args[0]); 

    return new ClientTemplateContext(optionsBuilder.Options); 
} 

ich habe auch using Microsoft.EntityFrameworkCore.Design; und hinzugefügt, um die IDesignTimeDbContextFactory<ClientTemplateContext> Schnittstelle zur Klasse. So sieht es wie folgt aus:

public partial class ClientTemplateContext : DbContext, IDesignTimeDbContextFactory<ClientTemplateContext> 

Hier wird die CreateDbContext(string[] args) von & kommt es uns eine neue Instanz einer abgeleiteten Kontext zur Entwurfszeit erstellen können.

Schließlich ist der Code für meinen Test-Controller wie folgt:

using Microsoft.AspNetCore.Mvc; 
using ServiceLayer.Entities; 
using System.Collections.Generic; 
using System.Linq; 

namespace ServiceLayer.Controllers 
{ 
    [Route("api/[controller]")] 
    public class ValuesController : BaseController 
    { 
     public ValuesController(
      AccountsContext accounts, 
      ConfigurationContext config, 
      ClientConnection connection 
     ) : base(accounts, config, connection) {} 

     // GET api/values 
     [HttpGet] 
     public IEnumerable<string> Get() 
     { 
      var herp = (from c in clientContext.Usage 
         select c).FirstOrDefault(); 

      return new string[] { 
       herp.TimeStamp.ToString(), 
       herp.Request, 
       herp.Payload 
      }; 
     } 
    } 
} 

Dieses erfolgreich liefert Daten aus der Datenbank dynamisch aus der Datasource Tabelle in der DatenbankKonfiguration ausgewählt!

["01/01/2017 00:00:00","derp","derp"] 

Wenn jemand Verbesserungen meiner Lösung vorschlagen kann ich sie gerne sehen würde, ist meine Lösung püriert zusammen, wie es steht & ich es so bald Refactoring wollen, wie ich ich bin kompetent genug fühlen, dies zu tun .

Verwandte Themen