5

I am working on a c# console application, and I am using entity framework 5.0 as the data access layer with sql server. now I want to track the changes and save them inside a log table. so to do so I am initiating 2 DbContext objects one for the business data while the other for the log data, as follow:

class Sync
    {
        static void Main(string[] args)
        {
            string syncResult = "Sync started";
            Entities entities = new Entities();//for business data
            Entities entities2 = new Entities();//for logs
            try
            {
              //code goes here
              entities.SaveChanges();
            }
            catch (Exception e)
            {
                syncResult = string.IsNullOrEmpty(e.Message) ? "Error" : e.Message;

            }
            entities.Dispose();
            entities2.LogHistories.Add(new LogHistory() { Description = syncResult });
            entities2.SaveChanges();
            entities2.Dispose(); 

now i provided separate DbContext objects for my logs , for the following reason/s:-

  1. if my first entity object is not able to save the changes , due to any reason such as unhandled validation error, or trying to hack the system, etc.. then the second entities2 will still be able to save the log entry. let take this example. let say I am integrating with a 3rd part API and I am expecting them to return JSON in a specific format.. now let assume that the return json object had missing data, in this case when I try adding the object it will raise and exception ... now since I am having separate entity object for the logs then the log entry will be saved (will not be affected by any business data exception). but if I was having a single DBContext object then the log entry will fail to save since I am unable to save the business data .. so my question is if initiating 2 DBContext objects one for logs and the other for business data a valid approach to follow, or it is a bad decision to follow?
Cœur
  • 37,241
  • 25
  • 195
  • 267
John John
  • 1
  • 72
  • 238
  • 501
  • 2
    Just move your logging to another method (Log(string description)), create new context there (don't pre-create it like in your example - why?) and done. Of course logging is usually not done like this, because if you save logs to database that should at least be asynchronous and queued to not slow down your main operations, but that's another story. – Evk Apr 19 '16 at 17:18
  • @Evk so if i move my logging logic to a separate class/method, then this will be a valid approach,, but having 2 DBContext classes inside the main method is the problem !! – John John Apr 26 '16 at 12:46
  • 1
    Well it's not a "problem". Having all that code right among the other logic is just bad practice, so at least moving it out to another method will make code more readable and maintainable. But what will be even better is if you move log processing to another class, which runs separate thread and has a queue with pending log messages. It then inserts logs one by one in your database, retrying if connection is for some reason lost, and main thing it does that _asynchronously_, independent of your other code. So you have error, you put that into queue and you continue to do useful work. – Evk Apr 26 '16 at 12:52

4 Answers4

6

Don't use 2 contexts, you should use a logger (for example log4net with AdoNetAppender). As Evk points out in the comment down below, EF automatically wraps everything up into a transaction when you call SaveChanges(), so as soon as an error occurs, nothing will be committed to the database and you can log the error. For example:

static void Main(string[] args)
{
    ILog log = LogManager.GetLogger("Entities");

    using(var ctx = new Entities())
    {
        try
        {
            ...
            ctx.SaveChanges();
        }
        catch(Exception ex)
        {
           log.Error(ex.Message);
        }
    }
}

This way your application stays clean, you will only update when everything succeeds when you call SaveChanges() at the end of the using block, and only log when an exception happens. Also it's better with the using blocks to always dispose of your context, even if an unexpected exception would occur. The logger will take care of writing the errors to the database in another thread without slowing down your program. Perhaps you can try out NLog, I've heard it's better (= easier to configure/use) than log4net but I still have to give it a try myself.

Alexander Derck
  • 13,818
  • 5
  • 54
  • 76
  • 1
    If OP uses two contexts because he logs to database, and you suggest to use separate logger, why use transaction scope here? ctx.SaveChanges already runs in transaction, and you don't need to call it twice since you don't log to database any more. – Evk Apr 19 '16 at 19:50
  • @Evk Hmm true, forgot EF automatically uses transactions – Alexander Derck Apr 19 '16 at 20:02
  • 1
    @AlexanderDerck so how my LogManager will write to the DB !! it need to use Entity framework.. and to be able to log an exception then it need to have separate DBContext is this correct ? – John John Apr 26 '16 at 12:44
  • 1
    @johnG That's the thing, for a simple task as logging (always the same format, always the same parameters) you don't need an ORM like Entity framework. The loggers use ADO.NET directly and execute sql commands (an example of command from log4net: ``). So you don't have to worry about dbcontexts at all, as soon as you configured your logger with the right connectionstring/commands you're good to go. – Alexander Derck Apr 26 '16 at 12:58
1

Having multiple contexts for a single database can be useful if your database contains multiple database schemas and you want to handle each of them as a separate self contained area. Its not clear from your code / requirement if this is the case.

If the tables you are working with are in the same database & same schema, then I can't see a reason for you to use two DbContexts. Even if you have a validation error or exception, you can still save to your log table with the same context.

If you are trying to log errors and / or other relevant information, why not use log4net to do your logging?

EDIT

In my opinion, logging should be independent of your normal transactions so you don't have to think about this scenario. That being said, in a transaction you can save and log, but also log if there is an exception. Unless I'm missing something, I still don't see the need for 2 DBContexts.

Take a look at the following for some guidance around transactions.

https://msdn.microsoft.com/en-us/data/dn456843.aspx

William Xifaras
  • 5,212
  • 2
  • 19
  • 21
  • 1
    i did not get your point correctly. now let say i am unable to save my first entity due to text field being too long (i am sending 100 chars to the DB while the field on the databse is only 20 char), in this case i will not be able to save the log entry since i am unable to save the business data .. while if i have two entities i can save the log entry even is the first entity is raising an exception .. did u get my point ? – John John Apr 11 '16 at 13:07
  • 1
    It wasn't clear whether your logging was related to logging exceptions or just info. Do you want to log if you are unable to save business data? Is logging part of the same transaction? – William Xifaras Apr 11 '16 at 13:09
  • 1
    now let say i first deploy my application,, and i want to log everything . now let assume we have a bug in the code, where there is a field inside the Emp table named FNAME with varchar(20) ,, but our application is ok with sending 25 characters to the DB (this is a bug !!) . then when i a user try to save a new EMP record, and he enters 25 chars inside the FNAME and click on save ,, then an exception will be raised, – John John Apr 11 '16 at 13:24
  • 1
    ... and if i am using single DBContext then my log entry will not be saved since the emp record can not be saved.. but if i am using two DBContext then my log entry will be saved , and the log will detail that the exception that was raised when a user try to insert an new emp and will log the details,, so we call go through the exception and find that the application did not prevent the user to enter 25 chars for the EMP name – John John Apr 11 '16 at 13:25
  • 1
    If you are using one transaction for the business and log save, then yes - the transaction as a whole will fail and nothing will get logged. That being said you can try to save the business data and if successful log. If there is an exception, you catch it and log. You don't need to 2 DBContexts for that. – William Xifaras Apr 11 '16 at 13:27
  • so returning back to my question is using 2 DBContext in this case a valid approach to follow, or it is a bad decision to follow? – John John Apr 11 '16 at 13:42
  • Its unnecessary as my answer clearly states. Make sense? – William Xifaras Apr 11 '16 at 13:44
  • You can still log the exception in the catch block. Unless there is something in the database that would prevent that like a foreign key constraint which isn't clear from your question. – William Xifaras Apr 11 '16 at 13:54
0

There are some problems.

  1. We keep running, if we face with exception on data entry (it can be, if any state isn't affected). But if we get exception at logging we don't handle ?

  2. What our business logic is doing ? If we get wrong json data our business logic should check and handle it.

    Ok let's say json is valid, it passes business logic. We have string field on db with varchar(20) but the data has 25 characters. So what our model validation is doing ?

    You should handle these things on upper levels.

  3. It's violate Single Responsibility. This method should have just one responsiblity which is saving entity to db. It shouldn't be responsible for logging also. You should implement it at another class.

    Because of this, your problem occurs. You try to give two reponsibilities to the method. Then you try to choose 1 dbcontext or 2 dbcontexts I need. If you follow Single Responsibility this problem wouldn't occur.

You should implement a logger service. Your classes shouldn't know or care how logger service handles it. You can save it to db, file or cloud like loggly. Additionally your logger service shouldn't share same context with other classses.

Erkan Demirel
  • 4,302
  • 1
  • 25
  • 43
  • so how my logger service will write to the DB !! it need to use Entity framework.. and to be able to log an exception then it need to have seperate DBContext is this correct ? – John John Apr 26 '16 at 12:43
  • Yes use another db context object otherwise your logger can save/edit entities to db which is not related with logger(if service and logger share/use same contex). Second way a bit dirty you can use same context but before adding log to context you should detach all of the modified and added things to be sure your logger service just ads logger entity. Plus if you want clean code you can use [Postsharp for Exception handling](https://michaelllucas.wordpress.com/2014/11/05/exception-handling-using-postsharp-c/) – Erkan Demirel Apr 26 '16 at 12:53
-2

Entity Framework 5 code first migrations is only able to manage a single DbContext per physical database instance. Entity Framework 6 code first migrations is able to manage multiple DbContext per physical database instance.

Check out pretty nice example at:

http://www.dotnet-tricks.com/Tutorial/entityframework/2VOa140214-Entity-Framework-6-Code-First-Migrations-with-Multiple-Data-Contexts.html