DbContext doesn't release SQLite database - c#

First, these are my intentions:
Create a DbContext on SQLite
Read and write from/to it
Close context
Move the file to another location
Points 1-3 work perfectly. The problem starts when I try to move the database. I get an error stating that:
'The process cannot access the file because it is being used by another process.'
How can I resolve this?
First, I create a context. I have to use it in several methods, and I don't want to create it every time I need it. So I am storing it as a member.
_sqliteContext = new SqlLiteContext(sqliteContextName);
Then I want to access a table called sync and get its latest entry.
var sync = _sqliteContext.Syncs.OrderByDescending(s => s.Date);
_lastSync = sync.Any() ? sync.First().Date : new DateTime(0);
That's it. Then I close the context.
_sqliteContext.Dispose();
And try to move the file.
File.Move(sqliteUploadLocation, sqliteDownloadLocation);
This is where I get the exception.
When I replace the selection with an insert, like the following:
var sync = new Sync { Id = Guid.NewGuid().ToString(), Date = DateTime.Now };
_sqliteContext.Syncs.Add(sync);
_sqliteContext.SaveChanges();
This works, and I can move the database. Any ideas why my selection doesn't release its lock?
Update
// Start synchronisation.
new SyncManager(mssqlString, sqliteUploadLocation).Start();
// Move file from upload to download location.
try
{
File.Move(sqliteUploadLocation, sqliteDownloadLocation);
}
catch (Exception ex)
{
Console.WriteLine("Moving failed!");
Console.WriteLine(ex.Message);
}
public void Start()
{
// Create connection string for the sqlite database.
const string sqliteContextName = "SqLiteContext";
var sqliteConnStringSettings = new ConnectionStringSettings
{
Name = sqliteContextName,
ConnectionString = "Data Source=" + _sqliteUploadLocation + ";Version=3;BinaryGUID=False;",
ProviderName = "System.Data.SQLite"
};
// Read configuration, delete available connection strings and add ours.
var conf = ConfigurationManager.OpenMachineConfiguration();
var connStrings = conf.ConnectionStrings;
connStrings.ConnectionStrings.Remove(sqliteContextName);
connStrings.ConnectionStrings.Add(sqliteConnStringSettings);
try
{
conf.Save(ConfigurationSaveMode.Minimal);
}
catch (Exception)
{
// Insufficient rights to save.
return;
}
ConfigurationManager.RefreshSection("connectionStrings");
// Create connection to the sqlite database.
_sqliteContext = new SqlLiteContext(sqliteContextName);
// Create connection to the mssql database.
_mssqlContext = new MsSqlContext(_mssqlConnString);
// Read last sync date.
var sync = _sqliteContext.Syncs.OrderByDescending(s => s.Date);
_lastSync = sync.Any() ? sync.First().Date : new DateTime(0);
// Synchronize tables.
//SyncTablePerson();
//SyncTableAddressAllocation();
// Creates an entry for this synchronisation.
CreateSyncEntry();
// Release resources.
_sqliteContext.Dispose();
_mssqlContext.Dispose();
}
private void CreateSyncEntry()
{
var sync = new Sync { Id = Guid.NewGuid().ToString(), Date = DateTime.Now };
_sqliteContext.Syncs.Add(sync);
_sqliteContext.SaveChanges();
}
Update 2
public class SqlLiteContext : Context
{
public DbSet<Sync> Syncs { get; set; }
public SqlLiteContext(string connectionString)
: base(connectionString)
{
Database.SetInitializer(new NoOperationStrategy<SqlLiteContext>());
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new PersonConfig());
modelBuilder.Configurations.Add(new AddressAllocationConfig());
modelBuilder.Configurations.Add(new AddressConfig());
modelBuilder.Configurations.Add(new SyncConfig());
}
}
public class NoOperationStrategy<T> : IDatabaseInitializer<T> where T : DbContext
{
public void InitializeDatabase(T context)
{
}
}
public abstract class Context : DbContext
{
public DbSet<Person> Persons { get; set; }
public DbSet<AddressAllocation> AddressAllocations { get; set; }
public DbSet<Address> Addresses { get; set; }
protected Context(string connectionString)
: base(connectionString)
{
}
}
Refactoring with using
using (var sqliteContext = new SqlLiteContext(_sqliteContextName))
{
// Read last sync date.
var sync = sqliteContext.Syncs.Select(s => s).OrderByDescending(s => s.Date);
var lastSync = sync.Any() ? sync.First().Date : new DateTime(1900, 1, 1);
using (var mssqlContext = new MsSqlContext(_mssqlConnString))
{
SyncTablePerson(sqliteContext, mssqlContext, lastSync);
SyncTableAddressAllocation(sqliteContext, mssqlContext, lastSync);
// Save server changes.
mssqlContext.SaveChanges();
}
// Creates an entry for this synchronisation.
sqliteContext.Syncs.Add(new Sync { Id = Guid.NewGuid().ToString(), Date = DateTime.Now });
// Save local changes.
sqliteContext.SaveChanges();
}

Two things jump to mind:
Make sure Visual Studio isn't locking the database file. Open Server Explorer and if there is a connection to the file make sure its closed or removed altogether.
It's likely that connection pooling is what is holding the connection open. Disable pooling in your connection string like this:
Data Source=e:\mydb.db;Version=3;Pooling=False;
As Matt pointed out you should really use a using statement rather than calling dispose manually. That way if there is an exception the resources are always released properly.

I found another topic with the same problem. After i refactored my code, i added
GC.Collect();
That removed the file lock and i could move the file.
see: https://stackoverflow.com/a/14151917/2462736

Related

NHibernate session factory count limit?

I am creating multiple session factories through code (not config file)
The problem I'm having is that after the 20th session factory creation, I start getting an exception (MappingException), and I don't know why.
Regardless of the order, it goes bad after the 20th. Sessions are successfully created as long as they are in the first 20 of them.
MappingException message: Unique suffix 100_ length must be less than maximum 4 characters
any help is appreciated.
public static void AddPortfolioToConnectionstrings(string portfolio, string
connectionString)
{
var configuration = new Configuration()
.Configure()
.SessionFactoryName(portfolio)
.SetProperty("connection.connection_string", connectionString);
...
_portfolios.Add(portfolio, configuration.BuildSessionFactory());
}
As usual the error messages of NHibernate aren't helping anyone.
In my case I had this error:
MappingException message: Unique suffix 100_ length must be less than maximum 4 characters
After intensive research, the cause of this error was that the connection string had the wrong server address, and then wrong credentials supplied.
About your question: having 20 destination databases to connect to is unusual, but it can be done. With the implementation I show you below, I use 12 session factories, but make sure you run your code in a 64-bit process space, otherwise it will quickly eat up the memory available to a 32-bit process.
The only thing you need to look out for is that you need to have a custom Session factory builder that you bind as a singleton. A lightweight version of my implementation looks like this:
public interface ISessionFactoryBuilder
{
IDictionary<string, ISessionFactory> SessionFactories { get; }
}
public IDictionary<string, ISessionFactory> SessionFactories { get; private set; }
private readonly IConfigurationManager _configurationManager;
public SessionFactoryBuilder(IConfigurationManager configurationManager)
{
this._configurationManager = configurationManager;
this.SessionFactories = this.BuildSessionFactories();
}
private IDictionary<string, ISessionFactory> BuildSessionFactories()
{
var sessionFactories = new Dictionary<string, ISessionFactory>(StringComparer.InvariantCultureIgnoreCase);
var connectionStrings = this._configurationManager.GetConnectionStrings();
if (connectionStrings.Count == 0)
throw new ConfigurationErrorsException("No connection descriptions can be found!");
foreach (ConnectionStringSettings item in connectionStrings)
if (item.Name != "LocalSqlServer" && item.Name != "OraAspNetConString")
sessionFactories.Add(item.Name, this.InitializeSessionFactory(item.ConnectionString, item.ProviderName));
return sessionFactories;
}
private class Connectiontypes
{
public string Db_type { get; set; }
public FluentConfiguration Configuration { get; set; }
}
private ISessionFactory InitializeSessionFactory(string connectionString = "", string providerName = "")
{
Trace.WriteLine($"{connectionString}");
List<SessionFactoryBuilder.Connectiontypes> conntypes = new List<SessionFactoryBuilder.Connectiontypes> {
new SessionFactoryBuilder.Connectiontypes
{
Db_type = "System.Data.SqlClient",
Configuration = Fluently.Configure().Database(MsSqlConfiguration.MsSql2005.ConnectionString(connectionString).ShowSql()
.Dialect<XMsSql2005Dialect>()) },
new SessionFactoryBuilder.Connectiontypes
{
Db_type = "System.Data.OracleDataClient",
Configuration = Fluently.Configure().Database(OracleDataClientConfiguration.Oracle10
.ConnectionString(connectionString).Provider<NHibernate.Connection.DriverConnectionProvider>()
.Driver<NHibernate.Driver.OracleManagedDataClientDriver>()
.Dialect<XOracle10gDialect>().ShowSql())
},
new SessionFactoryBuilder.Connectiontypes
{
Db_type = "System.Data.MySQLDataClient", Configuration = Fluently.Configure()
.Database(MySQLConfiguration.Standard.ConnectionString(connectionString).ShowSql())
}
};
FluentConfiguration fluentConfiguration = conntypes.Find(x => x.Db_type == providerName).Configuration;
fluentConfiguration.ExposeConfiguration(x =>
{
x.SetProperty("command_timeout", "120");
});
#if DEBUG
fluentConfiguration.ExposeConfiguration(x =>
{
x.SetInterceptor(new SqlStatementInterceptor());
});
#endif
var mappings = fluentConfiguration.Mappings(m =>
{
m.FluentMappings.AddFromAssemblyOf<UsersMap>();
});
var config = mappings.BuildConfiguration();
foreach (PersistentClass persistentClass in config.ClassMappings)
{
persistentClass.DynamicUpdate = true;
}
var sessionFactory = mappings
#if DEBUG
.Diagnostics(d => d.Enable(true))
.Diagnostics(d => d.OutputToConsole())
#endif
.BuildSessionFactory();
return sessionFactory;
}
public void Dispose()
{
if (this.SessionFactories.Count > 0)
{
foreach (var item in this.SessionFactories)
{
item.Value.Close();
item.Value.Dispose();
}
this.SessionFactories = null;
}
}
}
Then I bind this with NInject as:
Bind<ISessionFactoryBuilder>().To<SessionFactoryBuilder>().InSingletonScope().WithConstructorArgument("configurationManager", context => context.Kernel.Get<IConfigurationManager>());
Of course you have to execute this anywhere near your program startup, or as we call it the Composition Root.
The only thing missing here is the obvious implementation of the IConfigurationManager, which is just my custom wrapper around ConfigurationManager, that gets passed in to the constructor of SessionFactoryBuilder.
This way you can have your Session factory builder built at application startup and never again (obviously until the appdomain gets restarted or so), therefore you won't have memory leaks or OutOfMemory exceptions for having that many SessionFactories hanging around and getting created.

c# Entity Framework when should I use an new dbContext?

I was wondering for some time what is the proper way to make a new instance of an dbcontext? I got some problem with it because when I make up change in my database through SQL Server my context doesn't update the data. Let me explain how my website work.
We are doing an appointment website for our customer to take appointment obviously. we will hosting all database on our server. How it work is the application made up 2 connection:
first connection
this connection connect all the time to the same database let's call it master.
It'll redirect the user to the good database with the url code in it example:
www.example.com/foo
the server will check for the code where here is foo
So it'll lookup in the table to matchup the code and then take the good database name where it should redirect and it's here that my second connection come's up
Second connection
This one will make the connection to the correct database according to the data the master has return. From here all seems to work well except for the DBContext that actually never update because I don't instantiate it correctly and I don't have an large experience with it. Here's the code i did with my coworker:
using System;
using System.Data.EntityClient;
using System.Data.SqlClient;
using System.Linq;
using System.Threading;
using System.Web;
using System.Web.Routing;
using WebRV.Models.Entities;
namespace WebRV.RouteDb
{
public class DbConnection
{
private static DbConnection instance;
private Cliniciel_WebRV_Entities db;
private String connectionString;
private readonly Cliniciel_WebRV_MasterEntities masterDb = new Cliniciel_WebRV_MasterEntities();
private Int32 idCie;
private static readonly object myLock = new object();
private DbConnection() {
var context = new HttpContextWrapper(System.Web.HttpContext.Current);
var routeData = RouteTable.Routes.GetRouteData(context);
// Use RouteData directly:
String code = routeData.Values["code"].ToString();
//String code = Thread.CurrentContext. .RequestContext.RouteData.Values["code"].ToString();
var response = masterDb.SYS_tbCustDBLocation.Where(p => p.CustDBLocationCode == code).ToList();
if (response.Count == 1)
{
try
{
db = CreateConnection(response.FirstOrDefault());
idCie = (db.SYS_vwCie.Where(p => p.ClinicielWebName == code).FirstOrDefault()).IdCie;
}
catch (Exception e)
{
throw e;
}
}
else {
throw new FormatException();
}
}
private Cliniciel_WebRV_Entities CreateConnection(SYS_tbCustDBLocation data)
{
connectionString = *****
db = new Cliniciel_WebRV_Entities();
db.Database.Connection.ConnectionString = connectionString;
db.Database.Connection.Open();
return db;
}
private static void CreateInstance() {
instance = new DbConnection();
}
public static DbConnection GetInstance() {
lock (myLock)
{
if (instance == null)
{
CreateInstance();
}
}
return instance;
}
public String GetConnectionString()
{
return connectionString;
}
public Cliniciel_WebRV_Entities GetConnection()
{
return db;
}
public Int32 GetIdCie()
{
//hard code 1 but test
//return idCie;
return 1;
}
}
}
and here's an example of how I use it:
//[CompanyCodeFilter]
public class HomeController : AppointementController
{
//public static Cliniciel_WebRV_Entities entityDB = DbConnection.GetConnection();
public HomeController()
{
base.Refresh();
}
public JsonResult GetConsultationDescription(Int32 idService)
{
//base.Refresh();
entityDB.Set<SYS_vwService>().AsNoTracking();
var motifDescription = entityDB.SYS_vwService.Where(s => s.IDLang == cultureName && s.IdService == idService && s.IdCie == idCie).FirstOrDefault();
var base64 = Convert.ToBase64String(motifDescription.ServiceImage);
var imgSrc = String.Format("data:image/gif;base64,{0}", base64);
var imageDecode = imgSrc;
if (base64 == "AA==")
{
imageDecode = "";
}
var result = new { motifDescription, imageDecode };
return Json(result, JsonRequestBehavior.AllowGet);
}
}
Here base.refresh() call this:
using System;
using System.Linq;
using WebRV.Filters;
using WebRV.Localization;
using WebRV.Models.Entities;
using WebRV.RouteDb;
namespace WebRV.Controllers
{
//[CompanyCodeFilter]
public class AppointementController : BaseController
{
protected Cliniciel_WebRV_Entities entityDB;
protected Int32 idCie;
protected String cultureName;
//public AppointementController() {
// Refresh();
//}
//Permet de bien vérifier quel DB utilisé, quel idCie et quel cultureName.
protected void Refresh() {
entityDB = DbConnection.GetInstance().GetConnection();
idCie= DbConnection.GetInstance().GetIdCie();
cultureName = CultureLocalization.GetCulture();
//cultureName = "en-ca";
}
}
}
If someone can help me to instantiate the connection properly it'll be appreciate thank you
I think you need to better understand how the db context works.
It's a combination of many design patterns but at a basic level, it's essentially a unit of work that tracks changes to the objects that represent data. Because of this, it's not meant to be used as a connection that stays open all the time and you can just go back and forward, although you could use it like that to a certain extent.
From your code, it looks like you are using it in an ASP.NET MVC application. Because of the nature of the application, you can do one of two things:
You can create the db context whenever you need to use it, or
You can create the db context as a property of the controller
In the end, they somewhat amount to the same thing. I personally would recommend you create an instance of the context whenever you need to use it, making sure to dispose of it properly. You could then have a base controller class that exposes a CreateConnetion() method that creates an instance for your contexts, sort of like the Cliniciel_WebRV_Entities CreateConnection() method you already have, except that you don't need to open the connection explicitly and the connection string can be passed on as a constructor parameter if you add a partial class and implement one for the context. Something like this:
public partial class Cliniciel_WebRV_Entities
{
public Cliniciel_WebRV_Entities(string nameOrConnectionString):base(nameOrConnectionString)
{
}
}
You can then use it like this:
private Cliniciel_WebRV_Entities CreateConnection()
{
//Code to figure out which db to connect to (based on the other connection. You should consider caching that too if it doesn't change very often)
var nameOrConnectionString = FigureOutConnection();
var db = new Cliniciel_WebRV_Entities(nameOrConnectionString);
return db;
}
Keep in mind that the context depends on the metadata so make sure your connection string reflects that.
In your code, you would then consume it like this:
public JsonResult GetConsultationDescription(Int32 idService)
{
using(var entityDB = CreateConnection())
{
var motifDescription = entityDB.SYS_vwService.Where(s => s.IDLang == cultureName && s.IdService == idService && s.IdCie == idCie).FirstOrDefault();
var base64 = Convert.ToBase64String(motifDescription.ServiceImage);
var imgSrc = String.Format("data:image/gif;base64,{0}", base64);
var imageDecode = imgSrc;
if (base64 == "AA==")
{
imageDecode = "";
}
var result = new { motifDescription, imageDecode };
return Json(result, JsonRequestBehavior.AllowGet);
}
}
}
Another thing to remember and something that bites every newcomer is that you should not pass entities (objects from the context) to the views in models. This is because once the context is disposed, objects with navigational properties will break. There are ways around it but not recommended. Map the objects to models instead.
By the way, don't forget to call the SaveChanges() method after modifying entities or the database will not be updated with the changes.
You should really read very carefuly from some very good sources about DbContext: for example:
http://www.entityframeworktutorial.net/EntityFramework4.3/dbcontext-vs-objectcontext.aspx
https://msdn.microsoft.com/en-us/library/system.data.entity.dbcontext(v=vs.113).aspx
When you pass a DbContext to service or business class, you pass it as a Value not as a reference so you will loss all changes made in client code, the option is to pass it as a reference but it is not a safe and not a good practice but, if you instead pass the context as a delegate you are passing a reference to the object directly so its the best approch in order to keep your injected context with changes from business/service layers.
Here is the idea:
Option 1, pass it as reference, not recommended:
public void Call()
{
//Attach changes to context, Passing as reference
Process(ref _context);
//All changes attached to context
_context.SaveChanges();
}
public void Process(ref Cliniciel_WebRV_MasterEntities context)
{
var c = context();
//Get entites dbcontext
//Update entites
}
Option 2, pass it as a delegate, best approch:
public void Call()
{
//Attach changes to context, Passing as reference
Process(() => _context);
//All changes attached to context
_context.SaveChanges();
}
public void Process(Func<Cliniciel_WebRV_MasterEntities> context)
{
var c = context();
//Get entites dbcontext
//Update entites
}

ExecuteSqlCommand on seed to create trigger with EntityFramework throws SqlException

I'm trying to create a trigger to update a document number using the Seed() method in a ContextInitalizer with Entity Framework 6.0.2 and .Net 4. When I run the SQL separately the trigger is created; during context initialization an SqlException is thrown stating:
Incorrect syntax near the word 'TRIGGER'.
My sql script -- contained in /SQL/CreateOrderNumber.sql -- is:
CREATE TRIGGER [dbo].[CreateOrderNum]
ON [dbo].[Orders]
AFTER INSERT AS
BEGIN
SET NOCOUNT ON;
DECLARE #MaximumNumber int;
SET #MaximumNumber = (SELECT ISNULL(MAX(RIGHT([DocNumber],6)),0) FROM [Orders]);
UPDATE [Orders]
SET [DocNumber] = 'ORD-' + RIGHT('000000' + CAST((#MaximumNumber + 1) AS VARCHAR(8)), 6)
FROM inserted
WHERE [Orders].[Id] = inserted.Id;
END
And the following code recreates the error:
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;
using System.Data.SqlClient;
using System.IO;
using System.Reflection;
namespace TriggerCreationTest
{
class Program
{
static void Main(string[] args)
{
// Create the context, add an order, and save...
using (var context = new Context())
{
context.Orders.Add(new Order());
context.SaveChanges();
}
}
}
public class Context : DbContext
{
public Context() : base("TestDatabase")
{
Database.SetInitializer<Context>(new ContextInitializer());
}
public DbSet<Order> Orders { get; set; }
}
public class ContextInitializer : DropCreateDatabaseAlways<Context>
{
protected override void Seed(Context context)
{
// Get the file and read the text
var execPath = Assembly.GetExecutingAssembly().Location;
var createOrderNumPath = Path.Combine(execPath, #"..\SQL\CreateOrderNumber.sql");
var sql = File.ReadAllText(createOrderNumPath);
// Execute the CREATE TRIGGER on the database.
var emptyparams = new SqlParameter[] { new SqlParameter() };
context.Database.ExecuteSqlCommand(sql, emptyparams);
base.Seed(context);
}
}
public class Order
{
public Order() { }
[Key]
public int Id { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public string DocNumber { get; set; }
}
}
At this point I've attempted rewriting the CREATE TRIGGER script according to various sources around the web, but haven't had any success getting it to run.
After having worked around this, and paying attention to other projects, I returned to see whether upgrading to EntityFramework 6.1.0 would make a difference. It did not!
I did, however, find my mistake. I was passing an empty SqlParameter to the SqlCommand and this was causing it to fail. If instead I change the SqlParameter array to contain no elements:
public class ContextInitializer : DropCreateDatabaseAlways<Context>
{
protected override void Seed(Context context)
{
// Get the file and read the text
var execPath = Assembly.GetExecutingAssembly().Location;
var createOrderNumPath = Path.Combine(execPath, #"..\SQL\CreateOrderNumber.sql");
var sql = File.ReadAllText(createOrderNumPath);
// Execute the CREATE TRIGGER on the database.
// CHANGE emptyparams TO CONTAIN NO ELEMENTS
var emptyparams = new SqlParameter[] { };
context.Database.ExecuteSqlCommand(sql, emptyparams);
base.Seed(context);
}
}
The command executes as expected and the trigger is created.

Entity Framework 5 code-first not creating database

I'm trying to create a new database using the code first concept of Entity Framework. However when running the code the database isn't created (using the DropCreateDatabaseIfModelChanges setting), though the code is running fine. I'm seeing the following exception when I try to get something from the database.
My project is setup using a separate DataAccess layer with an generic service and repository construction. So all my entities, repository and also database context are in a separate project within the solution.
My global.asax file contains the following piece of code.
Database.SetInitializer(new DropCreateDatabaseIfModelChanges<MyContext>());
This should initialise a new database if it isn't there, right?
My database context class looks like this;
namespace Website.DAL.Model
{
public class MyContext : DbContext
{
public IDbSet<Project> Projects { get; set; }
public IDbSet<Portfolio> Portfolios { get; set; }
/// <summary>
/// The constructor, we provide the connectionstring to be used to it's base class.
/// </summary>
public MyContext()
: base("MyConnectionString")
{
}
static MyContext()
{
try
{
Database.SetInitializer<MyContext>(new DropCreateDatabaseIfModelChanges<MyContext>());
}
catch (Exception)
{
throw;
}
}
/// <summary>
/// This method prevents the plurarization of table names
/// </summary>
/// <param name="modelBuilder"></param>
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Conventions.Remove<System.Data.Entity.ModelConfiguration.Conventions.PluralizingTableNameConvention>();
}
}
}
I've created this class following several tutorials and articles on the internet. It's all new to me, but as far as I can see everything seems correct so far. So now the two entities I'm using. They're called 'Project' and 'Portfolio'. They look like this;
public class Portfolio
{
[Key]
public Guid Id { get; set; }
public String Name { get; set; }
public DateTime StartDate { get; set; }
public DateTime? EndDate { get; set; }
public bool IsPublished { get; set; }
public virtual ICollection<Project> Projects { get; set; }
}
And
public class Project
{
[Key]
public Guid Id { get; set; }
public DateTime StartDate { get; set; }
public DateTime? EndDate { get; set; }
public bool IsPublished { get; set; }
public String Title { get; set; }
}
The database I'm using is running on an external server, it came with the hosting provider I'm using. I've got a SQL Server database up and running and the connection string to the database is in the web.config of the website project. I've already tried removing the database and let the code recreate it, which unfortunately didn't work. Am I missing something obvious here? Or could it be a simple thing as access-rights to the server to create databases?
Note: When I run the Database-Update -Script command to generate SQL code, it seems that the correct SQL statements to create all tables are created.
UPDATE 1:
Okay, thanks to some comments I came a bit further. I've added two properties to my entities to force some changes and I've also created an custom initializer like this;
public class ForceDeleteInitializer : IDatabaseInitializer<MyContext>
{
private readonly IDatabaseInitializer<MyContext> _initializer = new DropCreateDatabaseIfModelChanges<MyContext>();
public ForceDeleteInitializer()
{
//_initializer = new ForceDeleteInitializer();
}
public void InitializeDatabase(MyContext context)
{
//This command is added to prevent open connections. See http://stackoverflow.com/questions/5288996/database-in-use-error-with-entity-framework-4-code-first
context.Database.ExecuteSqlCommand("ALTER DATABASE borloOntwikkel SET SINGLE_USER WITH ROLLBACK IMMEDIATE");
_initializer.InitializeDatabase(context);
}
}
I've also removed the initializer from the constructor of my context, so this mean i've remove this line of code;
Database.SetInitializer<MyContext>(new DropCreateDatabaseIfModelChanges<MyContext>());
After that i've added these three lines to my Global.asax file;
Database.SetInitializer(new ForceDeleteInitializer());
MyContext c = new MyContext();
c.Database.Initialize(true);
When debugging i'm now getting this exception;
This gives me the following information:
InnerException says: The provider didn't return a
ProviderManifestToken
InnerException in the InnerException says: "For this operation an connection to the masterdatabase is required. A connection can't be
made width the 'master'-database because the original connection is
opened and the references has been removed from the connection.
Please provide a non-open connection"
After these action the database is inaccessible, so most likely deleted..
What can possibly be done about this? It's most likely that I can't access the master database because my hostingprovider won't give me the proper access right ofcourse.
Since no other solution came by I decided to change my approach.
I've first created the database myself and made sure the correct SQL user was configured and I had access.
Then I removed the initializer and the code from the Global.asax file. After that I ran the following command in the Package Manager Console (since the layered design I had to select the correct project in the console);
Enable-Migrations
After the migrations where enabled and I made some last minute changes to my entities I ran the command below to scaffold an new migration;
Add-Migration AddSortOrder
After my migrations were created I ran the following command in the console and voila, the database was updated with my entities;
Update-Database -Verbose
To be able to seed the database when running the migration i've overridden the Seed method in my Configuraton.cs class, which was created when enabling the migrations. The final code in this method is like this;
protected override void Seed(MyContext context)
{
// This method will be called after migrating to the latest version.
//Add menu items and pages
if (!context.Menu.Any() && !context.Page.Any())
{
context.Menu.AddOrUpdate(
new Menu()
{
Id = Guid.NewGuid(),
Name = "MainMenu",
Description = "Some menu",
IsDeleted = false,
IsPublished = true,
PublishStart = DateTime.Now,
LastModified = DateTime.Now,
PublishEnd = null,
MenuItems = new List<MenuItem>()
{
new MenuItem()
{
Id = Guid.NewGuid(),
IsDeleted = false,
IsPublished = true,
PublishStart = DateTime.Now,
LastModified = DateTime.Now,
PublishEnd = null,
Name = "Some menuitem",
Page = new Page()
{
Id = Guid.NewGuid(),
ActionName = "Some Action",
ControllerName = "SomeController",
IsPublished = true,
IsDeleted = false,
PublishStart = DateTime.Now,
LastModified = DateTime.Now,
PublishEnd = null,
Title = "Some Page"
}
},
new MenuItem()
{
Id = Guid.NewGuid(),
IsDeleted = false,
IsPublished = true,
PublishStart = DateTime.Now,
LastModified = DateTime.Now,
PublishEnd = null,
Name = "Some MenuItem",
Page = new Page()
{
Id = Guid.NewGuid(),
ActionName = "Some Action",
ControllerName = "SomeController",
IsPublished = true,
IsDeleted = false,
PublishStart = DateTime.Now,
LastModified = DateTime.Now,
PublishEnd = null,
Title = "Some Page"
}
}
}
});
}
if (!context.ComponentType.Any())
{
context.ComponentType.AddOrUpdate(new ComponentType()
{
Id = Guid.NewGuid(),
IsDeleted = false,
IsPublished = true,
LastModified = DateTime.Now,
Name = "MyComponent",
PublishEnd = null,
PublishStart = DateTime.Now
});
}
try
{
// Your code...
// Could also be before try if you know the exception occurs in SaveChanges
context.SaveChanges();
}
catch (DbEntityValidationException e)
{
//foreach (var eve in e.EntityValidationErrors)
//{
// Console.WriteLine("Entity of type \"{0}\" in state \"{1}\" has the following validation errors:",
// eve.Entry.Entity.GetType().Name, eve.Entry.State);
// foreach (var ve in eve.ValidationErrors)
// {
// Console.WriteLine("- Property: \"{0}\", Error: \"{1}\"",
// ve.PropertyName, ve.ErrorMessage);
// }
//}
//throw;
var outputLines = new List<string>();
foreach (var eve in e.EntityValidationErrors)
{
outputLines.Add(string.Format(
"{0}: Entity of type \"{1}\" in state \"{2}\" has the following validation errors:",
DateTime.Now, eve.Entry.Entity.GetType().Name, eve.Entry.State));
foreach (var ve in eve.ValidationErrors)
{
outputLines.Add(string.Format(
"- Property: \"{0}\", Error: \"{1}\"",
ve.PropertyName, ve.ErrorMessage));
}
}
System.IO.File.AppendAllLines(#"c:\temp\errors.txt", outputLines);
throw;
}
}
The disadvantage at the moment is that I have to manually migrate with (only) 2 commands in the package manager console. But the same time, the fact that this doesn't happen dynamically is also good because this prevents possibly inwanted changes to my database. Further everything works just perfect.
+1 for the detailed question.
Check that your connection string is pointing at the correct database and add the authorization attributes like this to access your database:
<add name="PatientContext" providerName="System.Data.SqlClient" connectionString="Server=SQLSERVER2; Database=Patients; uid=PatientUser; password=123456; Integrated Security=False;" />
befor intialize it run following code to create your database:
context.Database.CreateIfNotExists();
So First Remove the Db Name from the constructor parameter on context class and provide Db_name on the connection string and then try to rebuild your solution and run the application it will create Database for you application.
For Example :
I am not passing the Db-Name on constructor parameter
public EmployeeContext()
: base()
{
Database.SetInitializer<EmployeeContext>(new DropCreateDatabaseIfModelChanges<EmployeeContext>());
}
and on the Connection string I am passing the Db-name like below.
<add name="EmployeeContext" connectionString="server=.; database=EFCodeFirstTPHInheritance; uid=sa;Password=crius#123;persistsecurityinfo=True" providerName="System.Data.SqlClient"/>
</connectionStrings>

How to check for duplicates before saving?

I'm having a heck of a time figuring out how to add entities like this to my db.
public class ThingWithListings
{
public virtual ICollection<Listing> Listings;
}
public class Listing
{
public int Id;
public virtual ListingData Data { get; set; } // has a FK to ListingData
public DateTime Creation { get; set; }
}
public class ListingData
{
public int listing_id; // PK
....
}
I'm retrieving a 'ThingWithLIstings' from another source and writing it to my db. The tricky part is that any number of Listings may report to the same ListingData. So when I add or update a ThingWithListings, I need to see if a ListingData already exists and if so, just use that one.
I'm new to EF, so I've been using the AddOrUpdate from Author Vickers' article here: Obviously, this doesn't work for this scenario and so I've tried for a day or so to figure out the right way to do this. I'll spare you all the story of my main failed attempts and hope someone can just tell me the right way to do this.
if (DatabaseContext.ListingData.Any(l => l.listing_id == myId))
{
//already exists
}
else
{
//do whatever
}
var newArrivals = new ThingWithListings();
newArrivals.Listings = new List<Listing>();
newArrivals.Listings.Add(
new Listing()
{
creation = DateTime.Now,
ListingData = new ListingData()
{
listing_id = 1
}
});
//another Listing with the same ListingData listing_id
newArrivals.Listings.Add(
new Listing()
{
creation = DateTime.Now,
ListingData = new ListingData()
{
listing_id = 1
}
});
//dummy id generator
int counter = 1;
using (var ctx = new Database1Entities())
{
//get the ListingData from the current db context
var dbListingData = ctx.ListingData;
// the new arrivals
foreach (Listing item in newArrivals.Listings)
{
//get the listing_id of a new arrival's ListingData
int id = item.ListingData.listing_id;
//get the ListingData from the db, if it exists
var listingDataFromDb = dbListingData.FirstOrDefault(i => i.listing_id == id);
//if not, create it and add it to the db
if (listingDataFromDb == null)
{
listingDataFromDb = new ListingData()
{
//use the new arrival's listing_id
listing_id = item.ListingData.listing_id
};
ctx.ListingData.Add(listingDataFromDb);
ctx.SaveChanges();
}
//add the Listing by using the listingDataFromDb, which now references the db ListingData
ctx.Listing.Add(new Listing()
{
id = counter++,
creation = item.creation,
ListingData = listingDataFromDb
});
ctx.SaveChanges();
}
}
I assume that besides the Data object reference you also have the primitive foreign key field listing_id in your Listing class. If not, I recommend adding it.
You could start by fetching the existing listing_ids in a list or array. That saves numerous database round trips later.
Then the process is really simple: for each Listing object that arrives check whether its listing_id occurs in the pre-fetched list:
If so, do nothing with ListingData - just add (or update) the Listing, including the listing_id property.
If not, add the Listing and set Listing.Data with the ListingData object, both as new (Added) objects. EF will set the keys.
(Note that this assumes that there are no concurrent users modifying ListingData, so it is safe to take a snapshot of the Id's)

Categories