1. Martin Eden
  2. FrameLog
  3. Issues
Issue #38 resolved

History.ChangesTo(model) returning null objects

Ian McIntosh
created an issue

Hi there, Sorry for the bombardment of tickets today, I'm getting there slowly.

I have managed to get FrameLog recording information when my entities are added/changed or deleted now thanks to your help.

I am now trying to view the history of changes for an object but am getting an "Object Reference Not Set To An Instance Of An Object"n error when calling the ChangesTo method and passing it a valid model. My code is:

        public IEnumerable<FrameLog.History.IChange<string, User>> GetBookDescriptionHistory(int id)
        {
            using (_db = new myDbContext())
            {
                var book = _db.Books.Find(id);
                var history = _db.HistoryExplorer.ChangesTo(book, b => b.description);
                return history.ToList();
            }
        }

It's probably something I'm missing but I can't see what it could be, and I've tried searching here and on StackOverflow with no joy, please can you help?

Thanks, Ian

Comments (41)

  1. Ian McIntosh reporter

    Hi Martin,

    Here is the stack trace I am getting:

    [NullReferenceException: Object reference not set to an instance of an object.]
       FrameLog.History.HistoryExplorer`2.apply(IObjectChange`1 change, TModel model) +842
       FrameLog.History.<applyChangesTo>d__6`1.MoveNext() +528
       System.Linq.Buffer`1..ctor(IEnumerable`1 source) +216
       System.Linq.<GetEnumerator>d__0.MoveNext() +110
       System.Collections.Generic.List`1..ctor(IEnumerable`1 collection) +381
       System.Linq.Enumerable.ToList(IEnumerable`1 source) +58
       _4_5_MVC_Test.BooksRepository.GetBookHistory(Int32 id) in c:\Users\ianm\Documents\Projects\Test Apps\4_5_MVC_Test\4_5_MVC_Test\BooksRepository.cs:67
       _4_5_MVC_Test.Controllers.FrameLogStuffController.ViewLogs(Int32 bookId) in c:\Users\ianm\Documents\Projects\Test Apps\4_5_MVC_Test\4_5_MVC_Test\Controllers\FrameLogStuffController.cs:66
       lambda_method(Closure , ControllerBase , Object[] ) +97
       System.Web.Mvc.ActionMethodDispatcher.Execute(ControllerBase controller, Object[] parameters) +14
       System.Web.Mvc.ReflectedActionDescriptor.Execute(ControllerContext controllerContext, IDictionary`2 parameters) +211
       System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary`2 parameters) +27
       System.Web.Mvc.Async.<>c__DisplayClass42.<BeginInvokeSynchronousActionMethod>b__41() +28
       System.Web.Mvc.Async.<>c__DisplayClass8`1.<BeginSynchronous>b__7(IAsyncResult _) +10
       System.Web.Mvc.Async.WrappedAsyncResult`1.End() +57
       System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeActionMethod(IAsyncResult asyncResult) +48
       System.Web.Mvc.Async.<>c__DisplayClass39.<BeginInvokeActionMethodWithFilters>b__33() +57
       System.Web.Mvc.Async.<>c__DisplayClass4f.<InvokeActionMethodFilterAsynchronously>b__49() +223
       System.Web.Mvc.Async.<>c__DisplayClass37.<BeginInvokeActionMethodWithFilters>b__36(IAsyncResult asyncResult) +10
       System.Web.Mvc.Async.WrappedAsyncResult`1.End() +57
       System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeActionMethodWithFilters(IAsyncResult asyncResult) +48
       System.Web.Mvc.Async.<>c__DisplayClass2a.<BeginInvokeAction>b__20() +24
       System.Web.Mvc.Async.<>c__DisplayClass25.<BeginInvokeAction>b__22(IAsyncResult asyncResult) +102
       System.Web.Mvc.Async.WrappedAsyncResult`1.End() +57
       System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeAction(IAsyncResult asyncResult) +43
       System.Web.Mvc.<>c__DisplayClass1d.<BeginExecuteCore>b__18(IAsyncResult asyncResult) +14
       System.Web.Mvc.Async.<>c__DisplayClass4.<MakeVoidDelegate>b__3(IAsyncResult ar) +23
       System.Web.Mvc.Async.WrappedAsyncResult`1.End() +62
       System.Web.Mvc.Controller.EndExecuteCore(IAsyncResult asyncResult) +57
       System.Web.Mvc.Async.<>c__DisplayClass4.<MakeVoidDelegate>b__3(IAsyncResult ar) +23
       System.Web.Mvc.Async.WrappedAsyncResult`1.End() +62
       System.Web.Mvc.Controller.EndExecute(IAsyncResult asyncResult) +47
       System.Web.Mvc.Controller.System.Web.Mvc.Async.IAsyncController.EndExecute(IAsyncResult asyncResult) +10
       System.Web.Mvc.<>c__DisplayClass8.<BeginProcessRequest>b__3(IAsyncResult asyncResult) +25
       System.Web.Mvc.Async.<>c__DisplayClass4.<MakeVoidDelegate>b__3(IAsyncResult ar) +23
       System.Web.Mvc.Async.WrappedAsyncResult`1.End() +62
       System.Web.Mvc.MvcHandler.EndProcessRequest(IAsyncResult asyncResult) +47
       System.Web.Mvc.MvcHandler.System.Web.IHttpAsyncHandler.EndProcessRequest(IAsyncResult result) +9
       System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +9634212
       System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +155
    

    Also, I don't know if these are related but for some reason FrameLog now isn't logging edits to my entities, which was working before. I'm currently looking into this and undoing any recent changes I've made, see where it broke.

    Thanks again, Ian

  2. Martin Eden repo owner

    That stack trace doesn't match up with the sample code provided. The method you provided has a method GetBookDescriptionHistory which calls the HistoryExplorer method to get the history of just one property. But your stack trace refers to GetBookHistory which looks to be calling the full history of an entity.

    Please provide the code that matches up with the stack trace.

  3. Ian McIntosh reporter

    Sorry, I was playing to try get it working since I posted the issue. The method for getting the full history is:

     public IEnumerable<FrameLog.History.IChange<Book, User>> GetBookHistory(int id)
            {
                using (_db = new myDbContext())
                {
                    var book = _db.Books.Find(id);
                    var history = _db.HistoryExplorer.ChangesTo(book);
                    return history.ToList();
                }
            }
    

    Cheers, Ian

  4. Ian McIntosh reporter

    Just an update on the editing entity issue I mentioned, I've undone my changes and it still isn't recording entity changes so I'll have to look into that a bit more, I don't think it's related to any of the changes I've had to make

  5. Ian McIntosh reporter

    Hi Martin,

    Thanks for this. I've not touched the Book model since creating it, except to add in the IClonable code, the columns have stayed the same since I first created it, which are: id (int), name (string), description (string)

    Cheers, Ian

  6. Martin Eden repo owner

    In that case, I'm not sure what is going wrong. I suggest that you download the source code and link your code to that, and then step through and find the line with the error. Then you can give it to me and I can provide a solution. Sorry to not provide a more low-effort solution.

  7. Ian McIntosh reporter

    When trying to get the full history the exception occurs in HistoryExplorer.cs line 46. When getting the history of a particular field, the exception occurs in HistoryExplorer.cs line 124. It looks to me like the change sets aren't being populated somehow. The exception and source trace are below:

    Object reference not set to an instance of an object.
    
    Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code. 
    
    Exception Details: System.NullReferenceException: Object reference not set to an instance of an object.
    
    Source Error: 
    
    
    Line 44:                 .OrderByDescending(p => p.ObjectChange.ChangeSet.Timestamp)
    Line 45:                 .AsEnumerable()
    Line 46:                 .Select(p => new Change<TValue, TPrincipal>(binder.Bind<TValue>(p.Value), p.ObjectChange.ChangeSet.Author, p.ObjectChange.ChangeSet.Timestamp));
    Line 47:         }
    Line 48:         /// <summary>
    
    Source File: c:\Users\ianm\Documents\Projects\Test Apps\FrameLogTestApp\FrameLog\History\HistoryExplorer.cs    Line: 46 
    
    Stack Trace: 
    
    
    [NullReferenceException: Object reference not set to an instance of an object.]
       FrameLog.History.HistoryExplorer`2.<ChangesTo>b__0(IPropertyChange`1 p) in c:\Users\ianm\Documents\Projects\Test Apps\FrameLogTestApp\FrameLog\History\HistoryExplorer.cs:46
       System.Linq.WhereSelectEnumerableIterator`2.MoveNext() +145
       System.Collections.Generic.List`1..ctor(IEnumerable`1 collection) +381
       System.Linq.Enumerable.ToList(IEnumerable`1 source) +58
       FrameLogTestApp.BookRepository.GetBookDescriptionHistory(Int32 id) in c:\Users\ianm\Documents\Projects\Test Apps\FrameLogTestApp\FrameLogTestApp\BookRepository.cs:78
       FrameLogTestApp.Controllers.HomeController.ViewLogs(Int32 id) in c:\Users\ianm\Documents\Projects\Test Apps\FrameLogTestApp\FrameLogTestApp\Controllers\HomeController.cs:68
       lambda_method(Closure , ControllerBase , Object[] ) +97
       System.Web.Mvc.ActionMethodDispatcher.Execute(ControllerBase controller, Object[] parameters) +14
       System.Web.Mvc.ReflectedActionDescriptor.Execute(ControllerContext controllerContext, IDictionary`2 parameters) +211
       System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary`2 parameters) +27
       System.Web.Mvc.Async.<>c__DisplayClass42.<BeginInvokeSynchronousActionMethod>b__41() +28
       System.Web.Mvc.Async.<>c__DisplayClass8`1.<BeginSynchronous>b__7(IAsyncResult _) +10
       System.Web.Mvc.Async.WrappedAsyncResult`1.End() +57
       System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeActionMethod(IAsyncResult asyncResult) +48
       System.Web.Mvc.Async.<>c__DisplayClass39.<BeginInvokeActionMethodWithFilters>b__33() +57
       System.Web.Mvc.Async.<>c__DisplayClass4f.<InvokeActionMethodFilterAsynchronously>b__49() +223
       System.Web.Mvc.Async.<>c__DisplayClass37.<BeginInvokeActionMethodWithFilters>b__36(IAsyncResult asyncResult) +10
       System.Web.Mvc.Async.WrappedAsyncResult`1.End() +57
       System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeActionMethodWithFilters(IAsyncResult asyncResult) +48
       System.Web.Mvc.Async.<>c__DisplayClass2a.<BeginInvokeAction>b__20() +24
       System.Web.Mvc.Async.<>c__DisplayClass25.<BeginInvokeAction>b__22(IAsyncResult asyncResult) +102
       System.Web.Mvc.Async.WrappedAsyncResult`1.End() +57
       System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeAction(IAsyncResult asyncResult) +43
       System.Web.Mvc.<>c__DisplayClass1d.<BeginExecuteCore>b__18(IAsyncResult asyncResult) +14
       System.Web.Mvc.Async.<>c__DisplayClass4.<MakeVoidDelegate>b__3(IAsyncResult ar) +23
       System.Web.Mvc.Async.WrappedAsyncResult`1.End() +62
       System.Web.Mvc.Controller.EndExecuteCore(IAsyncResult asyncResult) +57
       System.Web.Mvc.Async.<>c__DisplayClass4.<MakeVoidDelegate>b__3(IAsyncResult ar) +23
       System.Web.Mvc.Async.WrappedAsyncResult`1.End() +62
       System.Web.Mvc.Controller.EndExecute(IAsyncResult asyncResult) +47
       System.Web.Mvc.Controller.System.Web.Mvc.Async.IAsyncController.EndExecute(IAsyncResult asyncResult) +10
       System.Web.Mvc.<>c__DisplayClass8.<BeginProcessRequest>b__3(IAsyncResult asyncResult) +25
       System.Web.Mvc.Async.<>c__DisplayClass4.<MakeVoidDelegate>b__3(IAsyncResult ar) +23
       System.Web.Mvc.Async.WrappedAsyncResult`1.End() +62
       System.Web.Mvc.MvcHandler.EndProcessRequest(IAsyncResult asyncResult) +47
       System.Web.Mvc.MvcHandler.System.Web.IHttpAsyncHandler.EndProcessRequest(IAsyncResult result) +9
       System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +9634212
       System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +155
    

    I created a new project from scratch with a new database and used the models and context/context adapter code from your samples as the basis for my code but I still feel like I'm missing something obvious.

    I also rolled back my version of EF to 6.0.0 instead of 6.1 as that was causing some issues with the history explorer saying there was already a datareader open.

    Thanks again for the help, if you need anything more from me please let me know. Ian

  8. Ian McIntosh reporter

    Just an update in case it's useful. When stepping through my code and getting to HistoryExplorer.cs line 124, the db.ObjectChanges item count is zero, yet when stepping through the example code it has items each time it hits that bit of code.

    The changes are definitely being logged (the issue with edits not logging was a context one again, I was passing it an instance of the Book model from an MVC controller which it didn't like, even when using .attach, so I told it to re-grab the entity using the correct id and populate the details from the model) so I've got information in the database, and the report.sql script you provide brings information back ok.

  9. Ian McIntosh reporter

    Hi Martin,

    I've just managed to get some history showing but only for an object I create within the same context. To get my test working I used the following code:

    public IEnumerable<FrameLog.History.IChange<string, User>> GetBookDescriptionHistory(int id)
            {
                using (_db = new MyDbContext())
                {
                    var book = new Book() { description = string.Format("Log view test {0}", DateTime.Now.ToString("dd-MMM-yyyy HH:mm:ss")), name = "Log view test" };
                    _db.Books.Add(book);
                    var user = _db.Users.Find(1);
                    _db.Save(user);
                    var history = _db.HistoryExplorer.ChangesTo(book, b => b.description);
                    return history.ToList();
                }
            }
    

    The obvious problem with this is that I would like to be able to view the history logs as and when needed, and not just when an entity has been created within the same context. If I use the following code it doesn't work:

     public IEnumerable<FrameLog.History.IChange<string, User>> GetBookDescriptionHistory(int id)
            {
                using (_db = new MyDbContext())
                {
                    var book = _db.Books.Find(id);
                    var history = _db.HistoryExplorer.ChangesTo(book, b => b.description);
                    return history.ToList();
                }
            }
    

    Any ideas?

    Thanks, Ian

  10. Martin Eden repo owner

    Yeah, you are right, it does look like when trying to navigate from ObjectChange to ChangeSet the ChangeSet is missing.

    I assume that's not the case in your database? Every row in the ObjectChange table has a foreign key value for the ChangeSet it belongs to?

    If so, what happens when you try reading in the ObjectChanges through EntityFramework. e.g. _db.ObjectChanges.First().ChangeSet == null?

  11. Ian McIntosh reporter

    Hi Martin,

    Sorry for the delayed response. I ended up basically creating a stored procedure to grab the results like your report.sql script, then just created a class in my application which maps to the results. That way I could just use EF and grab the results when I need them as it's just another database call.

    I'll have a try when I can to see what happens when I read the object changes using EF and let you know.

  12. Martin Eden repo owner

    So if the IObjectChange.ChangeSet property is always null, that is the problem. I should probably make HistoryExplorer give a better exception in this case, but the fundamental problem is not in HistoryExplorer.

    There are two possibilities:

    1. ChangeSet is not getting set properly on save
    2. ChangeSet is not getting retrieved properly on load

    In my previous comment I asked you to check your database to see if ChangeSet was null there. Could you please do that? It will distinguish whether we are in case 1 or case 2.

  13. Ian McIntosh reporter

    Hi Martin,

    Changesets are getting created fine in the database, and I'm able to query the changesets table using either SQL or EF so I believe the issue is just down to the HistoryExplorer only pulling changes made within that use of the db context, ie within the same "using" statement etc

  14. Martin Eden repo owner

    No, that's not what I'm asking. Sorry for not being clear. I know that ChangeSets are being created fine and that you can query them. What I am interested is whether the ChangeSet property / foreign key is getting set on ObjectChanges.

    In this comment you said that ChangeSet was null when grabbing ObjectChanges from EF - not just in HistoryExplorer.

    But you still haven't told me: In the database, in the ObjectChanges table, is the ChangeSet column ever null?

  15. Ian McIntosh reporter

    Aahh, sorry, I misunderstood. No, the ChangeSet_Id column in the ObjectChanges table is never null and always relates correctly to a changeset.

    Have I got it right this time?

  16. Martin Eden repo owner

    Okay. So the ChangeSet foreign key is getting set correctly, but EntityFramework doesn't seem to be assigning the navigation property when pulling ObjectChanges from the database. That's odd. Something must be wrong with the mapping.

    Some thoughts:

    • Have you in some way enabled manual fetching of related entities? So EF doesn't do it automatically?
    • Is the ChangeSet property on your ObjectChange class virtual?

    Otherwise, maybe send me the source code of your ObjectChange class, and I'll take a look.

  17. Elliott Jenkins

    I think I may have recreated this. I downloaded the latest branch, run as normal. But then replaced the Program.cs code to get the history for the previously added Books and a similar thing seems to be happening:

    e.g. Run once as normal, then comment out the database being recreated and try to get the history

    static void Main(string[] args)
            {
                //Database.SetInitializer<ExampleContext>(new DropCreateDatabaseIfModelChanges<ExampleContext>());
                using (var db = new ExampleContext())
                {
                    //Console.WriteLine("CodeFirst is creating the database");
                    //db.Database.Delete();
                    //db.Database.Create();
    
                    //var user = new User() { Name = "TestUser" };
    
                    //bookDemo(db, user);
                    //publisherDemo(db, user);
    
                    var user = db.Users.Find(1);
                    var book = db.Books.Find(1);
    
                    var changes = db.HistoryExplorer.ChangesTo(book);
                    foreach (var change in changes)
                    {
                        Console.WriteLine(string.Format("{0} : {1} : {2}",
                            change.Author,
                            change.Timestamp,
                            change.Value.Title));
                    }
    
                }
            }
    

    The ChangeSet on the ObjectChange is null? Only other change has been to use localdb

  18. Elliott Jenkins

    I'm pretty new to code first but as I understand it, marking a property as virtual allows Lazy Loading to occur. So it can load the related entity.

    Therefore the ChangeSet does need to be virtual.

  19. Nicolas Vinicius Sroczynski

    Did not work for me

    using (DBEntities db = new DBEntities())
                {
    
                    Universidades universidade = new Universidades();
                    universidade = db.Universidades.Find(id);
    
                    var changes = db.HistoryExplorer.ChangesTo(universidade, x => x.Nome).ToArray();
    
                    foreach (var change in changes)
                    {
                        UniString uni = new UniString();
    
                        uni.text = string.Format("Autor {0}, Horário: {1} , Valor: ", change.Author, change.Timestamp, change.Value);
                    }
                }
    
  20. Erez Greenberg

    This is what my three classes look like

        public class ChangeSet : IChangeSet<ApplicationUser>
        {
            public int Id { get; set; }
            public DateTime Timestamp { get; set; }
            public virtual ApplicationUser Author { get; set; }
            public virtual List<ObjectChange> ObjectChanges { get; set; }
    
            IEnumerable<IObjectChange<ApplicationUser>> IChangeSet<ApplicationUser>.ObjectChanges
            {
                get { return ObjectChanges; }
            }
    
            void IChangeSet<ApplicationUser>.Add(IObjectChange<ApplicationUser> objectChange)
            {
                ObjectChanges.Add((ObjectChange)objectChange);
            }
    
            public override string ToString()
            {
                return string.Format("By {0} on {1}, with {2} ObjectChanges",
                    Author, Timestamp, ObjectChanges.Count);
            }
        }
    
        public class ObjectChange : IObjectChange<ApplicationUser>
        {
            public int Id { get; set; }
            public string TypeName { get; set; }
            public string ObjectReference { get; set; }
            public virtual ChangeSet ChangeSet { get; set; }
            public virtual List<PropertyChange> PropertyChanges { get; set; }
    
            IEnumerable<IPropertyChange<ApplicationUser>> IObjectChange<ApplicationUser>.PropertyChanges
            {
                get { return PropertyChanges; }
            }
            void IObjectChange<ApplicationUser>.Add(IPropertyChange<ApplicationUser> propertyChange)
            {
                PropertyChanges.Add((PropertyChange)propertyChange);
            }
            IChangeSet<ApplicationUser> IObjectChange<ApplicationUser>.ChangeSet
            {
                get { return ChangeSet; }
                set { ChangeSet = (ChangeSet)value; }
            }
    
            public override string ToString()
            {
                return string.Format("{0}:{1}", TypeName, ObjectReference);
            }
        }
    
        public class PropertyChange : IPropertyChange<ApplicationUser>
        {
            public int Id { get; set; }
            public virtual ObjectChange ObjectChange { get; set; }
            public string PropertyName { get; set; }
            public string Value { get; set; }
            public int? ValueAsInt { get; set; }
    
            IObjectChange<ApplicationUser> IPropertyChange<ApplicationUser>.ObjectChange
            {
                get { return ObjectChange; }
                set { ObjectChange = (ObjectChange)value; }
            }
    
            public override string ToString()
            {
                return string.Format("{0}:{1}", PropertyName, Value);
            }
        }
    

    Note the virtual keyword - that's what made it work for me

  21. Goran Sneperger

    Hi all, I'm having different kind of exception when trying to get history in the same way as Ian described in his comment from May 19th

    using (var db = new ProjectContext())
    {
        var project = db.Projects.FirstOrDefault(pr => pr.Id == id);
        var history = db.HistoryExplorer.ChangesTo(project);
    }
    

    I get The specified type member 'ChangeSet' is not supported in LINQ to Entities.

    It's set as virtual. Any thoughts?

  22. Log in to comment