While trying to implement EF Migrations in my project I am stuck at one place.
EF Code First MigrateDatabaseToLatestVersion accepts connection string Name from config.
In my case database name get known at Runtime (User selects it from dropdown).
Just the way DbContext either accepts, ConnectionString or connectionString Name in it's constructor, "MigrateDatabaseToLatestVersion" does not accept the same
System.Data.Entity.Database.SetInitializer
(new MigrateDatabaseToLatestVersion<SrcDbContext, SRC.DomainModel.ORMapping.Migrations.Configuration>(connString));
Is there any other way to achieve this?
Thank you all. I did checkout the EF code from codeplex, and inherited my own class after understanding their source code. Here is the solution which I opted :-
public class MigrateDbToLatestInitializerConnString<TContext, TMigrationsConfiguration> : IDatabaseInitializer<TContext>
where TContext : DbContext
where TMigrationsConfiguration : DbMigrationsConfiguration<TContext>, new()
{
private readonly DbMigrationsConfiguration config;
/// <summary>
/// Initializes a new instance of the MigrateDatabaseToLatestVersion class.
/// </summary>
public MigrateDbToLatestInitializerConnString()
{
config = new TMigrationsConfiguration();
}
/// <summary>
/// Initializes a new instance of the MigrateDatabaseToLatestVersion class that will
/// use a specific connection string from the configuration file to connect to
/// the database to perform the migration.
/// </summary>
/// <param name="connectionString"> connection string to use for migration. </param>
public MigrateDbToLatestInitializerConnString(string connectionString)
{
config = new TMigrationsConfiguration
{
TargetDatabase = new DbConnectionInfo(connectionString, "System.Data.SqlClient")
};
}
public void InitializeDatabase(TContext context)
{
if (context == null)
{
throw new ArgumentException("Context passed to InitializeDatabase can not be null");
}
var migrator = new DbMigrator(config);
migrator.Update();
}
}
public static class DatabaseHelper
{
/// <summary>
/// This method will create data base for given parameters supplied by caller.
/// </summary>
/// <param name="serverName">Name of the server where database has to be created</param>
/// <param name="databaseName">Name of database</param>
/// <param name="userName">SQL user name</param>
/// <param name="password">SQL password</param>
/// <returns>void</returns>
public static bool CreateDb(string serverName, string databaseName, string userName, string password)
{
bool integratedSecurity = !(!string.IsNullOrEmpty(userName) || !string.IsNullOrEmpty(password));
var builder = new System.Data.SqlClient.SqlConnectionStringBuilder
{
DataSource = serverName,
UserID = userName,
Password = password,
InitialCatalog = databaseName,
IntegratedSecurity = integratedSecurity,
};
var db = new SrcDbContext(builder.ConnectionString);
var dbInitializer = new MigrateDbToLatestInitializerConnString<SrcDbContext, SRC.DomainModel.ORMapping.Migrations.Configuration>(builder.ConnectionString);
//following uses strategy to "CreateIfNotExist<>"
dbInitializer.InitializeDatabase(db);
return true;
}
}
You can make the MigrateDatabaseToLatestVersion initializer to use the connection string that was used by the context that triggered the migration in the first place.
This is done by passing useSuppliedContext: true to the MigrateDatabaseToLatestVersion constructor as described in the docs. In your case:
Database.SetInitializer(new MigrateDatabaseToLatestVersion<SrcDbContext, SRC.DomainModel.ORMapping.Migrations.Configuration>(useSuppliedContext: true));
What context is this running under? Website or Desktop App?
Under website, doing that is not a good idea. The database initializing strategy set against the type of context. So different connection strings with same type of context will override each other's init strategy.
If Desktop App, maybe include an extra utility to switch between database?
Either way I don't recommend doing this, but if you really want to do what you mentioned, it looks like you have to hack it.
using (var context = new DbContext("<Your connection string right in here>"))
{
var constructors = typeof (DbMigrator).GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic);
var hackedDbMigrator = constructors[0].Invoke(new object[] { new Configuration(), context }) as DbMigrator;
hackedDbMigrator.Update();
}
The is a issue with Migrations call the DbContext dervied class with parameter. Once this is solved it should work. see here for a sample solution.
EntityFramework code-first custom connection string and migrations
Related
I need to use same entity DatabaseContext constructor for different connections (between sqlite and mysql). The connection string can changes (for both connections), so i can't use definied connectionString in App.config (or I need to change it somehow).
Two DatabaseContexts
UPDATE:
Here's code from pic above where I use different entity DatabaseContext constructors. In comment of first constructor shown unworking code how I want to use it (different database connections in same constructor).
/// <summary>
/// Sqlite database connection
/// </summary>
/// <param name="connectionString"></param>
public DatabaseContext(string connectionString) : base(new SQLiteConnection() {ConnectionString = connectionString}, true)
{
//base.Configuration = new MySqlConnection();
//base.Configuration = new SQLiteConnection() {ConnectionString = connectionString}, true);
}
/// <summary>
/// MySql database connection
/// </summary>
/// <param name="connectionString"></param>
/// <param name="mock">Identifies mysql connect</param>
public DatabaseContext(string connectionString, bool mock) : base(new MySqlConnection() {ConnectionString = connectionString}, true)
{
}
I've read the Microsoft documentation of fundamentals for Options and Configuration, but still can't find the right way to extract configuration into an object while validating data annotations.
One approach I tried in Startup.ConfigureServices
services.AddOptions<EmailConfig>().Bind(Configuration.GetSection("Email")).ValidateDataAnnotations();
This "should" allow accessing the configuration by adding this in the class constructor: (IOptions<EmailConfig> emailConfig)
However it's not working.
Another approach is to add (IConfiguration configuration) to the constructor, but this doesn't allow me to call ValidateDataAnnotations.
configuration.GetSection("Email").Get<EmailConfig>();
First question: does the responsibility to bind and validate the configuration belong to the Startup class or to the class using it? If it's used by several classes I'd say it belongs to Startup; and the class could be used in another project with different configuration layout.
Second question: what is the correct syntax to bind and validate the configuration so it can be accessed from the class?
Third question: if I'm validating through data annotations in Startup, then the class using the configuration simply assumes the configuration is valid and I don't put any re-validation whatsoever?
UPDATE: After gaining more experience and reviewing the structure of all my code, I changed my approach to follow standard patterns.
The following code DOES work... but only validates it when used. This can be registered in a class library and won't throw any errors until the particular service is used.
services.AddOptions<EmailConfig>()
.Bind(configuration.GetSection("Email"))
.ValidateDataAnnotations();
Then, in Configure, I add this to force validation of needed configuration values at startup (CheckNotNull is a custom extension method, what matters is simply that you call IOptions.Value
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app?.ApplicationServices.GetService<IOptions<EmailConfig>>().Value.CheckNotNull("Config: Email");
app?.ApplicationServices.GetService<IOptions<OntraportConfig>>().Value.CheckNotNull("Config: Ontraport");
...
Then in the class using it
public class EmailService(IOptions<EmailConfig> config)
You can try validating the class yourself in start up before adding it to service collection.
Startup
var settings = Configuration.GetSection("Email").Get<EmailConfig>();
//validate
var validationResults = new List<ValidationResult>();
var validationContext = new ValidationContext(settings, serviceProvider: null, items: null);
if (!Validator.TryValidateObject(settings, validationContext, validationResults,
validateAllProperties: true)) {
//...Fail early
//will have the validation results in the list
}
services.AddSingleton(settings);
That way you are not coupled to IOptions and you also allow your code to fail early and you can explicitly inject the dependency where needed.
You could package the validation up into your own extension method like
public static T GetValid<T>(this IConfiguration configuration) {
var obj = configuration.Get<T>();
//validate
Validator.ValidateObject(obj, new ValidationContext(obj), true);
return obj;
}
for calls like
EmailConfig emailSection = Configuration.GetSection("Email").GetValid<EmailConfig>();
services.AddSingleton(emailSection);
Internally, ValidateDataAnnotations is basically doing the same thing.
/// <summary>
/// Validates a specific named options instance (or all when name is null).
/// </summary>
/// <param name="name">The name of the options instance being validated.</param>
/// <param name="options">The options instance.</param>
/// <returns>The <see cref="ValidateOptionsResult"/> result.</returns>
public ValidateOptionsResult Validate(string name, TOptions options)
{
// Null name is used to configure all named options.
if (Name == null || name == Name)
{
var validationResults = new List<ValidationResult>();
if (Validator.TryValidateObject(options,
new ValidationContext(options, serviceProvider: null, items: null),
validationResults,
validateAllProperties: true))
{
return ValidateOptionsResult.Success;
}
return ValidateOptionsResult.Fail(String.Join(Environment.NewLine,
validationResults.Select(r => "DataAnnotation validation failed for members " +
String.Join(", ", r.MemberNames) +
" with the error '" + r.ErrorMessage + "'.")));
}
// Ignored if not validating this instance.
return ValidateOptionsResult.Skip;
}
Source Code
Update from the Future
Newer versions of .NET added more extension methods to simplify this.
Note: Technically these are all from Microsoft.Extensions.XYZ packages released alongside .NET. It's possible that these packages are compatible with earlier versions of .NET as well, but I haven't verified backward-compatibility.
OP's Example
services.AddOptions<EmailConfig>()
.Bind(configuration.GetSection("Email"))
.ValidateDataAnnotations();
Can now be simplified to:
// Requires .NET 5 extensions or greater
services.AddOptions<EmailConfig>()
.BindConfiguration("Email")
.ValidateDataAnnotations();
...and for eager validation at startup (rather than when options are used), we can add a single line:
// Requires .NET 6 extensions or greater
services.AddOptions<EmailConfig>()
.BindConfiguration("Email")
.ValidateDataAnnotations()
.ValidateOnStart();
Source/Credit
I learned about these updates from Andrew Lock's blog post. Credit and thanks go to him: Adding validation to strongly typed configuration objects in .NET 6
There is still no answer as to how ValidateDataAnnotations work, but based on Nkosi's answer, I wrote this class extension to easily run the validation on-demand. Because it's an extension on Object, I put it into a sub-namespace to only enable it when needed.
namespace Websites.Business.Validation {
/// <summary>
/// Provides methods to validate objects based on DataAnnotations.
/// </summary>
public static class ValidationExtensions {
/// <summary>
/// Validates an object based on its DataAnnotations and throws an exception if the object is not valid.
/// </summary>
/// <param name="obj">The object to validate.</param>
public static T ValidateAndThrow<T>(this T obj) {
Validator.ValidateObject(obj, new ValidationContext(obj), true);
return obj;
}
/// <summary>
/// Validates an object based on its DataAnnotations and returns a list of validation errors.
/// </summary>
/// <param name="obj">The object to validate.</param>
/// <returns>A list of validation errors.</returns>
public static ICollection<ValidationResult> Validate<T>(this T obj) {
var Results = new List<ValidationResult>();
var Context = new ValidationContext(obj);
if (!Validator.TryValidateObject(obj, Context, Results, true))
return Results;
return null;
}
}
}
Then in Startup it's quite straightforward
EmailConfig EmailSection = Configuration.GetSection("Email").Get<EmailConfig>().ValidateAndThrow();
services.AddSingleton<EmailConfig>(EmailSection);
Works like a charm; actually works like I'd expect ValidateDataAnnotations to work.
You can also use a method to validate all IOptions in your IOC conainter
private void CheckConfiguration(IApplicationBuilder app, IServiceCollection services)
{
var optionsServiceDescriptors = services.Where(s => s.ServiceType.Name.Contains("IOptionsChangeTokenSource"));
foreach (var service in optionsServiceDescriptors)
{
var genericTypes = service.ServiceType.GenericTypeArguments;
if (genericTypes.Length > 0)
{
var optionsType = genericTypes[0];
var genericOptions = typeof(IOptions<>).MakeGenericType(optionsType);
dynamic instance = app.ApplicationServices.GetService(genericOptions);
var options = instance.Value;
var results = new List<ValidationResult>();
var isValid = Validator.TryValidateObject(options, new ValidationContext(options), results, true);
if (!isValid)
{
var messages = new List<string> { "Configuration issues" };
messages.AddRange(results.Select(r => r.ErrorMessage));
throw new Exception(string.Join("\n", messages));
}
}
}
}
You can find a example here : https://github.com/michelcedric/GetRequiredSectionSample/blob/feature/add-check-configuration/GetRequiredSectionSample/Startup.cs#L73
I've got a wpf application and use the EF4 to communicate with a database.
I'm choosing the connectionstring dynamically, dependent on if a debugger is attach or not:
public static string CustomerConnectionString;
public static string ProjectsConnectionString;
private static void GetConnectionstring()
{
if (Debugger.IsAttached)
{
CustomerConnectionString = ConfigurationManager.ConnectionStrings["kundenEntities"].ConnectionString;
ProjectsConnectionString = ConfigurationManager.ConnectionStrings["projekteEntities"].ConnectionString;
}
else
{
CustomerConnectionString = ConfigurationManager.ConnectionStrings["kundenEntitiesRelease"].ConnectionString;
ProjectsConnectionString = ConfigurationManager.ConnectionStrings["projekteEntitiesRelease"].ConnectionString;
}
}
I'm doing this, to ensure not to accidently do stuff in the productive database.
An entity object is created on that way then:
using (ProjectEntities proj = new ProjectEntities(App.ProjectsConnectionString))
{
I'm proper code
}
Now I want to ensure, that I'm not accidently calling the default constructor to get the entity object.
That would cause that the entity object would use the default connectionstring from the app.config. The default connectionstring is the debug one, which connects to my local database.
Should be clear, that if that happens, the data that is getting queried from that entity
is working in my debugger, but wouldn't work for the user, because that user hasn't got an connection to my local database (-:
I did a workaround for now and derived the entity class and provide just the constructor that requires the connectionstring:
public class ProjectEntities : projekteEntities
{
public ProjectEntities(string connectionString) : base (connectionString){}
}
Update
I solve dit to my needs on that way:
I renamed the generted projekteEntitites class...
public partial class ObsoleteProjekteEntities : ObjectContext
{
#region Konstruktoren
/// <summary>
/// Initialisiert ein neues projekteEntities-Objekt mithilfe der in Abschnitt 'projekteEntities' der Anwendungskonfigurationsdatei gefundenen Verbindungszeichenfolge.
/// </summary>
public ObsoleteProjekteEntities() : base("name=projekteEntities", "projekteEntities")
{
this.ContextOptions.LazyLoadingEnabled = true;
OnContextCreated();
}
/// <summary>
/// Initialisiert ein neues projekteEntities-Objekt.
/// </summary>
public ObsoleteProjekteEntities(string connectionString) : base(connectionString, "projekteEntities")
{
this.ContextOptions.LazyLoadingEnabled = true;
OnContextCreated();
}
/// <summary>
/// Initialisiert ein neues projekteEntities-Objekt.
/// </summary>
public ObsoleteProjekteEntities(EntityConnection connection) : base(connection, "projekteEntities")
{
this.ContextOptions.LazyLoadingEnabled = true;
OnContextCreated();
}
and derived that class...
public class ProjectEntities : ObsoleteProjekteEntities
{
public ProjectEntities(string connectionString) : base (connectionString){}
}
...now I won't accidently call the original generated context with the default constructor.
Thanks for the other suggestions, will have a look at them too (-:
If you're already managing both release and debug connection string in your code, then comment out connection string in the web.config file. Even better, use .config file transformation tool to do this. Take a look at this tutorial.
Secondly, don't rely on coders to take care which connection string is used each time. Don't use Debugger.IsAttached to decide between debug and release mode. If someone runs the application without debugger, it will hit release database. Instead use C# pre-processor directives.
For example:
Go to Project properties
Go to Build tab
Check Define DEBUG constant
Go to file where you're setting the connection string and use following code:
#if DEBUG
CustomerConnectionString = ConfigurationManager.ConnectionStrings["kundenEntities"].ConnectionString;
ProjectsConnectionString = ConfigurationManager.ConnectionStrings["projekteEntities"].ConnectionString;
#else
CustomerConnectionString = ConfigurationManager.ConnectionStrings["kundenEntitiesRelease"].ConnectionString;
ProjectsConnectionString = ConfigurationManager.ConnectionStrings["projekteEntitiesRelease"].ConnectionString;
#endif
This is my entity class:
public partial class NerdDinnerEntities : ObjectContext
{
public NerdDinnerEntities(string connectionString)
: base(connectionString, "NerdDinnerEntities")
{
try
{
ObjectContext oc = new ObjectContext(connectionString);
oc.Connection.ChangeDatabase("NERDDINNER1");
oc.AcceptAllChanges();
this.ContextOptions.LazyLoadingEnabled = true;
OnContextCreated();
}
catch (Exception ex) { }
}
partial void OnContextCreated();
/// <summary>
/// No Metadata Documentation available.
/// </summary>
public ObjectSet<Dinner> Dinners
{
get
{
if ((_Dinners == null))
{
_Dinners = base.CreateObjectSet<Dinner>("Dinners");
}
return _Dinners;
}
}
private ObjectSet<Dinner> _Dinners;
/// <summary>
/// No Metadata Documentation available.
/// </summary>
public ObjectSet<RSVP> RSVPs
{
get
{
if ((_RSVPs == null))
{
_RSVPs = base.CreateObjectSet<RSVP>("RSVPs");
}
return _RSVPs;
}
}
private ObjectSet<RSVP> _RSVPs;
/// <summary>
/// No Metadata Documentation available.
/// </summary>
public ObjectSet<sysdiagram> sysdiagrams
{
get
{
if ((_sysdiagrams == null))
{
_sysdiagrams = base.CreateObjectSet<sysdiagram>("sysdiagrams");
}
return _sysdiagrams;
}
}
private ObjectSet<sysdiagram> _sysdiagrams;
/// <summary>
/// Deprecated Method for adding a new object to the Dinners EntitySet. Consider using the .Add method of the associated ObjectSet<T> property instead.
/// </summary>
public void AddToDinners(Dinner dinner)
{
base.AddObject("Dinners", dinner);
}
/// <summary>
/// Deprecated Method for adding a new object to the RSVPs EntitySet. Consider using the .Add method of the associated ObjectSet<T> property instead.
/// </summary>
public void AddToRSVPs(RSVP rSVP)
{
base.AddObject("RSVPs", rSVP);
}
/// <summary>
/// Deprecated Method for adding a new object to the sysdiagrams EntitySet. Consider using the .Add method of the associated ObjectSet<T> property instead.
/// </summary>
public void AddTosysdiagrams(sysdiagram sysdiagram)
{
base.AddObject("sysdiagrams", sysdiagram);
}
}
and these is my web.config file as
<add name="NerdDinnerEntities" connectionString="metadata=res://*/Models.NerdDinner.csdl|res://*/Models.NerdDinner.ssdl|res://*/Models.NerdDinner.msl;provider=System.Data.SqlClient;provider connection string="Data Source=#;Database=NERDDINNER;User ID=#;Password=###;MultipleActiveResultSets=True"" providerName="System.Data.EntityClients" />
and i am getting error:
Specified method is not supported
in this line:
oc.Connection.ChangeDatabase("NERDDINNER1");
If you look up the docs at MSDN you will see that the method literally is not supported. It must be a placeholder for future improvements or something.
To expand for those who do want to change the database at runtime:
1.Create an entry in your settings to use in-place of the default in the app.config. Pull out the specifics, like Username, password, catalog name (database name), server etc into other settings entries.
<Setting Name="EntityConnectionString2" Type="System.String" Scope="Application">
<Value Profile="(Default)">metadata=res://*/Model1.csdl|res://*/Model1.ssdl|res://*/Model1.msl;provider=System.Data.SqlClient;provider connection string="data source={0};initial catalog={1};persist security info=True;user id={2};password={3};encrypt=True;trustservercertificate=True;multipleactiveresultsets=True;App=EntityFramework"</Value>
</Setting>
Please note the {0}..{3} entries & that this connection string is not the whole configuration/connectionStrings/add entry in the app.config
2.Use one of the overloaded constructors for the EF Database that accepts a connection string.
var settings = Properties.Settings.Default;
string constring = string.Format(settings.EntityConnectionString2, settings.Server, settings.Database, settings.User, settings.Password);
NerdDinnerEntities db = new NerdDinnerEntities (constring);
3.To change at runtime you can create a different object in the same manner with a different catalog name, or dispose and recreate the db object with a different catalog name.
ChangeDatabase method of EntityConnection is not supported (http://msdn.microsoft.com/en-us/library/system.data.entityclient.entityconnection.changedatabase.aspx)
If you want to use your data context with another database, create another connection string and create data context instance using new connection string
I have set up some In Memory SQLite Unit Tests for my Fluent NHibernate Database, which looks like this. It works fine. (Using NUnit)
namespace Testing.Database {
/// <summary>
/// Represents a memory only database that does not persist beyond the immediate
/// testing usage, using <see cref="System.Data.SQLite"/>.
/// </summary>
public abstract class InMemoryDatabase : IDisposable {
/// <summary>
/// The configuration of the memorized database.
/// </summary>
private static Configuration Configuration { get; set; }
/// <summary>
/// The singleton session factory.
/// </summary>
protected static ISessionFactory SessionFactory { get; set; }
/// <summary>
/// The current session being used.
/// </summary>
protected ISession Session { get; set; }
protected InMemoryDatabase() {
SessionFactory = CreateSessionFactory();
Session = SessionFactory.OpenSession();
BuildSchema(Session);
}
/// <summary>
/// Construct a memory based session factory.
/// </summary>
/// <returns>
/// The session factory in an SQLite Memory Database.
/// </returns>
private static ISessionFactory CreateSessionFactory() {
return FluentNHibernate.Cfg.Fluently.Configure()
.Database(FluentNHibernate.Cfg.Db.SQLiteConfiguration
.Standard
.InMemory()
.ShowSql())
.Mappings(mappings => mappings.FluentMappings.AddFromAssemblyOf<Data.Mappings.AspectMap>())
.ExposeConfiguration(configuration => Configuration = configuration)
.BuildSessionFactory();
}
/// <summary>
/// Builds the NHibernate Schema so that it can be mapped to the SessionFactory.
/// </summary>
/// <param name="Session">
/// The <see cref="NHibernate.ISession"/> to build a schema into.
/// </param>
private static void BuildSchema(ISession Session) {
var export = new NHibernate.Tool.hbm2ddl.SchemaExport(Configuration);
export.Execute(true, true, false, Session.Connection, null);
}
/// <summary>
/// Dispose of the session and released resources.
/// </summary>
public void Dispose() {
Session.Dispose();
}
}
}
So now, in order to use it, I just inherit InMemoryDatabase and add my Test methods, like this.
[TestFixture]
public class PersistenceTests : InMemoryDatabase {
[Test]
public void Save_Member() {
var member = // ...;
Session.Save(member); // not really how it looks, but you get the idea...
}
}
My problem isn't that this doesn't work. It does. But if I have two tests in the same class that test similar data, for instance ...
Username_Is_Unique() and then Email_Is_Unique(). Not real tests again, but it's a good example.
[Test]
public void Username_Is_Unique(){
var user = new User {
Name = "uniqueName"
Email = "uniqueEmail"
};
// do some testing here...
}
[Test]
public void Email_Is_Unique(){
var user = new User {
Name = "uniqueName"
Email = "uniqueEmail"
};
// do some testing here...
}
I realize these are very bad tests. These are not real tests, I am just citing an example.
In both cases, I would construct a mock User or Member or what-have you and submit it to the database.
The first one works fine, but since the database is in memory (which makes sense, since I told it to be), the second one doesn't. Effectively, the Unit Tests do not reflect real-world situations, because each test stands alone. But when running them sequentially in a batch, it behaves like it should in the real world (I suppose that's partially a good thing)
What I want to do is flush the in memory database after each method. So I came up with a simple way to do this by repeating the constructor. This goes in the InMemoryDatabase class.
protected void Restart() {
SessionFactory = CreateSessionFactory();
Session = SessionFactory.OpenSession();
BuildSchema(Session);
}
So now, in each method in my inheriting class, I call Restart() before I do my testing.
I feel like this isn't the intended, or efficient way to solve my problem. Can anyone propose a better solution?
If it is of any relevance, I am using Fluent nHibernate for the persistence, and Telerik JustMock for my Mocking - but for my database stuff, I've yet to need any mocking.
You need to drop and recreate the database for every test. Every test should be independent of the other. You can do do two thing, first have your test use a setup method (Assuming NUnit here but others have the same functionality)
[SetUp]
public void Setup()
{
// Create in memory database
Memdb = new InMemoryDatabase();
}
Alternatively, you can wrap each test in a using statement for the database. For example
[Test]
public void Test()
{
using(var db = new InMemmoryDatabase())
{
Do Some Testing Here
}
}