I´m stuck in a ASP.NET application for a small web site. I´m using EF5 with Oracle´s .Net provider using the database first approach.
All works nicely, except that I need to switch from production to development environment at runtime without touching any configuration file (like Web.config file).
In Oracle, as I want to connect to a different database, I need to call a different connection string. The connection strings I have currently are (EZ notation):
MyProductionUserName/MyProductionPassword@192.168.0.200/XE
- For the production environment
MyDevUserName/MyDevPassword@192.168.0.200/XE
- For the development environment.
When using database first, the EF tool builds me the following model:
namespace DataAccessOracleModel.Model
{
using System;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
public partial class Entities : DbContext
{
public Entities()
: base("name=Entities")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public DbSet<TABLE1> TABLE1 { get; set; }
public DbSet<TABLE2> TABLE2 { get; set; }
public DbSet<TABLE3> TABLE3 { get; set; }
.
.
.
public DbSet<TABLEn> TABLEn { get; set; }
}
}
Where TABLE1, TABLE2, TABLE3, TABLEn
are all my database tables.
I can´t touch the above code as It´s generated automatically.
So, here is what I did already:
Looking at this link and at an answer to my own post here I came out with this classes:
public static class ConnectionHelper
{
/// <summary>
/// Builds the connection string for Entity framework on Oracle.
/// </summary>
/// <returns></returns>
public static EntityConnection BuildOracleConnection(OracleConnectionParams connectionParams)
{
string oraConnectionString = "DATA SOURCE=192.168.0.200:1521/XE;PERSIST SECURITY INFO=True;USER ID=MyDevUsername;PASSWORD=MyDevPassword"; /// Test connection string for the development environment
var entityBuilder = new EntityConnectionStringBuilder
{
Provider = "Oracle.ManagedDataAccess.Client", // or System.Data.EntityClient?
ProviderConnectionString = oraConnectionString,
Metadata = string.Format(@"res://*/{0}.csdl|
res://*/{0}.ssdl|
res://*/{0}.msl", connectionParams.ModelName)
};
return CreateConnection(connectionParams.UserId, entityBuilder, connectionParams.ModelName);
}
/// <summary>
/// Creates the EntityConnection, based on new schema & existing connectionString. Use reflection to get the model and them
/// change the internal files.
/// </summary>
/// <param name="schemaName">Name of the schema.</param>
/// <param name="connectionBuilder">The string builder for the connection string</param>
/// <param name="modelName">Name of the model.</param>
/// <returns></returns>
public static EntityConnection CreateConnection(string schemaName, EntityConnectionStringBuilder connectionBuilder, string modelName)
{
Func<string, Stream> generateStream =
extension => Assembly.GetExecutingAssembly().GetManifestResourceStream(string.Concat(modelName, extension));
Action<IEnumerable<Stream>> disposeCollection = streams =>
{
if (streams == null)
return;
foreach (var stream in streams.Where(stream => stream != null))
stream.Dispose();
};
var conceptualReader = generateStream(".csdl");
var mappingReader = generateStream(".msl");
var storageReader = generateStream(".ssdl");
///
/// Get the EF file pointers
///
if (conceptualReader == null || mappingReader == null || storageReader == null)
{
disposeCollection(new[] { conceptualReader, mappingReader, storageReader });
return null;
}
///
/// Change the storage reader
///
var storageXml = XElement.Load(storageReader);
foreach (var entitySet in storageXml.Descendants())
{
var schemaAttribute = entitySet.Attributes("Schema").FirstOrDefault();
if (schemaAttribute != null)
schemaAttribute.SetValue(schemaName);
}
storageXml.CreateReader();
var workspace = new MetadataWorkspace();
var storageCollection = new StoreItemCollection(new[] { storageXml.CreateReader() });
var conceptualCollection = new EdmItemCollection(new[] { XmlReader.Create(conceptualReader) });
var mappingCollection = new StorageMappingItemCollection(conceptualCollection,
storageCollection,
new[] { XmlReader.Create(mappingReader) });
workspace.RegisterItemCollection(conceptualCollection);
workspace.RegisterItemCollection(storageCollection);
workspace.RegisterItemCollection(mappingCollection);
var connection = DbProviderFactories.GetFactory(connectionBuilder.Provider).CreateConnection();
if (connection == null)
{
disposeCollection(new[] { conceptualReader, mappingReader, storageReader });
return null;
}
connection.ConnectionString = connectionBuilder.ProviderConnectionString;
return new EntityConnection(workspace, connection);
}
}
}
And
/// <summary>
/// Define parameters used for Oracle database connection. Used to build the connection string at runtime.
/// </summary>
public class OracleConnectionParams
{
/// <summary>
/// Name of provider
/// </summary>
public string Host { get; set; }
/// <summary>
/// Database server name
/// </summary>
public string Port { get; set; }
/// <summary>
/// Database name
/// </summary>
public string Service { get; set; }
/// <summary>
/// Model name
/// </summary>
public string UserId { get; set; }
/// <summary>
/// Model name
/// </summary>
public string Password { get; set; }
/// <summary>
/// Model name
/// </summary>
public string ModelName { get; set; }
}
But I´m stuck as I came up with 2 different EntityConnections (Entities
and the one returned by CreateConnection
).. Questions:
I can´t create another Entities, as it was created by the EF tool. I don´t want to touch the EF original code as our approach in development is to change the database and then generate a new EF class (this is done several times a day).
AS I´m using EF5, I don´t have a constructor to change the connection string, as desired.
I need to make work a simple console application like that (non functional):
namespace TestApp
{
class Program
{
static void Main(string[] args)
{
SomeFunctionToSetDbContext("192.168.0.200", "1521", "XE", "MyDevUserId", "MyDevPassword");
var testtableitens = TestTable1.List();
foreach (var item in testtableitens)
Console.WriteLine(item);
}
}
}
But as I said I don´t know where to go to "join" both ideas... Help very appreciated...