C# EF, refresh context - c#

I'm creating an app that should work with various sqlite databases (with same structure) and I want to be able to open and close different databases. I use EF6 and I just can't figure out how to open a different database file (and make EF to reload and use data from new file). There are many questions regarding this but none of them work for me.
this is my dbContext generated by EF
public partial class mainEntities : DbContext
{
public mainEntities()
: base("name=mainEntities")
{
}
...
}
this is how I use and try to update my context
class Db
{
private static mainEntities myDbInstance;
public static mainEntities MyDbInstance
{
get
{
if (myDbInstance == null)
{
myDbInstance = new mainEntities();
}
return myDbInstance;
}
}
public static void UpdateConnectionString(string pth)
{
StringBuilder sb = new StringBuilder();
sb.Append("metadata=res://*/Model1.csdl|res://*/Model1.ssdl|res://*/Model1.msl;");
sb.Append("provider=System.Data.SQLite.EF6;");
sb.Append("provider connection string=");
sb.Append("'");
sb.Append("datetime format=JulianDay;");
sb.Append("foreign keys=True;");
sb.Append("data source=");
sb.Append(pth);
sb.Append("'");
var config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
var connectionStringsSection = (ConnectionStringsSection)config.GetSection("connectionStrings");
connectionStringsSection.ConnectionStrings["mainEntities"].ConnectionString = sb.ToString();
connectionStringsSection.ConnectionStrings["mainEntities"].ProviderName = "System.Data.EntityClient";
config.Save();
ConfigurationManager.RefreshSection("connectionStrings");
Properties.Settings.Default.Save();
Properties.Settings.Default.Reload();
//reset context - does not work
MyDbInstance.Dispose();
myDbInstance = null;
}
}
I am able to update the connectionString,
var conn_str = System.Configuration.ConfigurationManager.ConnectionStrings["mainEntities"].ConnectionString;
returns correct string after I change database file, but data is loaded from the original one.
EDIT:
I check if the connection changed with simple WPF GUI
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void refresh_labels()
{
var conn_str = System.Configuration.ConfigurationManager.ConnectionStrings["mainEntities"].ConnectionString;
var rows_count =
(from d in Db.MyDbInstance.TagsFiles
select d).Count();
label1.Content = conn_str.Split(';')[4];
label2.Content = rows_count;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
String DbFileName1 = #"c:\tmp\db1.db3";
String DbFileName2 = #"c:\tmp\db2.db3";
var conn_str = System.Configuration.ConfigurationManager.ConnectionStrings["mainEntities"].ConnectionString;
if (conn_str.Contains(DbFileName1))
{
Db.UpdateConnectionString(DbFileName2);
}
else
{
Db.UpdateConnectionString(DbFileName1);
}
refresh_labels();
}
}

Related

How to call object from one class to another in C#

I have a problem that I just can't solve on my own. I am new to programming and I would appreciate if you could help me with this issue:
I have a class I would like to inherit from:
namespace rsDeployer.Common.SQLServerCommunication
{
public class RSDatabaseConnectionCreator: LoggerBase
{
public RSProfile profile;
public RSDatabaseConnectionCreator(RSProfile profile)
{
this.profile = profile;
}
public SqlConnection CreateConnection(RSDatabaseNames DatabaseName, bool UseWindowsAuthentication, bool testConnection = false)
{
var connectionString = BuildRSDatabaseConnectionString(DatabaseName, UseWindowsAuthentication);
if (testConnection)
{
return IsConnectionAvailable(connectionString) ? new SqlConnection(connectionString) : null;
}
return new SqlConnection(connectionString);
}
}
}
and I would like to call CreateConnection() in another class to inject to methods to allow me to open connection and then execute scripts.
Edit 1 - class I would like to have it injected to.
public void QueryExecution(string SQLQuery)
{
//here's where I would like to inject it
SqlCommand command = new SqlCommand(SQLQuery, conn);
var file = new StreamWriter(#"D:\Project\rsDeployer\ImportedQuery.txt");
file.WriteLine(command);
file.Close();
}
If this question is way to silly to deserve answer would you just point in the direction where I should read about it?
I hope this question is well asked and clear.
Thanks in advance.
Like this,
public void QueryExecution(string SQLQuery)
{
RSProfile profile = new RSProfile();
RSDatabaseConnectionCreator instance = new RSDatabaseConnectionCreator(profile);
SqlConnection conn = instance.CreateConnection(...);
SqlCommand command = new SqlCommand(SQLQuery, conn);
var file = new StreamWriter(#"D:\Project\rsDeployer\ImportedQuery.txt");
file.WriteLine(command);
file.Close();
conn.Close();
}
You also told that you want to inherit from this class, here is another approach,
public class RSDatabaseConnectionCreator : LoggerBase
{
public virtual object CreateConnection() // by virtual you can override it.
{
return new object();
}
}
public class AnotherClass : RSDatabaseConnectionCreator {
public AnotherClass() {
CreateConnection(); // by inheriting RSDatabaseConnectionCreator , you can reach public functions.
}
public override object CreateConnection() // or you can override it
{
// here might be some user Login check
return base.CreateConnection(); // then you open connection
}
}
Hope helps,
Hope this is what you are requesting for
public class ClassX
{
private RSProfile _rsprofile;
RSDatabaseConnectionCreator _dbConnectionCreator;
private SqlConnection _sqlConnection;
public ClassX()
{
_rsProfile = xxx; // Get the RSProfile object
_dbConnectionCreator = new RSDatabaseConnectionCreator (_rsProfile);
RSDatabaseNames databaseName = yyy; // get the RSDatabaseNames
var useWindowsAuthentication = true;
var testConnection = false;
_sqlConnection = _dbConnectionCreator.CreateConnection(databaseName,useWindowsAuthentication ,testConnection );
}
}
This is how you do it. Apologies for a major blunder in the earlier answer. This one has the connection surrounded by a using.
namespace rsDeployer.Common.SQLServerCommunication
{
public class ConsumerClass
{
public void QueryExecution(string SQLQuery)
{
var profile = new RsProfile();
var rsConnectionCreator = new RSDatabaseConnectionCreator(profile);
using(var sqlConnection = rsConnectionCreator.CreateConnection(...Parameters here...)){
SqlCommand command = new SqlCommand(SQLQuery, sqlConnection );
}
var file = new StreamWriter(#"D:\Project\rsDeployer\ImportedQuery.txt");
file.WriteLine(command);
file.Close();
}
}
}
You can inject the connection creator into the consumer class through the constructor.
public class Consumer
{
private RSDatabaseConnectionCreator _connectionCreator;
// Constructor injection
public Consumer (RSDatabaseConnectionCreator connectionCreator)
{
_connectionCreator = connectionCreator;
}
public void QueryExecution(string SQLQuery)
{
using (var conn = _connectionCreator.CreateConnection(dbName, true, true)) {
if (conn != null) {
...
}
}
}
}
Note: The using statement automatically closes the connection.
Usage
var connectionCreator = new RSDatabaseConnectionCreator(profile);
var consumer = new Consumer(connectionCreator);
consumer.QueryExecution(sqlQuery);
If you want to inject the connection creator at each call of QueryExecution, you can inject it directly into the method as an additional parameter, instead.
public void QueryExecution(string SQLQuery, RSDatabaseConnectionCreator connectionCreator)
{
using (var conn = connectionCreator.CreateConnection(dbName, true, true)) {
if (conn != null) {
...
}
}
}
Usage
var connectionCreator = new RSDatabaseConnectionCreator(profile);
var consumer = new Consumer();
consumer.QueryExecution(sqlQuery, connectionCreator);

Loading XML document loads the same group twice

Some classes to start, I'm writing them all so you can reproduce my problem:
public class PermissionObject
{
public string permissionName;
public string permissionObject;
public bool permissionGranted;
public PermissionObject()
{
permissionName = "";
permissionObject = "";
permissionGranted = true;
}
public PermissionObject(string name, string obj, bool granted)
{
permissionName = name;
permissionObject = obj;
permissionGranted = granted;
}
}
public class Config
{
public string cmsDataPath = "";
public string cmsIP = "";
public List<UserClass> usersCMS = new List<UserClass>();
static public string pathToConfig = #"E:\testconpcms.xml";
public string cardServerAddress = "";
public void Save()
{
XmlSerializer serializer = new XmlSerializer(typeof(Config));
using (Stream fileStream = new FileStream(pathToConfig, FileMode.Create))
{
serializer.Serialize(fileStream, this);
}
}
public static Config Load()
{
if (File.Exists(pathToConfig))
{
XmlSerializer serializer = new XmlSerializer(typeof(Config));
try
{
using (Stream fileStream = new FileStream(pathToConfig, FileMode.Open))
{
return (Config)serializer.Deserialize(fileStream);
}
}
catch (Exception ex)
{
return new Config();
}
}
else
{
return null;
}
}
}
public class UserClass
{
public string Name;
public string Login;
public string Password;
public PCMS2 PermissionsList; // OR new PCMS1, as I will explain in a bit
public UserClass()
{
this.Name = "Admin";
this.Login = "61-64-6D-69-6E";
this.Password = "61-64-6D-69-6E";
this.PermissionsList = new PCMS2(); // OR new PCMS1, as I will explain in a bit
}
}
The problematic bit: consider two implementations of PCMS class, PCMS1 and PCMS2:
public class PCMS1
{
public PermissionObject p1, p2;
public PCMS1()
{
p1 = new PermissionObject("ImportConfigCMS", "tsmiImportCMSConfigFile", true);
p2 = new PermissionObject("ExportConfigCMS", "tsmiExportCMSConfigFile", true);
}
}
public class PCMS2
{
public List<PermissionObject> listOfPermissions = new List<PermissionObject>();
public PCMS2()
{
listOfPermissions.Add(new PermissionObject("ImportConfigCMS", "tsmiImportCMSConfigFile", true));
listOfPermissions.Add(new PermissionObject("ExportConfigCMS", "tsmiExportCMSConfigFile", true));
}
}
And finally main class:
public partial class Form1 : Form
{
private Config Con;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
Con = Config.Load();
if (Con == null)
{
Con = new Config();
Con.cmsDataPath = #"E:\testconpcms.xml";
Con.Save();
}
if (Con.usersCMS.Count == 0)
{
UserClass adminDefault = new UserClass();
Con.usersCMS.Add(adminDefault);
Con.Save();
}
}
}
Now, using either PCMS1 or PCMS2, the config file generates properly - one user with 2 permissions.
However, when config file is present, calling Con = Config.Load() in the main class gives different results.
Using PCMS1, the Con object is as expected - 1 user with 2 permissions.
However, using PCMS2, the Con object is 1 user with 4 (four) permissions. It doubles that field (it's basically p1, p2, p1, p2). Put a BP to see Con after Load().
I guess the list (PCMS2) implementation is doing something wonky during load which I'm not aware of, but I can't seem to find the issue.
You creates your permission objects in constructor of PMCS2 you do it in the constructor of PMCS1 too, but there you do have two properties that will be overwritten by serializer.
In case of of PMCS2 your constructor adds two items to List and than serializer adds the items it has deserilized to the same list.
I don't know exactly your usecase but i would suggest to move init of the permissions to separated method:
public class PCMS1
{
public PermissionObject p1, p2;
public void Init()
{
p1 = new PermissionObject("ImportConfigCMS", "tsmiImportCMSConfigFile", true);
p2 = new PermissionObject("ExportConfigCMS", "tsmiExportCMSConfigFile", true);
}
}
public class PCMS2
{
public List<PermissionObject> listOfPermissions = new List<PermissionObject>();
public void Init()
{
listOfPermissions.Add(new PermissionObject("ImportConfigCMS", "tsmiImportCMSConfigFile", true));
listOfPermissions.Add(new PermissionObject("ExportConfigCMS", "tsmiExportCMSConfigFile", true));
}
}
after that you could call it, if you want to get initial settings:
if (Con.usersCMS.Count == 0)
{
UserClass adminDefault = new UserClass();
adminDefault.PermissionsList.Init();
Con.usersCMS.Add(adminDefault);
Con.Save();
}

How to manually (via code) apply DbMigration which was manually crafted?

I manually created a class
public class AddClientsTable : DbMigration, IMigrationMetadata
{
string IMigrationMetadata.Id
{
get { return "201611281757258_AddClientsTable"; }
}
string IMigrationMetadata.Source
{
get { return null; }
}
string IMigrationMetadata.Target
{
get { return "AddClientsTable-Migration"; }
}
public override void Up() {
CreateTable("Clients", t => new {
ClientId = t.Guid(name:"ClientId"),
Name = t.String()
})
.PrimaryKey( t => t.ClientId, "ClientId")
.Index( t => t.ClientId, "PK_Clients", true);
}
public override void Down() {
DropIndex("Clients", "PK_Clients");
DropTable("Clients");
}
}
and i want to apply it via code-first migrations from code like this :
var migration = new AddClientsTable();
migration.Up();
context.RunMigration(migration);
which I stole from here but when I run the code I'm getting this exception :
Unable to cast object of type 'System.Data.Entity.Migrations.Model.CreateIndexOperation' to type 'System.Data.Entity.Migrations.Model.HistoryOperation'.
HistoryOperation is the operation which updates __MigrationHistory table ? so How do I do that via code ?
Am I missing something or the EntityFrameowrk Update-Database command does more than what I'm aware of ?
It doesn't make sense to cherry pick a migration and run it, because the migrations are cumulative and must be run in sequence. As such, you'd be better to run the equivalent of update-database powershell command at application startup.
Here's some code we use to do that:
In the Configuration.cs class constructor (this file was made when you enable-migrations)
AutomaticMigrationsEnabled = false;
AutomaticMigrationDataLossAllowed = false;
then at app startup call the following method:
public static void ApplyDatabaseMigrations()
{
//Configuration is the class created by Enable-Migrations
DbMigrationsConfiguration dbMgConfig = new Configuration()
{
ContextType = typeof(MyDbContext) //+++++CHANGE ME+++++
};
using (var databaseContext = new MyDbContext()) //+++++CHANGE ME+++++
{
try
{
var database = databaseContext.Database;
var migrationConfiguration = dbMgConfig;
migrationConfiguration.TargetDatabase =
new DbConnectionInfo(database.Connection.ConnectionString,
"System.Data.SqlClient");
var migrator = new DbMigrator(migrationConfiguration);
migrator.Update();
}
catch (AutomaticDataLossException adle)
{
dbMgConfig.AutomaticMigrationDataLossAllowed = true;
var mg = new DbMigrator(dbMgConfig);
var scriptor = new MigratorScriptingDecorator(mg);
string script = scriptor.ScriptUpdate(null, null);
throw new Exception(adle.Message + " : " + script);
}
}
}

Entity Framework 6 set connection string in code

I have a dll that uses the Entity Framework 6 to do some database operations. I'm using a database first approach.
The model and everything concerning the Entity Framework, like the connection string in the App.config, were created via the wizzard in Visual Studio.
So I compiled the dll and put it together with the corresponding .config in the folder where the application using the dll expects it.
Everything works fine until I get to the point where an actual database call is made. There I get the error:
Cannot find connection string for MyDatabaseEntity
The automatically generated connectionstring is, as I said, in the config file of the dll. I cannot change the App.config of the application.
But the application hands over an object that has all the information I need to build the connection string myself.
So I'm looking for a way to set the connection string in the code without relying on a config file.
All the tutorials I find for a database first approach use this method though.
I found a post here that says to simply give the connection string as a parameter when creating the Object like
MyDatabaseEntities = new MyDatabaseEntities(dbConnect);
but ´MyDatabaseEntities´ doesn't have a constructor that takes any parameters
public partial class MyDatabaseEntities : DbContext
{
public MyDatabaseEntities()
: base("name=MyDatabaseEntities")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public virtual DbSet<MyTable> MyTable { get; set; }
}
How about:
public partial class MyDatabaseEntities : DbContext
{
public MyDatabaseEntities(string connectionString)
: base(connectionString)
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public virtual DbSet<MyTable> MyTable { get; set; }
}
Then initialize your database like you did before:
string myConnectionString = "...";
MyDatabaseEntities = new MyDatabaseEntities(myConnectionString);
I had the similar issue. My Edmx and App.Config was in a different project. My startup project was different, had 3 different connection strings, we need to choose one on the fly depending on the environment. So couldn't use a fixed connection string. I created a partial class overload of the Context.cs using the same namespace. Following was my default Context.cs;
namespace CW.Repository.DBModel
{
public partial class CWEntities : DbContext
{
public CWEntities()
: base("name=CWEntities")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
...
...
}
}
My partial class overload;
namespace CW.Repository.DBModel
{
public partial class CWEntities : DbContext
{
public CWEntities(string ConnectionString)
: base(ConnectionString)
{
}
}
}
Lastly, as my connection strings were not for EF, I converted them to a EF connection string.
public static string GetEntityConnectionString(string connectionString)
{
var entityBuilder = new EntityConnectionStringBuilder();
// WARNING
// Check app config and set the appropriate DBModel
entityBuilder.Provider = "System.Data.SqlClient";
entityBuilder.ProviderConnectionString = connectionString + ";MultipleActiveResultSets=True;App=EntityFramework;";
entityBuilder.Metadata = #"res://*/DBModel.CWDB.csdl|res://*/DBModel.CWDB.ssdl|res://*/DBModel.CWDB.msl";
return entityBuilder.ToString();
}
Lastly, the calling
var Entity = new CWEntities(CWUtilities.GetEntityConnectionString(ConnectionString));
I got this solution using below code, I can hardcode connection string using C# code without using config file.
public class SingleConnection
{
private SingleConnection() { }
private static SingleConnection _ConsString = null;
private String _String = null;
public static string ConString
{
get
{
if (_ConsString == null)
{
_ConsString = new SingleConnection { _String = SingleConnection.Connect() };
return _ConsString._String;
}
else
return _ConsString._String;
}
}
public static string Connect()
{
//Build an SQL connection string
SqlConnectionStringBuilder sqlString = new SqlConnectionStringBuilder()
{
DataSource = "SIPL35\\SQL2016".ToString(), // Server name
InitialCatalog = "Join8ShopDB", //Database
UserID = "Sa", //Username
Password = "Sa123!##", //Password
};
//Build an Entity Framework connection string
EntityConnectionStringBuilder entityString = new EntityConnectionStringBuilder()
{
Provider = "System.Data.SqlClient",
Metadata = "res://*/ShopModel.csdl|res://*/ShopModel.ssdl|res://*/ShopModel.msl",
ProviderConnectionString = #"data source=SIPL35\SQL2016;initial catalog=Join8ShopDB2;user id=Sa;password=Sa123!##;"// sqlString.ToString()
};
return entityString.ConnectionString;
}
and using DbContext using like this:
Join8ShopDBEntities dbContext = new Join8ShopDBEntities(SingleConnection.ConString);
Thanks a lot . I changed little for Code First EF6.
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data.Entity.Core.EntityClient;
using System.Data.SqlClient;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Data
{
public class SingleConnection
{
private SingleConnection() { }
private static SingleConnection _ConsString = null;
private String _String = null;
public static string ConString
{
get
{
if (_ConsString == null)
{
_ConsString = new SingleConnection { _String = SingleConnection.Connect() };
return _ConsString._String;
}
else
return _ConsString._String;
}
}
public static string Connect()
{
string conString = ConfigurationManager.ConnectionStrings["YourConnectionStringsName"].ConnectionString;
if (conString.ToLower().StartsWith("metadata="))
{
System.Data.Entity.Core.EntityClient.EntityConnectionStringBuilder efBuilder = new System.Data.Entity.Core.EntityClient.EntityConnectionStringBuilder(conString);
conString = efBuilder.ProviderConnectionString;
}
SqlConnectionStringBuilder cns = new SqlConnectionStringBuilder(conString);
string dataSource = cns.DataSource;
SqlConnectionStringBuilder sqlString = new SqlConnectionStringBuilder()
{
DataSource = cns.DataSource, // Server name
InitialCatalog = cns.InitialCatalog, //Database
UserID = cns.UserID, //Username
Password = cns.Password, //Password,
MultipleActiveResultSets = true,
ApplicationName = "EntityFramework",
};
//Build an Entity Framework connection string
EntityConnectionStringBuilder entityString = new EntityConnectionStringBuilder()
{
Provider = "System.Data.SqlClient",
Metadata = "res://*",
ProviderConnectionString = sqlString.ToString()
};
return entityString.ConnectionString;
}
}
}
You can use singleton patter for it . For example
private YouurDBContext context;
public YouurDBContext Context
{
get
{
if (context==null)
{
context = new YouurDBContext();
}
return context;
}
set { context = value; }
}

Refresh Application Automatically When Data changed in SQL Server

I use SQL Server and I have 3 Application servers. When a table in my database have changed I need to those application servers refresh there local cached data. I use a trigger to known change and send a message via Service broker queue. Then I create a stored procedure and assign it to activate stored procedure of my queue, In this stored procedure I receive message, but I don't know How should I call refresh method in my application.
I had similar issue but this code resolved the issue :
public class QueryNotification
{
public DataSet DataToWatch { get; set; }
public SqlConnection Connection { get; set; }
public SqlCommand Command { get; set; }
public string GetSQL()
{
return "SELECT * From YourTable";
}
public string GetConnection()
{
return ConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString;
}
public bool CanRequestNotifications()
{
try
{
var perm = new SqlClientPermission(PermissionState.Unrestricted);
perm.Demand();
return true;
}
catch
{
return false;
}
}
public void GetData()
{
DataToWatch.Clear();
Command.Notification = null;
var dependency = new SqlDependency(Command);
dependency.OnChange += dependency_OnChange;
using (var adapter = new SqlDataAdapter(Command))
{
adapter.Fill(DataToWatch, "YourTableName");
}
}
private void dependency_OnChange(object sender, SqlNotificationEventArgs e)
{
var i = (ISynchronizeInvoke)sender;
if (i.InvokeRequired)
{
var tempDelegate = new OnChangeEventHandler(dependency_OnChange);
object[] args = { sender, e };
i.BeginInvoke(tempDelegate, args);
return;
}
var dependency = (SqlDependency)sender;
dependency.OnChange -= dependency_OnChange;
GetData();
}
}
Update:
Check for permission:
public bool CanRequestNotifications()
{
try
{
var perm = new SqlClientPermission(PermissionState.Unrestricted);
perm.Demand();
return true;
}
catch
{
return false;
}
}
For Instance in your window load:
if (!_queryNotification.CanRequestNotifications())
{
MessageBox.Show("ERROR:Cannot Connect To Database");
}
SqlDependency.Stop(_queryNotification.GetConnection());
SqlDependency.Start(_queryNotification.GetConnection());
if (_queryNotification.Connection == null)
{
_queryNotification.Connection = new SqlConnection(_queryNotification.GetConnection());
}
if (_queryNotification.Command == null)
{
_queryNotification.Command = new SqlCommand(_queryNotification.GetSQL(),
_queryNotification.Connection);
}
if (_queryNotification.DataToWatch == null)
{
_queryNotification.DataToWatch = new DataSet();
}
GetData();
You should look at using the SqlDependency class.
More information at: http://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqldependency(v=vs.110).aspx
I can suggest you to try solving the problem using TCP. Each app listens to a port and when another app updates the db it sends a message to the other apps saying they need to refresh.
Hope that was a good idea.

Categories