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
}
Related
I've got method AddOrUpdateFruitMetaData which should add or update record in FruitMetaData table.
Unfortunately I'm keep getting an errors:
Violation of PRIMARY KEY constraint 'PK_FruitMetaData'. Cannot insert duplicate key in object 'dbo.FruitMetaData'. The duplicate key value is (0, COLOR). The statement has been terminated.
How this is even possible even when I'm trying to find existing record before?
Also using AddOrUpdate() from System.Data.Entity.Migrations instead of Add() is not helping me.
Call stack looks that:
In my main class in Parallel.ForEach with multiple for loop looking like that:
Parallel.ForEach(args)
{
for(something)
{
var fruit = new Fruit();
fruit.FruitId = uniqueId;
UpdateOrCreateFruitMetaData(fruit, "Color", "Blue")
}
}
I'm calling method:
private void UpdateOrCreateFruitMetaData(Fruit fruit, string metaType, string value)
{
var fruitMetaData = new FruitMetaData();
fruitMetaData.FruitId = fruit.FruitId;
fruitMetaData.MetaType = metaType;
fruitMetaData.Value = value;
using (var db = Context.DB)
{
db.AddOrUpdateFruitMetaData(fruitMetaData);
}
}
That method is using Context.DB which is object containing new DBEntity() and also Dispose(). So my Entity Context is disposed every time.
Then I'm calling next method AddOrUpdateFruitMetaData():
AddOrUpdateFruitMetaData(FruitMetaData fruitMetaData)
{
lock (thisLock)
{
try
{
var fmd = db.FruitMetaData.Where(x => x.FruitId == fruitMetaData.FruitId)
.Where(x => x.MetaType == fruitMetaData.MetaType)
.FirstOrDefault();
if (fmd == null)
db.FruitMetaData.Add(fruitMetaData);
else
fmd.Value = fruitMetaData.Value;
db.SaveChanges();
}
catch (Exception ex)
{
Log.Error($"AddOrUpdateFruitMetaData. FruitId:{fruitMetaData.FruitId} MetaType:{fruitMetaData.MetaType}", ex);
}
}
}
[EDIT] To explain using (var db = Context.DB) :
My Context class contains DbData DB Property which looks like that :
public DbData DB
{
get { return new DbData(); }
}
And DbData class looks like that:
public class DbData : IDisposable
{
private DBEntity db;
public DbData()
{
db = new FruitDBEntity();
}
// This class contains that problematic method
AddOrUpdateFruitMetaData(FruitMetaData fruitMetaData)(...)
}
So each time I'm getting Context.DB property I am actually creating new Entity Context.
1.
I have this method in my repository class
public class VariablesRepository : IVariablesRepository
{
readonly DBContextClass _context = DBContextClass.Current;
public Variables Find(string name)
{
return _context.Variables.FirstOrDefault(c => c.Name.ToLower().Equals(name.ToLower())) ?? new Variables();
}
}
2.
I also have this static class
public class Defaults {
private static VariablesRepository _variablesRepository;
static Defaults() {
_variablesRepository = new VariablesRepository();
}
public class MOSScheduleTypes
{
private static int _tryValue;
public static readonly int OneTime = int.TryParse(_variablesRepository.Find("MOSScheduleTypes.OneTime").Value, out _tryValue)
? _tryValue
: 1;
}
}
3.
Now if i do this somewhere in code: for example
if(someValue == Defaults.MOSScheduleTypes.OneTime)
{
//some code here....
}
I get the error : The operation cannot be completed because the DbContext has been disposed
The error is caused by the data context being disposed. As you haven't shown any code for disposing of...anything, it means that there is some code somewhere else that is disposing of that same data context.
Note that data contexts are designed to be short lived; it's code smell to see you holding onto the data context as you are for later use, particularly in a static variable that appears to be long lived.
Instead of grabbing the current data context once you should be grabbing it right when you need it. Since you won't be storing the data context as an instance field of VariablesRepository, the method can also be made static (it will have no instance data to use).
public class VariablesRepository : IVariablesRepository
{
public static Variables Find(string name)
{
return DBContextClass.Current.Variables.FirstOrDefault(c => c.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase)) ?? new Variables();
}
}
public class Defaults
{
public class MOSScheduleTypes
{
private static int _tryValue;
public static readonly int OneTime = int.TryParse(VariablesRepository.Find("MOSScheduleTypes.OneTime").Value, out _tryValue)
? _tryValue
: 1;
}
}
Ok, solved this issue by using raw ado.net code, so i just changed the find method from entity framework linq code to raw ado.net data access code.
The error didn't occur any more, if anyone has a better solution using entity framework, please share. The find method rewritten, code below:
public Variables Find(string name)
{
var objVariable = new Variables();
using (var conn = new SqlConnection(ConfigurationManager.ConnectionStrings["ConnectionStringValue"].ToString()))
{
conn.Open();
var sql = #" select top 1 *
from SomeTable
where Column1 = #Name";
try
{
var cmd = new SqlCommand(sql, conn);
cmd.CommandType = CommandType.Text;
cmd.Parameters.Add("#Name", SqlDbType.NVarChar).Value = name;
SqlDataReader dr = cmd.ExecuteReader();
if (dr.HasRows)
{
if (dr.Read())
{
objVariable.Column1 = dr["Column1"].ToString();
objVariable.Column2 = dr["Column2"].ToString();
}
}
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
}
return objVariable;
}
The reason for this is that your LINQ query is deferred, in other words it is not run in your "VariablesRepository" but only in the calling code. So the DBContext has already been disposed of when it is executed. You can force it to execute immediately by adding .ToList() so changing
return _context.Variables.FirstOrDefault(c => c.Name.ToLower().Equals(name.ToLower())) ?? new Variables();
to
return _context.Variables.ToList().FirstOrDefault(c => c.Name.ToLower().Equals(name.ToLower())) ?? new Variables();
is it possible in Db4o to load new objects into persistent IObjectContainer?
I have a desktop application which opens one connection (IObjectContainer) when started. if I query all objects with:
var objects = from DummyClass foo in session
select foo
it selects all objects perfectly. However, if another client adds new classes after this, the same query still selects the same objects, without new ones.
I also know about:
session.Ext().Refresh(obj, int.MaxValue);
but I don't have even not activated references to new objects so there. How to refresh new objects?
Just note: I don't want to open/close session every time I need some data, I want to take advantage of OODB (Transparent activation, object persistance since loaded etc.)
Thank you
UPDATE (code example for better understanding)
// store one class to fill database with some data
using (var mainSession = SessionFactory.CreateNewConnection())
{
mainSession.Store(new DummyClass());
mainSession.Commit();
}
using (var mainSession = SessionFactory.CreateNewConnection())
{
// returns one object
var objects = from DummyClass foo in session
select foo;
using (var secondSession = SessionFactory.CreateNewConnection())
{
secondSession.Store(new DummyClass());
secondSession.Commit();
}
// this loop reload objects known for mainSession (which is not new object)
foreach (var obj in objects2)
{
mainSession.Ext().Refresh(obj, int.MaxValue);
}
// new DummyClass is commited but still not visible (Read-Commited isolation)
// returns one object
var objects2 = from DummyClass foo in session
select foo;
}
using (var mainSession = SessionFactory.CreateNewConnection())
{
// returns two objects
var objects = from DummyClass foo in session
select foo;
}
I need something like:
// refresh all objects of DummyClass
session.Ext().Refresh(typeof(DummyClass), int.MaxValue);
You may use Commited events:
using Db4objects.Db4o;
using Db4objects.Db4o.Events;
using Db4objects.Db4o.IO;
using Db4objects.Db4o.Ext;
namespace PushedUpdates
{
class Program
{
static void Main()
{
var config = Db4oEmbedded.NewConfiguration();
config.File.Storage = new MemoryStorage();
var container = Db4oEmbedded.OpenFile(config, "IN-MEMORY");
var client = container.Ext().OpenSession();
var clientEvents = EventRegistryFactory.ForObjectContainer(client);
clientEvents.Committed += (s, a) =>
{
foreach(IObjectInfo added in a.Added)
{
System.Console.WriteLine(added.GetObject());
}
};
container.Store(new Item { Value = 1 } );
container.Commit();
container.Store(new Item { Value = 2 });
container.Commit();
container.Store(new Item { Value = 3 });
container.Commit();
client.Close();
container.Close();
}
}
class Item
{
public int Value { get; set; }
public override string ToString()
{
return "" + Value;
}
}
}
Did your client call the commit() method after storing the data? Otherwise the new data will not be available for other clients.
I am trying to experiment with RhinoMocks, where I have to say I am a newbie and probably I don't get some obvious thing here. What I'm doing is something like :
[TestMethod]
public void SaveResponsibleUserFromChangeset()
{
var action = mocks.StrictMock<GenomeAction>();
var changeset = new ActionChangeset();
changeset.ResponsibleUser = new ChangeableProperty<UserIdentity>("Administrator") {IsChanged = true};
changeset.MarkAll(true);
using(mocks.Record())
{
Expect.Call(action.ResponsibleUser).SetPropertyAndIgnoreArgument();
}
using(mocks.Playback())
{
var persistor = new ActionPersistor(new MockIdentityResolver());
persistor.SaveActionChanges(changeset, action);
}
action.VerifyAllExpectations();
}
private class MockIdentityResolver : IIdentityResolver
{
public GenomeUser GetUser(UserIdentity identity)
{
var user = mocks.DynamicMock<GenomeUser>();
user.Username = identity.Username;
return user;
}
}
The intention is to have a very simple test which checks whether the SaveActionChanges method sets the ResponsibleUser property. As a part of this, it needs to resolve the user identity using the resolver, for which I have provided a mock implementation. Unfortunately, it seems I can't just return back another mock within the Playback mode, because it says (on the closing bracket of the second using) that The action is invalid when the object (of type GenomeUser) is in record state.
Any ideas of what is causing the trouble and how to overcome it ?
I think you need to create you new MockIdentityResolver() outside the mocks.Playback().
[TestMethod]
public void SaveResponsibleUserFromChangeset()
{
var action = mocks.StrictMock<GenomeAction>();
var changeset = new ActionChangeset();
var identityResolver;
changeset.ResponsibleUser = new ChangeableProperty<UserIdentity>("Administrator") {IsChanged = true};
changeset.MarkAll(true);
using(mocks.Record())
{
Expect.Call(action.ResponsibleUser).SetPropertyAndIgnoreArgument();
identityResolver = new MockIdentityResolver()
}
using(mocks.Playback())
{
var persistor = new ActionPersistor(identityResolver);
persistor.SaveActionChanges(changeset, action);
}
action.VerifyAllExpectations();
}
private class MockIdentityResolver : IIdentityResolver
{
public GenomeUser GetUser(UserIdentity identity)
{
var user = mocks.DynamicMock<GenomeUser>();
user.Username = identity.Username;
return user;
}
}
you should look at using the AAA syntax, it seems to be generally accepted that it's a clearer way of using stuff.
I have a very simple table and I can happily query it using LINQ To SQL, but when I do any save/updates the GetChangeSet method on my datacontext is always empty.
My code is very simple (concatenated code from various classes):
public static EntitiesDataContext EntitiesContext
{
get { return new EntitiesDataContext("Data Source=ANTSLAPTOP\\sqlexpress;Initial Catalog=nmncouk_d000;Integrated Security=True"); }
}
public static void Save(Price price)
{
EntitiesContext.Prices.InsertOnSubmit(price);
EntitiesContext.SubmitChanges();
}
[Test]
public void SavePrice()
{
Price price = new Price();
price.Lower = 1;
price.Upper = 2;
price.PricePerDay = 10;
Price.Save(price);
Assert.AreNotEqual(price.PriceId, 0);
}
The problem is with your EntitiesDataContext method. Although it's static, it's returning a new DataContext on each call. So you are calling insertonsubmit and submitchanges against different contexts.
Adjust your save method to use the same context and all should be well.