Aplikacja, której dotyczy problem stworzona została z wykorzystaniem technologi: Asp.net v4.5, MVC, Oracle 11g, Entity Framework 6 – database first.
Jest to aplikacja intranetowa, której użytkowanie rozpoczyna podstrona z listą schematów bazy danych znajdujących się na serwerze Oracle.
Parametry połączenia określone są w zmiennych aplikacji.
Użytkownik wybiera interesujący go schemat bazy danych i kontynuuje pracę w kontekście wybranego przez siebie schematu.
Wszystkie schematy mają taką samą strukturę/model, różnią się jedynie danymi.
Chciałbym dowiedzieć się jak poradzić sobie z problemem dynamicznych parametrów połączeniowych w Entity Framework 6.
Dotychczasowe rozwiązanie umożliwia oczekiwane działanie, jednakże jest ono związane z dużymi niedogodnościami, co spróbuję zobrazować na przykładzie.
Użytkownik A – wybiera z listy, schemat bazy danych X i rozpoczyna dalsze korzystanie z aplikacji.
W tym momencie użytkownik B wybiera z listy schemat Y.
Powoduje to, że użytkownikowi A przełącza się schemat bazy danych na schemat Y.
Wobec tego zaproponowane przeze mnie rozwiązanie umożliwia dynamiczne definiowanie połączenia z bazą danych ale jedynie w kontekście całej aplikacji a nie pojedynczego użytkownika aplikacji - co jest błędem.
Definicja dynamicznych parametrów połączeniowych nie stanowi problemu.
Kłopotem jest informacja o schemacie bazy danych wykorzystywanej przez model EF, która znajduje się w plikach dll generowanych przez Entity Framework.
Kluczowe fragmenty mojego rozwiązania:
Kod wygenerowany przez EF6, wraz ze zmianami wprowadzonymi w celu umożliwienia pracy w zmiennym środowisku bazodanowym:
public partial class Entities : DbContext
{
public Entities()
: base("metadata=res://*/Models.Model1.csdl|" + @Assembly.GetExecutingAssembly().Location.Replace(@"WebApplication10.dll", string.Empty) + "\\Models.Model1a.ssdl|res://*/Models.Model1.msl;provider=Oracle.ManagedDataAccess.Client ;provider connection string=';Data Source=" + Models.ReportConnectionModel.hostname + ":" + Models.ReportConnectionModel.port + "/" + Models.ReportConnectionModel.sid + ";password=" + Models.ReportConnectionModel.password + ";PERSIST SECURITY INFO=True;user id=" + Models.ReportConnectionModel.username + "';")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public virtual DbSet<GROUND_UNIT> GROUND_UNIT { get; set; }
public virtual DbSet<TACT_UNIT_PROTO> TACT_UNIT_PROTO { get; set; }
public virtual DbSet<FARP> FARP { get; set; }
public virtual DbSet<AIRBASE> AIRBASE { get; set; }
public virtual DbSet<HIGH_RESOLUTION_UNIT> HIGH_RESOLUTION_UNIT { get; set; }
public virtual DbSet<NAVAL_UNIT> NAVAL_UNIT { get; set; }
public virtual DbSet<SQUADRON> SQUADRON { get; set; }
public virtual DbSet<SUPPORT_UNIT> SUPPORT_UNIT { get; set; }
public virtual DbSet<HUP_CS> HUP_CS { get; set; }
public virtual DbSet<HUP_SC> HUP_SC { get; set; }
public virtual DbSet<HUP_TARGET_TYPE> HUP_TARGET_TYPE { get; set; }
public virtual DbSet<SUP_CS> SUP_CS { get; set; }
public virtual DbSet<SUP_POT> SUP_POT { get; set; }
public virtual DbSet<SUP_SC> SUP_SC { get; set; }
public virtual DbSet<TUP_CS> TUP_CS { get; set; }
public virtual DbSet<TUP_DIR> TUP_DIR { get; set; }
public virtual DbSet<TUP_POT> TUP_POT { get; set; }
public virtual DbSet<TUP_SC> TUP_SC { get; set; }
public virtual DbSet<HIGHRES_UNIT_PROTOTYPE> HIGHRES_UNIT_PROTOTYPE { get; set; }
public virtual DbSet<SHIP_UNIT_PROTO> SHIP_UNIT_PROTO { get; set; }
public virtual DbSet<FACTION_COUNTRY> FACTION_COUNTRY { get; set; }
public virtual DbSet<FORCE_SIDE> FORCE_SIDE { get; set; }
}
}
Klasa udostępniająca funkcję przerabiającą pliki dll zawierające metadane modelu, a konkretniej, wpis dotyczący schematu bazy danych.
public class EntitySchemaConfigurator
{
public static void Create(string schema)
{
string[] args = { @Assembly.GetExecutingAssembly().Location.Replace(@"WebApplication10.dll", string.Empty), Assembly.GetExecutingAssembly().Location };
if (args.Length > 0)
{
string folder = args.First();
string dll = string.Empty;
if (System.IO.Directory.Exists(folder))
{
DirectoryInfo info = new DirectoryInfo(folder);
if (args.Length > 1)
{
dll = args[1];
Assembly assembly = Assembly.LoadFile(dll);
string[] resources =
assembly.GetManifestResourceNames();
string content = string.Empty;
foreach (var item in resources)
{
if (item.ToUpper().EndsWith("SSDL"))
{
using (Stream itemStream =
assembly.
GetManifestResourceStream(item))
{
using (StreamReader reader =
new StreamReader(itemStream))
{
content = reader.ReadToEnd();
reader.Close();
}
itemStream.Close();
}
File.WriteAllText(
string.Concat(info.FullName,
@"\",
item), content);
}
}
}
FileInfo[] files = info.GetFiles("*.ssdl",
System.IO.SearchOption.TopDirectoryOnly);
if (files != null &&
files.Length > 0 &&
files[0] != null)
{
foreach (var item in files)
{
string content = File
.ReadAllText(item.FullName);
content = content
.Replace(@"COEE_15",
@schema);
File.WriteAllText(item.FullName.Replace(@"Models.Model1.ssdl",
@"Models.Model1a.ssdl"), content);
}
}
}
else
{
Console.WriteLine("Target folder is invalid");
}
}
else
{
Console.WriteLine(@"Target folder is required
as command line parameter");
}
}
}
Przykład korzystania z EF:
private Entities db = new Entities();
EntitySchemaConfigurator.Create(ReportConnectionModel.username);
Proszę o radę jak poradzić sobie z tym problemem, najlepiej bez przechodzenia z trybu database first na code first?