简体   繁体   中英

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.

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
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM