2

I have a many to many relationship between Tickets and Tags and can add tags to tickets without an issue. However I'm unable to update/remove tags from Tickets.

Test

[TestMethod]
        public void TagRepository_UpdateTicketWithTwoTagsToNone_TicketHasZeroTag()
        {
            // Arrange
            var ticketId = new Guid("54E86203-71F9-E411-80E5-000C29193DF7");
            var selectedTags = "";

            using (var context = new TicketModelContext())
            {
                using (new TransactionScope())
                {
                    var ticketToUpdate = context.Tickets.Include(t=>t.Tags).First(t => t.TicketId == ticketId);
                    Assert.AreEqual(0, ticketToUpdate.Tags.Count);

                    ticketToUpdate.Tags.Add(context.Tags.Find(new Guid("D1757675-A06C-4C1F-9DAD-03EE00BB1100")));
                    ticketToUpdate.Tags.Add(context.Tags.Find(new Guid("96C66A97-9C3E-4B15-BD70-A4C832EEDE8B")));
                    context.SaveChanges();

                    var setupTicket = context.Tickets.Single(t => t.TicketId == ticketId);
                    Assert.AreEqual(2, setupTicket.Tags.Count);


                    // Act
                    new TagRepository().UpdateTicketTags(ticketId, selectedTags);

                    // Assert
                    var updatedTeicket = context.Tickets.Include(t => t.Tags).First(t => t.TicketId == ticketId);
                    Assert.AreEqual(0, updatedTeicket.Tags.Count);
                    // HERE I EXPECT 0 BUT GET 2
                }
            }
        }

Repository Method

public void UpdateTicketTags(Guid ticketId, string selectedTags)
        {
            var tags = new List<Tag>();

            using (var context = new TicketModelContext())
            {
                if (!String.IsNullOrEmpty(selectedTags))
                {
                    foreach (var selectedTag in selectedTags.Split(','))
                    {
                        tags.Add(context.Tags.Find(new Guid(selectedTag)));
                    }
                }

                var ticketToUpdateTags = context.Tickets.Find(ticketId);

                context.Entry(ticketToUpdateTags).Collection(t => t.Tags).Load();

                ticketToUpdateTags.Tags = tags; // I EXPECT TAGS TO BE EMPTY IN THIS TEST
                context.SaveChanges();
            }
        }

SQL outputted by command context.Database.Log = s => System.Diagnostics.Debug.WriteLine(s);

DELETE [dbo].[TagTickets]
WHERE (([Tag_TagId] = @0) AND ([Ticket_TicketId] = @1))


-- @0: '96c66a97-9c3e-4b15-bd70-a4c832eede8b' (Type = Guid)

-- @1: '54e86203-71f9-e411-80e5-000c29193df7' (Type = Guid)

-- Executing at 26/05/2015 13:16:39 +01:00

-- Completed in 24 ms with result: 1



DELETE [dbo].[TagTickets]
WHERE (([Tag_TagId] = @0) AND ([Ticket_TicketId] = @1))


-- @0: 'd1757675-a06c-4c1f-9dad-03ee00bb1100' (Type = Guid)

-- @1: '54e86203-71f9-e411-80e5-000c29193df7' (Type = Guid)

-- Executing at 26/05/2015 13:16:39 +01:00

-- Completed in 21 ms with result: 1

I have spent hours trying different variations of code with different tests and all have the same outcome. Any help would be much appreciated!

David Hawkins
  • 959
  • 1
  • 8
  • 29
  • I am not sure why you expect tags to be empty. Didn't you create 2 new tags on line "ticketToUpdate.Tags.Add(...)"? So the behavior your are experiencing is correct in my opinion. – Parth Shah May 26 '15 at 11:20
  • I did create two tags in the test to start with, but the test is to then have the repository remove them. In the repo I get the ticket, load it's tags and then set the tags to an empty List of tags ticketToUpdateTags.Tags = tags and then save the changes. I expect to then get the ticket again in the test with 0 tags. – David Hawkins May 26 '15 at 11:53
  • Okay but setting a property to an empty list doesn't issue delete statement on the tags. – Parth Shah May 26 '15 at 11:55

3 Answers3

1

Basically the reason why your test wasn't working is because changes that occur over different contexts in Entity Framework are not reflected. Even though you used a TransactionScope, if you call SaveChanges() on inner context (happening in your UpdateTicketTags()), then those changes will not be reflected on the outer context.

I usually prefer breaking actions down into smaller chunks instead of one long context, so that

  • it simulates how real world actions can occur in your application (e.g. if this is an ASP.NET MVC application, multiple controllers are processing multiple requests that may or may not be modifying the same table) and
  • it gets me the most recent information for each object.

    [TestMethod]
    public void TagRepository_UpdateTicketWithTwoTagsToNone_TicketHasZeroTag()
    {
        // Arrange
        var ticketId = new Guid("54E86203-71F9-E411-80E5-000C29193DF7");
        var selectedTags = "";
    
        using (var context = new TicketModelContext())
        {
            var ticketToUpdate = context.Tickets.Include(t=>t.Tags).First(t => t.TicketId == ticketId);
            Assert.AreEqual(0, ticketToUpdate.Tags.Count);
    
            ticketToUpdate.Tags.Add(context.Tags.Find(new Guid("D1757675-A06C-4C1F-9DAD-03EE00BB1100")));
            ticketToUpdate.Tags.Add(context.Tags.Find(new Guid("96C66A97-9C3E-4B15-BD70-A4C832EEDE8B")));
            context.SaveChanges();
    
            var setupTicket = context.Tickets.Single(t => t.TicketId == ticketId);
            Assert.AreEqual(2, setupTicket.Tags.Count);
        }
    
        // Act
        new TagRepository().UpdateTicketTags(ticketId, selectedTags);
    
        using (var context = new TicketModelContext())
        {
            // Assert
            var updatedTeicket = context.Tickets.Include(t => t.Tags).First(t => t.TicketId == ticketId);
            Assert.AreEqual(0, updatedTeicket.Tags.Count);
        }
    }
    
Parth Shah
  • 1,882
  • 1
  • 18
  • 28
  • I don't want to delete the tag itself I want to delete it association with the ticket. My code appears to be doing the SQL I expect but back in the test it does not reflect the changes my by the repository. – David Hawkins May 26 '15 at 12:29
  • I suspect the reason why the change is not reflected is because the context under which you delete the association between tags and tickets is different than the context under which you are retrieving the tag. – Parth Shah May 26 '15 at 12:33
  • They are different context but I'm saving the changes in each and the second context I suspect should be wrapped in the first contexts transaction. What's weird is if I create a test and add tag to the ticket via the repository it works fine in the outer test checking the resulting ticket. – David Hawkins May 26 '15 at 12:41
  • I'll try your re-factored test. – David Hawkins May 26 '15 at 12:42
  • That works fine, THANKS! Now I just need to figure out why the transaction is causing the issue as I need it to roll back my changes. – David Hawkins May 26 '15 at 12:45
  • @DavidHawkins could you try moving the TranscationScope out of the context declaration? The example on MSDN shows the TransactionScope was declared first and then the context was declared. https://msdn.microsoft.com/en-us/data/dn456843.aspx#transactionScope – Parth Shah May 26 '15 at 12:48
  • 1
    @ParthShah Would you mind adding some explanation to make this more instructive? (It has to do with objects present in the first context's cache). Code-only answers are not well-received at StackOverflow. – Gert Arnold May 26 '15 at 12:54
  • I moved my transaction outside by context and still have the issue with the inner context changes not being seen... As I need this transaction to rollback all changes across inner contexts are there any further suggestions on how to achieve this. The test is then repeatable without having to worry if the data has been put back correctly. – David Hawkins May 26 '15 at 13:28
  • @DavidHawkins unfortunately I am not the most qualified person to assist you with TransactionScope as I have not used this construct much. However, I would recommend to read this post: http://stackoverflow.com/a/3653392/2310818. The key takeaway is for each context, any changes made to an entity over the lifetime of that context from an external source (like another context or interacting with database yourself) will not be reflected automatically. You have to manually refresh that entity. – Parth Shah May 27 '15 at 03:16
  • How to refresh an entity? http://stackoverflow.com/questions/6126845/refreshing-data-using-entity-framework – Parth Shah May 27 '15 at 03:17
0

Hmm I'm not sure what kind of error you are getting, but I would guess you have set MultipleActiveResultSets as false in web.config. Try adding this to web.config MultipleActiveResultSets=True to your connection string. That will enable EF to query while retrieving data etc. From my point of view your code looks fine.

"Multiple Active Result Sets (MARS) is a feature that works with SQL Server to allow the execution of multiple batches on a single connection." https://msdn.microsoft.com/en-us/library/h32h3abf%28v=vs.110%29.aspx

Jon Koivula
  • 814
  • 1
  • 11
  • 21
  • Just tried this and confirmed the context had a connection string with MARS enabled but still the same result. I suspect it may be the transaction causing the issue. – David Hawkins May 26 '15 at 12:21
0

I have re-structured my test code to remove a repository call whilst inside a database context and moved my transaction scope outside to achieve what I needed. The main learning point is don't try and make changes to the database inside a context by calling another context.

[TestMethod]
        public void TagRepository_UpdateTicketWithTwoTagsToNone_TicketHasZeroTag()
        {
            // Arrange
            var ticketId = new Guid("54E86203-71F9-E411-80E5-000C29193DF7");
            var selectedTags = "";

            using (new TransactionScope())
            {
                using (var context = new TicketModelContext())
                {

                    var ticketToUpdate = context.Tickets.Include(t => t.Tags).First(t => t.TicketId == ticketId);
                    Assert.AreEqual(0, ticketToUpdate.Tags.Count);

                    ticketToUpdate.Tags.Add(context.Tags.Find(new Guid("D1757675-A06C-4C1F-9DAD-03EE00BB1100")));
                    ticketToUpdate.Tags.Add(context.Tags.Find(new Guid("96C66A97-9C3E-4B15-BD70-A4C832EEDE8B")));
                    context.SaveChanges();

                    var setupTicket = context.Tickets.Single(t => t.TicketId == ticketId);
                    Assert.AreEqual(2, setupTicket.Tags.Count);
                }

                // Act
                new TagRepository().UpdateTicketTags(ticketId, selectedTags);

                // Assert
                using (var context2 = new TicketModelContext())
                {
                    var updatedTeicket = context2.Tickets.Include(t => t.Tags).First(t => t.TicketId == ticketId);
                    Assert.AreEqual(0, updatedTeicket.Tags.Count);
                }
            }
        }
David Hawkins
  • 959
  • 1
  • 8
  • 29