From e6add4bc790f4ea0ce26cf99311306425f22fe6b Mon Sep 17 00:00:00 2001 From: Marcus Miris Date: Thu, 31 Oct 2013 18:51:38 -0200 Subject: [PATCH] Added support for attached entities There was errors when tests was changed for not simulate dettach. --- .gitignore | 1 + .../GraphDiff.Tests/GraphDiff.Tests.csproj | 3 +- .../GraphDiff.Tests/Tests/AttachedTests.cs | 986 ++++++++++++++++++ .../Tests/{Tests.cs => DettachedTests.cs} | 109 +- GraphDiff/GraphDiff/DbContextExtensions.cs | 70 +- 5 files changed, 1135 insertions(+), 34 deletions(-) create mode 100644 GraphDiff/GraphDiff.Tests/Tests/AttachedTests.cs rename GraphDiff/GraphDiff.Tests/Tests/{Tests.cs => DettachedTests.cs} (91%) diff --git a/.gitignore b/.gitignore index bdc3535..26064b3 100644 --- a/.gitignore +++ b/.gitignore @@ -106,3 +106,4 @@ Generated_Code #added for RIA/Silverlight projects _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML +.localhistory/ diff --git a/GraphDiff/GraphDiff.Tests/GraphDiff.Tests.csproj b/GraphDiff/GraphDiff.Tests/GraphDiff.Tests.csproj index e5be696..14b2657 100644 --- a/GraphDiff/GraphDiff.Tests/GraphDiff.Tests.csproj +++ b/GraphDiff/GraphDiff.Tests/GraphDiff.Tests.csproj @@ -54,7 +54,8 @@ - + + diff --git a/GraphDiff/GraphDiff.Tests/Tests/AttachedTests.cs b/GraphDiff/GraphDiff.Tests/Tests/AttachedTests.cs new file mode 100644 index 0000000..2c701ac --- /dev/null +++ b/GraphDiff/GraphDiff.Tests/Tests/AttachedTests.cs @@ -0,0 +1,986 @@ +using System.Data; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Collections.Generic; +using System.Data.Entity; +using System.Linq; +using System.Transactions; +using RefactorThis.GraphDiff.Tests.Models; + +namespace RefactorThis.GraphDiff.Tests +{ + + /// + /// Tests + /// + [TestClass] + public class AttachedTests + { + #region Class construction & initialization + + private TransactionScope _transactionScope; + + public AttachedTests() + { + Database.SetInitializer(new DropCreateDatabaseAlways()); + } + + [ClassInitialize] + public static void SetupTheDatabase(TestContext testContext) + { + using (var context = new TestDbContext()) + { + var company1 = context.Companies.Add(new Models.Company + { + Name = "Company 1", + Contacts = new List + { + new Models.CompanyContact + { + FirstName = "Bob", + LastName = "Brown", + Infos = new List + { + new Models.ContactInfo + { + Description = "Home", + Email = "test@test.com", + PhoneNumber = "0255525255" + } + } + } + } + }); + + var company2 = context.Companies.Add(new Models.Company + { + Name = "Company 2", + Contacts = new List + { + new Models.CompanyContact + { + FirstName = "Tim", + LastName = "Jones", + Infos = new List + { + new Models.ContactInfo + { + Description = "Work", + Email = "test@test.com", + PhoneNumber = "456456456456" + } + } + } + } + }); + + var project1 = context.Projects.Add(new Models.Project + { + Name = "Major Project 1", + Deadline = DateTime.Now, + Stakeholders = new List { company2 } + }); + + var project2 = context.Projects.Add(new Models.Project + { + Name = "Major Project 2", + Deadline = DateTime.Now, + Stakeholders = new List { company1 } + }); + + var manager1 = context.Managers.Add(new Models.Manager + { + PartKey = "manager1", + PartKey2 = 1, + FirstName = "Trent" + }); + var manager2 = context.Managers.Add(new Models.Manager + { + PartKey = "manager2", + PartKey2 = 2, + FirstName = "Timothy" + }); + + var locker1 = new Models.Locker + { + Combination = "Asdfasdf", + Location = "Middle Earth" + }; + + var employee = new Models.Employee + { + Manager = manager1, + Key = "Asdf", + FirstName = "Test employee", + Locker = locker1 + }; + + context.Lockers.Add(locker1); + context.Employees.Add(employee); + + project2.LeadCoordinator = manager2; + + context.SaveChanges(); + } + } + + #endregion + + #region Test Initialize and Cleanup + + [TestInitialize] + public virtual void CreateTransactionOnTestInitialize() + { + _transactionScope = new TransactionScope(TransactionScopeOption.RequiresNew, new TransactionOptions { Timeout = new TimeSpan(0, 10, 0) }); + } + + [TestCleanup] + public virtual void Attached_DisposeTransactionOnTestCleanup() + { + Transaction.Current.Rollback(); + _transactionScope.Dispose(); + } + + #endregion + + #region Base record update + + [TestMethod] + public void Attached_BaseEntityUpdate() + { + Models.Company company1; + using (var context = new TestDbContext()) + { + company1 = context.Companies.Single(p => p.Id == 2); + //} // Simulate detach + + company1.Name = "Company #1"; // Change from Company 1 to Company #1 + + //using (var context = new TestDbContext()) + //{ + context.UpdateGraph(company1, null); + context.SaveChanges(); + Assert.IsTrue(context.Companies.Single(p => p.Id == 2).Name == "Company #1"); + } + } + + [TestMethod] + public void Attached_DoesNotUpdateEntityIfNoChangesHaveBeenMade() + { + Models.Company company1; + using (var context = new TestDbContext()) + { + company1 = context.Companies.Single(p => p.Id == 2); + //} // Simulate detach + + //using (var context = new TestDbContext()) + //{ + context.UpdateGraph(company1, null); + Assert.IsTrue(context.ChangeTracker.Entries().All(p => p.State == System.Data.EntityState.Unchanged)); + } + } + + [TestMethod] + public void Attached_MarksAssociatedRelationAsChangedEvenIfEntitiesAreUnchanged() + { + Models.Project project1; + Models.Manager manager1; + using (var context = new TestDbContext()) + { + project1 = context.Projects.Include(m => m.LeadCoordinator).Single(p => p.Id == 1); + manager1 = context.Managers.First(); + //} // Simulate detach + + project1.LeadCoordinator = manager1; + + //using (var context = new TestDbContext()) + //{ + context.UpdateGraph(project1, p => p.AssociatedEntity(e => e.LeadCoordinator)); + context.SaveChanges(); + Assert.IsTrue(context.Projects.Include(m => m.LeadCoordinator).Single(p => p.Id == 1).LeadCoordinator == manager1); + } + } + + #endregion + + #region Associated Entity + + [TestMethod] + public void Attached_AssociatedEntityWherePreviousValueWasNull() + { + Models.Project project; + Models.Manager coord; + using (var context = new TestDbContext()) + { + project = context.Projects + .Include(p => p.LeadCoordinator) + .Single(p => p.Id == 1); + + coord = context.Managers + .Single(p => p.PartKey == "manager1" && p.PartKey2 == 1); + + //} // Simulate detach + + project.LeadCoordinator = coord; + + //using (var context = new TestDbContext()) + //{ + // Setup mapping + context.UpdateGraph(project, map => map + .AssociatedEntity(p => p.LeadCoordinator)); + + context.SaveChanges(); + Assert.IsTrue(context.Projects + .Include(p => p.LeadCoordinator) + .Single(p => p.Id == 1) + .LeadCoordinator.PartKey == coord.PartKey); + } + } + + [TestMethod] + public void Attached_AssociatedEntityWhereNewValueIsNull() + { + Models.Project project; + using (var context = new TestDbContext()) + { + project = context.Projects + .Include(p => p.LeadCoordinator) + .Single(p => p.Id == 2); + + //} // Simulate detach + + project.LeadCoordinator = null; + + //using (var context = new TestDbContext()) + //{ + // Setup mapping + context.UpdateGraph(project, map => map + .AssociatedEntity(p => p.LeadCoordinator)); + + context.SaveChanges(); + Assert.IsTrue(context.Projects + .Include(p => p.LeadCoordinator) + .Single(p => p.Id == 2) + .LeadCoordinator == null); + } + } + + [TestMethod] + public void Attached_AssociatedEntityWherePreviousValueIsNewValue() + { + Models.Project project; + Models.Manager coord; + using (var context = new TestDbContext()) + { + project = context.Projects + .Include(p => p.LeadCoordinator) + .Single(p => p.Id == 2); + + coord = context.Managers + .Single(p => p.PartKey == "manager2" && p.PartKey2 == 2); + + // } // Simulate detach + + project.LeadCoordinator = coord; + + //using (var context = new TestDbContext()) + //{ + // Setup mapping + context.UpdateGraph(project, map => map + .AssociatedEntity(p => p.LeadCoordinator)); + + context.SaveChanges(); + Assert.IsTrue(context.Projects + .Include(p => p.LeadCoordinator) + .Single(p => p.Id == 2) + .LeadCoordinator.PartKey == coord.PartKey); + } + } + + [TestMethod] + public void Attached_AssociatedEntityWherePreviousValueIsNotNewValue() + { + Models.Project project; + Models.Manager coord; + using (var context = new TestDbContext()) + { + project = context.Projects + .Include(p => p.LeadCoordinator) + .Single(p => p.Id == 2); + + coord = context.Managers + .Single(p => p.PartKey == "manager1" && p.PartKey2 == 1); + + //} // Simulate detach + + project.LeadCoordinator = coord; + + //using (var context = new TestDbContext()) + //{ + // Setup mapping + context.UpdateGraph(project, map => map + .AssociatedEntity(p => p.LeadCoordinator)); + + context.SaveChanges(); + Assert.IsTrue(context.Projects + .Include(p => p.LeadCoordinator) + .Single(p => p.Id == 2) + .LeadCoordinator.PartKey == coord.PartKey); + } + } + + [TestMethod] + public void Attached_AssociatedEntityValuesShouldNotBeUpdated() + { + Models.Project project; + using (var context = new TestDbContext()) + { + project = context.Projects + .Include(p => p.LeadCoordinator) + .Single(p => p.Id == 2); + + //} // Simulate detach + + project.LeadCoordinator.FirstName = "Larry"; + + //using (var context = new TestDbContext()) + //{ + // Setup mapping + context.UpdateGraph(project, map => map + .AssociatedEntity(p => p.LeadCoordinator)); + + Assert.IsTrue(context.ChangeTracker.Entries().All(p => p.State == System.Data.EntityState.Unchanged)); + + context.SaveChanges(); + } + + using (var context = new TestDbContext()) + { + Assert.IsTrue(context.Projects + .Include(p => p.LeadCoordinator) + .Single(p => p.Id == 2) + .LeadCoordinator.FirstName != "Larry"); + } + + } + + [TestMethod] + public void Attached_AssociatedEntityValuesForNewValueShouldNotBeUpdated() + { + Models.Project project; + Models.Manager coord; + using (var context = new TestDbContext()) + { + project = context.Projects + .Include(p => p.LeadCoordinator) + .Single(p => p.Id == 2); + + coord = context.Managers + .Single(p => p.PartKey == "manager1" && p.PartKey2 == 1); + + //} // Simulate detach + + project.LeadCoordinator = coord; + coord.FirstName = "Larry"; + + //using (var context = new TestDbContext()) + //{ + // Setup mapping + context.UpdateGraph(project, map => map + .AssociatedEntity(p => p.LeadCoordinator)); + + context.SaveChanges(); + } + + // Force reload of DB entities. + // note can also be done with GraphDiffConfiguration.ReloadAssociatedEntitiesWhenAttached. + using (var context = new TestDbContext()) + { + Assert.IsTrue(context.Projects + .Include(p => p.LeadCoordinator) + .Single(p => p.Id == 2) + .LeadCoordinator.FirstName == "Trent"); + } + } + + #endregion + + #region Owned Entity + + [TestMethod] + public void Attached_OwnedEntityUpdateValues() + { + Models.Project project; + using (var context = new TestDbContext()) + { + project = context.Projects + .Include(p => p.LeadCoordinator) + .Single(p => p.Id == 2); + + //} // Simulate detach + + project.LeadCoordinator.FirstName = "Tada"; + + //using (var context = new TestDbContext()) + //{ + // Setup mapping + context.UpdateGraph(project, map => map + .OwnedEntity(p => p.LeadCoordinator)); + + context.SaveChanges(); + Assert.IsTrue(context.Projects + .Include(p => p.LeadCoordinator) + .Single(p => p.Id == 2) + .LeadCoordinator.FirstName == "Tada"); + } + } + + [TestMethod] + public void Attached_OwnedEntityNewEntity() + { + Models.Project project; + using (var context = new TestDbContext()) + { + project = context.Projects + .Include(p => p.LeadCoordinator) + .Single(p => p.Id == 2); + + //} // Simulate detach + + project.LeadCoordinator = new Models.Manager { FirstName = "Br", PartKey = "TER", PartKey2 = 2 }; + + //using (var context = new TestDbContext()) + //{ + // Setup mapping + context.UpdateGraph(project, map => map + .OwnedEntity(p => p.LeadCoordinator)); + + context.SaveChanges(); + Assert.IsTrue(context.Projects + .Include(p => p.LeadCoordinator) + .Single(p => p.Id == 2) + .LeadCoordinator.PartKey == "TER"); + } + } + + [TestMethod] + public void Attached_OwnedEntityRemoveEntity() + { + Models.Project project; + using (var context = new TestDbContext()) + { + project = context.Projects + .Include(p => p.LeadCoordinator) + .Single(p => p.Id == 2); + + //} // Simulate detach + + project.LeadCoordinator = null; + + //using (var context = new TestDbContext()) + //{ + // Setup mapping + context.UpdateGraph(project, map => map + .OwnedEntity(p => p.LeadCoordinator)); + + context.SaveChanges(); + Assert.IsTrue(context.Projects + .Include(p => p.LeadCoordinator) + .Single(p => p.Id == 2) + .LeadCoordinator == null); + } + } + + #endregion + + #region Associated Collection + + [TestMethod] + public void Attached_AssociatedCollectionAdd() + { + // don't know what to do about this yet.. + Models.Project project1; + Models.Company company2; + using (var context = new TestDbContext()) + { + project1 = context.Projects + .Include(p => p.Stakeholders) + .Single(p => p.Id == 2); + + company2 = context.Companies.Single(p => p.Id == 2); + //} // Simulate detach + + project1.Stakeholders.Add(company2); + + //using (var context = new TestDbContext()) + //{ + // Setup mapping + context.UpdateGraph(project1, map => map + .AssociatedCollection(p => p.Stakeholders)); + + context.SaveChanges(); + Assert.IsTrue(context.Projects + .Include(p => p.Stakeholders) + .Single(p => p.Id == 2) + .Stakeholders.Count == 2); + } + } + + [TestMethod] + public void Attached_AssociatedCollectionRemove() + { + Models.Project project1; + using (var context = new TestDbContext()) + { + project1 = context.Projects + .Include(p => p.Stakeholders) + .Single(p => p.Id == 2); + //} // Simulate detach + + var company = project1.Stakeholders.First(); + project1.Stakeholders.Remove(company); + + //using (var context = new TestDbContext()) + //{ + // Setup mapping + context.UpdateGraph(project1, map => map + .AssociatedCollection(p => p.Stakeholders)); + + context.SaveChanges(); + Assert.IsTrue(context.Projects + .Include(p => p.Stakeholders) + .Single(p => p.Id == 2) + .Stakeholders.Count == 0); + + // Ensure does not delete non owned entity + Assert.IsTrue(context.Companies.Any(p => p.Id == company.Id)); + } + } + + [TestMethod] + public void Attached_AssociatedCollectionsEntitiesValuesShouldNotBeUpdated() + { + Models.Project project1; + using (var context = new TestDbContext()) + { + project1 = context.Projects + .Include(p => p.Stakeholders) + .Single(p => p.Id == 2); + //} // Simulate detach + + var company = project1.Stakeholders.First(); + company.Name = "TEST OVERWRITE NAME"; + + //using (var context = new TestDbContext()) + //{ + // Setup mapping + context.UpdateGraph(project1, map => map + .AssociatedCollection(p => p.Stakeholders)); + + context.SaveChanges(); + } + + using (var context = new TestDbContext()) + { + Assert.IsTrue(context.Projects + .Include(p => p.Stakeholders) + .Single(p => p.Id == 2) + .Stakeholders.First().Name != "TEST OVERWRITE NAME"); + } + } + + #endregion + + #region Owned Collection + + [TestMethod] + public void Attached_OwnedCollectionUpdate() + { + Models.Company company1; + using (var context = new TestDbContext()) + { + company1 = context.Companies + .Include(p => p.Contacts) + .Single(p => p.Id == 2); + //} // Simulate detach + + company1.Name = "Company #1"; // Change from Company 1 to Company #1 + company1.Contacts.First().FirstName = "Bobby"; // change to bobby + + //using (var context = new TestDbContext()) + //{ + // Setup mapping + context.UpdateGraph(company1, map => map + .OwnedCollection(p => p.Contacts)); + + context.SaveChanges(); + } + + using (var context = new TestDbContext()) + { + Assert.IsTrue(context.Companies + .Include(p => p.Contacts) + .Single(p => p.Id == 2) + .Contacts.First() + .FirstName == "Bobby"); + Assert.IsTrue(context.Companies + .Include(p => p.Contacts) + .Single(p => p.Id == 2) + .Contacts.First() + .LastName == "Jones"); + } + } + + [TestMethod] + public void Attached_OwnedCollectionAdd() + { + Models.Company company1; + using (var context = new TestDbContext()) + { + company1 = context.Companies + .Include(p => p.Contacts.Select(m => m.Infos)) + .Single(p => p.Id == 2); + //} // Simulate detach + + company1.Name = "Company #1"; // Change from Company 1 to Company #1 + company1.Contacts.Add(new Models.CompanyContact + { + FirstName = "Charlie", + LastName = "Sheen", + Infos = new List + { + new Models.ContactInfo { PhoneNumber = "123456789", Description = "Home" } + } + }); + + //using (var context = new TestDbContext()) + //{ + // Setup mapping + context.UpdateGraph(company1, map => map + .OwnedCollection(p => p.Contacts, with => with + .OwnedCollection(p => p.Infos))); + + context.SaveChanges(); + Assert.IsTrue(context.Companies + .Include(p => p.Contacts.Select(m => m.Infos)) + .Single(p => p.Id == 2) + .Contacts.Count == 2); + Assert.IsTrue(context.Companies + .Include(p => p.Contacts.Select(m => m.Infos)) + .Single(p => p.Id == 2) + .Contacts.Any(p => p.LastName == "Sheen")); + } + } + + [TestMethod] + public void Attached_OwnedCollectionAddMultiple() + { + Models.Company company1; + using (var context = new TestDbContext()) + { + company1 = context.Companies + .Include(p => p.Contacts.Select(m => m.Infos)) + .Single(p => p.Id == 2); + //} // Simulate detach + + company1.Name = "Company #1"; // Change from Company 1 to Company #1 + company1.Contacts.Add(new Models.CompanyContact + { + FirstName = "Charlie", + LastName = "Sheen", + Infos = new List + { + new Models.ContactInfo { PhoneNumber = "123456789", Description = "Home" } + } + }); + company1.Contacts.Add(new Models.CompanyContact + { + FirstName = "Tim", + LastName = "Sheen" + }); + company1.Contacts.Add(new Models.CompanyContact + { + FirstName = "Emily", + LastName = "Sheen" + }); + company1.Contacts.Add(new Models.CompanyContact + { + FirstName = "Mr", + LastName = "Sheen", + Infos = new List + { + new Models.ContactInfo { PhoneNumber = "123456789", Description = "Home" } + } + }); + company1.Contacts.Add(new Models.CompanyContact + { + FirstName = "Mr", + LastName = "X" + }); + + //using (var context = new TestDbContext()) + //{ + // Setup mapping + context.UpdateGraph(company1, map => map + .OwnedCollection(p => p.Contacts, with => with + .OwnedCollection(p => p.Infos))); + + context.SaveChanges(); + Assert.IsTrue(context.Companies + .Include(p => p.Contacts.Select(m => m.Infos)) + .Single(p => p.Id == 2) + .Contacts.Count == 6); + } + } + + [TestMethod] + public void Attached_OwnedCollectionRemove() + { + Models.Company company1; + using (var context = new TestDbContext()) + { + company1 = context.Companies + .Include(p => p.Contacts.Select(m => m.Infos)) + .Single(p => p.Id == 2); + //} // Simulate detach + + //company1.Contacts.Remove(company1.Contacts.First()); + context.Entry(company1.Contacts.First()).State = EntityState.Deleted; + + + //using (var context = new TestDbContext()) + //{ + // Setup mapping + //context.Entry(company1).State = EntityState.Modified; + context.UpdateGraph(company1, map => map + .OwnedCollection(p => p.Contacts, with => with + .OwnedCollection(p => p.Infos))); + + context.SaveChanges(); + Assert.IsTrue(context.Companies + .Include(p => p.Contacts.Select(m => m.Infos)) + .Single(p => p.Id == 2) + .Contacts.Count == 0); + } + } + + [TestMethod] + public void Attached_OwnedCollectionAddRemoveUpdate() + { + Models.Company company1; + using (var context = new TestDbContext()) + { + company1 = context.Companies + .Include(p => p.Contacts.Select(m => m.Infos)) + .Single(p => p.Id == 2); + + company1.Contacts.Add(new Models.CompanyContact { FirstName = "Hello", LastName = "Test" }); + context.SaveChanges(); + //} // Simulate detach + + // Update, remove and add + company1.Name = "Company #1"; // Change from Company 1 to Company #1 + + string originalname = company1.Contacts.First().FirstName; + company1.Contacts.First().FirstName = "Terrrrrry"; + + company1.Contacts.Remove(company1.Contacts.Skip(1).First()); + + company1.Contacts.Add(new Models.CompanyContact + { + FirstName = "Charlie", + LastName = "Sheen", + Infos = new List + { + new Models.ContactInfo { PhoneNumber = "123456789", Description = "Home" } + } + }); + + //using (var context = new TestDbContext()) + //{ + // Setup mapping + context.UpdateGraph(company1, map => map + .OwnedCollection(p => p.Contacts, with => with + .OwnedCollection(p => p.Infos))); + + context.SaveChanges(); + + var test = context.Companies + .Include(p => p.Contacts.Select(m => m.Infos)) + .Single(p => p.Id == 2); + + + Assert.IsTrue(test.Contacts.Count == 2); + Assert.IsTrue(test.Contacts.First().FirstName == "Terrrrrry"); + Assert.IsTrue(test.Contacts.Skip(1).First().FirstName == "Charlie"); + } + } + + [TestMethod] + public void Attached_OwnedCollectionWithOwnedCollection() + { + Models.Company company1; + using (var context = new TestDbContext()) + { + company1 = context.Companies + .Include(p => p.Contacts.Select(m => m.Infos)) + .First(); + //} // Simulate detach + + company1.Contacts.First().Infos.First().Email = "testeremail"; + company1.Contacts.First().Infos.Add(new Models.ContactInfo { Description = "Test", Email = "test@test.com" }); + + //using (var context = new TestDbContext()) + //{ + // Setup mapping + context.UpdateGraph(company1, map => map + .OwnedCollection(p => p.Contacts, with => with + .OwnedCollection(m => m.Infos))); + + context.SaveChanges(); + var value = context.Companies.Include(p => p.Contacts.Select(m => m.Infos)) + .First(); + + Assert.IsTrue(value.Contacts.First().Infos.Count == 2); + Assert.IsTrue(value.Contacts.First().Infos.First().Email == "testeremail"); + } + } + + // added as per ticket #5 + // also tried to add some more complication to this graph to ensure everything works well + [TestMethod] + public void Attached_OwnedMultipleLevelCollectionMappingWithAssociatedReload() + { + var hoby = new Hobby() {HobbyType = "Teste Hoby"}; + + + Models.MultiLevelTest multiLevelTest; + Models.Hobby hobby; + using (var context = new TestDbContext()) + { + multiLevelTest = context.MultiLevelTest.Add(new Models.MultiLevelTest + { + Managers = new[] // test arrays as well + { + new Models.Manager + { + PartKey = "xxx", + PartKey2 = 2, + Employees = new List + { + new Models.Employee + { + Key = "xsdf", + FirstName = "Asdf", + Hobbies = Enumerable.Range(1, 2000).Select(s => hoby).ToList() + } + } + }, + new Models.Manager + { + PartKey = "xxx7", + PartKey2 = 3, + Employees = new List + { + new Models.Employee + { + Key = "xs7df", + FirstName = "Asdf", + Hobbies = Enumerable.Range(1, 3000).Select(s => hoby).ToList() + } + } + } + } + }); + + hobby = context.Hobbies.Add(new Models.Hobby { HobbyType = "Skiing" }); + context.SaveChanges(); + //} // Simulate detach + + // Graph changes + + // Should not update changes to hobby + hobby.HobbyType = "Something Else"; + + // Update changes to manager + var manager = multiLevelTest.Managers.First(); + manager.FirstName = "Tester"; + + // Update changes to employees + var employeeToUpdate = manager.Employees.First(); + employeeToUpdate.Hobbies.Clear(); + employeeToUpdate.Hobbies.Add(hobby); + + manager.Employees.Add(new Models.Employee + { + FirstName = "Tim", + Key = "Tim1", + Hobbies = Enumerable.Range(1, 2000).Select(s => hoby).ToList(), + Manager = multiLevelTest.Managers.First() + }); + + //using (var context = new TestDbContext()) + //{ + GraphDiffConfiguration.ReloadAssociatedEntitiesWhenAttached = true; + // Setup mapping + context.UpdateGraph(multiLevelTest, map => map + .OwnedCollection(x => x.Managers, withx => withx + .AssociatedCollection(pro => pro.Projects) + .OwnedCollection(p => p.Employees, with => with + .AssociatedCollection(m => m.Hobbies) + .OwnedEntity(m => m.Locker)))); + + context.SaveChanges(); + + GraphDiffConfiguration.ReloadAssociatedEntitiesWhenAttached = false; + + var result = context.MultiLevelTest + .Include("Managers.Employees.Hobbies") + .Include("Managers.Employees.Locker") + .Include("Managers.Projects") + .First(); + + var updateManager = result.Managers.Single(p => p.PartKey == manager.PartKey && p.PartKey2 == manager.PartKey2); + var updateEmployee = updateManager.Employees.Single(p => p.Key == employeeToUpdate.Key); + var updateHobby = context.Hobbies.Single(p => p.Id == hobby.Id); + + Assert.IsTrue(updateManager.Employees.Count() == 2); + Assert.IsTrue(result.Managers.First().FirstName == "Tester"); + Assert.IsTrue(updateEmployee.Hobbies.Count() == 1); + Assert.IsTrue(updateEmployee.Hobbies.First().HobbyType == "Skiing"); + Assert.IsTrue(updateHobby.HobbyType == "Skiing"); + Assert.IsTrue(result.Managers.First().Employees.Any(p => p.Key == "Tim1")); + } + } + + #endregion + + #region 2 way relation + + [TestMethod] + public void Attached_EnsureWeCanUseCyclicRelationsOnOwnedCollections() + { + Models.Manager manager; + using (var context = new TestDbContext()) + { + manager = context.Managers.Include(p => p.Employees).First(); + //} // Simulate disconnect + + var newEmployee = new Models.Employee { Key = "assdf", FirstName = "Test Employee", Manager = manager }; + manager.Employees.Add(newEmployee); + + //using (var context = new TestDbContext()) + //{ + context.UpdateGraph(manager, m1 => m1.OwnedCollection(o => o.Employees)); + context.SaveChanges(); + Assert.IsTrue(context.Employees.Include(p => p.Manager).Single(p => p.Key == "assdf").Manager.FirstName == manager.FirstName); + } + } + + #endregion + + // TODO Incomplete. Please report any bugs to GraphDiff on github. + // Will add more tests when I have time. + + } +} diff --git a/GraphDiff/GraphDiff.Tests/Tests/Tests.cs b/GraphDiff/GraphDiff.Tests/Tests/DettachedTests.cs similarity index 91% rename from GraphDiff/GraphDiff.Tests/Tests/Tests.cs rename to GraphDiff/GraphDiff.Tests/Tests/DettachedTests.cs index ac65cf5..9949004 100644 --- a/GraphDiff/GraphDiff.Tests/Tests/Tests.cs +++ b/GraphDiff/GraphDiff.Tests/Tests/DettachedTests.cs @@ -6,6 +6,7 @@ using RefactorThis.GraphDiff; using System.Data.Entity; using System.Transactions; +using RefactorThis.GraphDiff.Tests.Models; namespace RefactorThis.GraphDiff.Tests { @@ -13,13 +14,13 @@ namespace RefactorThis.GraphDiff.Tests /// Tests /// [TestClass] - public class Tests + public class DettachedTests { #region Class construction & initialization private TransactionScope _transactionScope; - public Tests() + public DettachedTests() { Database.SetInitializer(new DropCreateDatabaseAlways()); } @@ -145,7 +146,7 @@ public virtual void DisposeTransactionOnTestCleanup() #region Base record update [TestMethod] - public void BaseEntityUpdate() + public void Detached_BaseEntityUpdate() { Models.Company company1; using (var context = new TestDbContext()) @@ -159,12 +160,16 @@ public void BaseEntityUpdate() { context.UpdateGraph(company1, null); context.SaveChanges(); + } + + using (var context = new TestDbContext()) + { Assert.IsTrue(context.Companies.Single(p => p.Id == 2).Name == "Company #1"); } } [TestMethod] - public void DoesNotUpdateEntityIfNoChangesHaveBeenMade() + public void Detached_DoesNotUpdateEntityIfNoChangesHaveBeenMade() { Models.Company company1; using (var context = new TestDbContext()) @@ -180,7 +185,7 @@ public void DoesNotUpdateEntityIfNoChangesHaveBeenMade() } [TestMethod] - public void MarksAssociatedRelationAsChangedEvenIfEntitiesAreUnchanged() + public void Detached_MarksAssociatedRelationAsChangedEvenIfEntitiesAreUnchanged() { Models.Project project1; Models.Manager manager1; @@ -196,7 +201,15 @@ public void MarksAssociatedRelationAsChangedEvenIfEntitiesAreUnchanged() { context.UpdateGraph(project1, p => p.AssociatedEntity(e => e.LeadCoordinator)); context.SaveChanges(); - Assert.IsTrue(context.Projects.Include(m => m.LeadCoordinator).Single(p => p.Id == 1).LeadCoordinator == manager1); + } + + using (var context = new TestDbContext()) + { + var coordinator = context.Projects.Include(m => m.LeadCoordinator).Single(p => p.Id == 1).LeadCoordinator; + + Assert.IsNotNull(coordinator); + Assert.AreEqual(coordinator.PartKey, manager1.PartKey); // Compare Keys because the instances are not equals. + Assert.AreEqual(coordinator.PartKey2, manager1.PartKey2); // Compare Keys because the instances are not equals. } } @@ -205,7 +218,7 @@ public void MarksAssociatedRelationAsChangedEvenIfEntitiesAreUnchanged() #region Associated Entity [TestMethod] - public void AssociatedEntityWherePreviousValueWasNull() + public void Detached_AssociatedEntityWherePreviousValueWasNull() { Models.Project project; Models.Manager coord; @@ -229,6 +242,10 @@ public void AssociatedEntityWherePreviousValueWasNull() .AssociatedEntity(p => p.LeadCoordinator)); context.SaveChanges(); + } + + using (var context = new TestDbContext()) + { Assert.IsTrue(context.Projects .Include(p => p.LeadCoordinator) .Single(p => p.Id == 1) @@ -237,7 +254,7 @@ public void AssociatedEntityWherePreviousValueWasNull() } [TestMethod] - public void AssociatedEntityWhereNewValueIsNull() + public void Detached_AssociatedEntityWhereNewValueIsNull() { Models.Project project; using (var context = new TestDbContext()) @@ -257,6 +274,10 @@ public void AssociatedEntityWhereNewValueIsNull() .AssociatedEntity(p => p.LeadCoordinator)); context.SaveChanges(); + } + + using (var context = new TestDbContext()) + { Assert.IsTrue(context.Projects .Include(p => p.LeadCoordinator) .Single(p => p.Id == 2) @@ -265,7 +286,7 @@ public void AssociatedEntityWhereNewValueIsNull() } [TestMethod] - public void AssociatedEntityWherePreviousValueIsNewValue() + public void Detached_AssociatedEntityWherePreviousValueIsNewValue() { Models.Project project; Models.Manager coord; @@ -289,6 +310,10 @@ public void AssociatedEntityWherePreviousValueIsNewValue() .AssociatedEntity(p => p.LeadCoordinator)); context.SaveChanges(); + } + + using (var context = new TestDbContext()) + { Assert.IsTrue(context.Projects .Include(p => p.LeadCoordinator) .Single(p => p.Id == 2) @@ -297,7 +322,7 @@ public void AssociatedEntityWherePreviousValueIsNewValue() } [TestMethod] - public void AssociatedEntityWherePreviousValueIsNotNewValue() + public void Detached_AssociatedEntityWherePreviousValueIsNotNewValue() { Models.Project project; Models.Manager coord; @@ -321,6 +346,10 @@ public void AssociatedEntityWherePreviousValueIsNotNewValue() .AssociatedEntity(p => p.LeadCoordinator)); context.SaveChanges(); + } + + using (var context = new TestDbContext()) + { Assert.IsTrue(context.Projects .Include(p => p.LeadCoordinator) .Single(p => p.Id == 2) @@ -329,7 +358,7 @@ public void AssociatedEntityWherePreviousValueIsNotNewValue() } [TestMethod] - public void AssociatedEntityValuesShouldNotBeUpdated() + public void Detached_AssociatedEntityValuesShouldNotBeUpdated() { Models.Project project; using (var context = new TestDbContext()) @@ -357,7 +386,7 @@ public void AssociatedEntityValuesShouldNotBeUpdated() } [TestMethod] - public void AssociatedEntityValuesForNewValueShouldNotBeUpdated() + public void Detached_AssociatedEntityValuesForNewValueShouldNotBeUpdated() { Models.Project project; Models.Manager coord; @@ -400,7 +429,7 @@ public void AssociatedEntityValuesForNewValueShouldNotBeUpdated() #region Owned Entity [TestMethod] - public void OwnedEntityUpdateValues() + public void Detached_OwnedEntityUpdateValues() { Models.Project project; using (var context = new TestDbContext()) @@ -420,6 +449,10 @@ public void OwnedEntityUpdateValues() .OwnedEntity(p => p.LeadCoordinator)); context.SaveChanges(); + } + + using (var context = new TestDbContext()) + { Assert.IsTrue(context.Projects .Include(p => p.LeadCoordinator) .Single(p => p.Id == 2) @@ -428,7 +461,7 @@ public void OwnedEntityUpdateValues() } [TestMethod] - public void OwnedEntityNewEntity() + public void Detached_OwnedEntityNewEntity() { Models.Project project; using (var context = new TestDbContext()) @@ -448,6 +481,10 @@ public void OwnedEntityNewEntity() .OwnedEntity(p => p.LeadCoordinator)); context.SaveChanges(); + } + + using (var context = new TestDbContext()) + { Assert.IsTrue(context.Projects .Include(p => p.LeadCoordinator) .Single(p => p.Id == 2) @@ -456,7 +493,7 @@ public void OwnedEntityNewEntity() } [TestMethod] - public void OwnedEntityRemoveEntity() + public void Detached_OwnedEntityRemoveEntity() { Models.Project project; using (var context = new TestDbContext()) @@ -476,6 +513,10 @@ public void OwnedEntityRemoveEntity() .OwnedEntity(p => p.LeadCoordinator)); context.SaveChanges(); + } + + using (var context = new TestDbContext()) + { Assert.IsTrue(context.Projects .Include(p => p.LeadCoordinator) .Single(p => p.Id == 2) @@ -488,7 +529,7 @@ public void OwnedEntityRemoveEntity() #region Associated Collection [TestMethod] - public void AssociatedCollectionAdd() + public void Detached_AssociatedCollectionAdd() { // don't know what to do about this yet.. Models.Project project1; @@ -511,6 +552,10 @@ public void AssociatedCollectionAdd() .AssociatedCollection(p => p.Stakeholders)); context.SaveChanges(); + } + + using (var context = new TestDbContext()) + { Assert.IsTrue(context.Projects .Include(p => p.Stakeholders) .Single(p => p.Id == 2) @@ -519,7 +564,7 @@ public void AssociatedCollectionAdd() } [TestMethod] - public void AssociatedCollectionRemove() + public void Detached_AssociatedCollectionRemove() { Models.Project project1; using (var context = new TestDbContext()) @@ -539,6 +584,10 @@ public void AssociatedCollectionRemove() .AssociatedCollection(p => p.Stakeholders)); context.SaveChanges(); + } + + using (var context = new TestDbContext()) + { Assert.IsTrue(context.Projects .Include(p => p.Stakeholders) .Single(p => p.Id == 2) @@ -550,7 +599,7 @@ public void AssociatedCollectionRemove() } [TestMethod] - public void AssociatedCollectionsEntitiesValuesShouldNotBeUpdated() + public void Detached_AssociatedCollectionsEntitiesValuesShouldNotBeUpdated() { Models.Project project1; using (var context = new TestDbContext()) @@ -570,6 +619,10 @@ public void AssociatedCollectionsEntitiesValuesShouldNotBeUpdated() .AssociatedCollection(p => p.Stakeholders)); context.SaveChanges(); + } + + using (var context = new TestDbContext()) + { Assert.IsTrue(context.Projects .Include(p => p.Stakeholders) .Single(p => p.Id == 2) @@ -582,7 +635,7 @@ public void AssociatedCollectionsEntitiesValuesShouldNotBeUpdated() #region Owned Collection [TestMethod] - public void OwnedCollectionUpdate() + public void Detached_OwnedCollectionUpdate() { Models.Company company1; using (var context = new TestDbContext()) @@ -602,6 +655,10 @@ public void OwnedCollectionUpdate() .OwnedCollection(p => p.Contacts)); context.SaveChanges(); + } + + using (var context = new TestDbContext()) + { Assert.IsTrue(context.Companies .Include(p => p.Contacts) .Single(p => p.Id == 2) @@ -616,7 +673,7 @@ public void OwnedCollectionUpdate() } [TestMethod] - public void OwnedCollectionAdd() + public void Detached_OwnedCollectionAdd() { Models.Company company1; using (var context = new TestDbContext()) @@ -657,7 +714,7 @@ public void OwnedCollectionAdd() } [TestMethod] - public void OwnedCollectionAddMultiple() + public void Detached_OwnedCollectionAddMultiple() { Models.Company company1; using (var context = new TestDbContext()) @@ -718,7 +775,7 @@ public void OwnedCollectionAddMultiple() } [TestMethod] - public void OwnedCollectionRemove() + public void Detached_OwnedCollectionRemove() { Models.Company company1; using (var context = new TestDbContext()) @@ -746,7 +803,7 @@ public void OwnedCollectionRemove() } [TestMethod] - public void OwnedCollectionAddRemoveUpdate() + public void Detached_OwnedCollectionAddRemoveUpdate() { Models.Company company1; using (var context = new TestDbContext()) @@ -798,7 +855,7 @@ public void OwnedCollectionAddRemoveUpdate() } [TestMethod] - public void OwnedCollectionWithOwnedCollection() + public void Detached_OwnedCollectionWithOwnedCollection() { Models.Company company1; using (var context = new TestDbContext()) @@ -830,7 +887,7 @@ public void OwnedCollectionWithOwnedCollection() // added as per ticket #5 // also tried to add some more complication to this graph to ensure everything works well [TestMethod] - public void OwnedMultipleLevelCollectionMappingWithAssociatedReload() + public void Detached_OwnedMultipleLevelCollectionMappingWithAssociatedReload() { Models.MultiLevelTest multiLevelTest; Models.Hobby hobby; @@ -926,7 +983,7 @@ public void OwnedMultipleLevelCollectionMappingWithAssociatedReload() #region 2 way relation [TestMethod] - public void EnsureWeCanUseCyclicRelationsOnOwnedCollections() + public void Detached_EnsureWeCanUseCyclicRelationsOnOwnedCollections() { Models.Manager manager; using (var context = new TestDbContext()) diff --git a/GraphDiff/GraphDiff/DbContextExtensions.cs b/GraphDiff/GraphDiff/DbContextExtensions.cs index 19586d9..d960cb6 100644 --- a/GraphDiff/GraphDiff/DbContextExtensions.cs +++ b/GraphDiff/GraphDiff/DbContextExtensions.cs @@ -49,8 +49,13 @@ public static void UpdateGraph(this DbContext context, T entity, Expression(this DbContext context, T entity) where T : cl /// The entity (sub)graph after it has been updated private static void RecursiveGraphUpdate(DbContext context, object dataStoreEntity, object updatingEntity, UpdateMember member) { + // Get item Type + var itemType = member.Accessor.PropertyType.IsGenericType + ? member.Accessor.PropertyType.GetGenericArguments().First() + : member.Accessor.PropertyType; + + // Create Helpful Linq Methods + Func distinct = l => (IEnumerable)typeof(Enumerable).GetMethods().Single(m => m.Name.Equals("Distinct") && m.GetParameters().Count() == 1).MakeGenericMethod(new[] { itemType }).Invoke(null, new object[] { l }); + Func toList = l => (IEnumerable)typeof(Enumerable).GetMethod("ToList").MakeGenericMethod(new[] { itemType }).Invoke(null, new object[] { l }); + Func singleOrDefault = l => typeof(Enumerable).GetMethods().Single(m => m.Name.Equals("SingleOrDefault") && m.GetParameters().Count() == 1).MakeGenericMethod(new[] { itemType }).Invoke(null, new object[] { l }); + + // Get member's navigation property + var memberNavProp = (member.Parent == null) + ? member.IncludeString + : member.IncludeString.Substring((member.Parent.IncludeString ?? "").Length).TrimStart('.'); + + if (member.IsCollection) { // We are dealing with a collection - var updateValues = (IEnumerable)member.Accessor.GetValue(updatingEntity, null); - var dbCollection = (IEnumerable)member.Accessor.GetValue(dataStoreEntity, null); - if (updateValues == null) - updateValues = new List(); + // Get distinctly the new itens + var updateValues = (IEnumerable) member.Accessor.GetValue(updatingEntity, null); + updateValues = updateValues != null ? toList(distinct(updateValues)) : new List(); + + // Get database values + IEnumerable dbCollection; + var actualEntityIsOnTheContext = updatingEntity == dataStoreEntity; + if (actualEntityIsOnTheContext) + { + dbCollection = member.IsOwned + ? toList(context.Entry(updatingEntity).Collection(memberNavProp).Query()) + : toList(((ObjectQuery)context.Entry(updatingEntity).Collection(memberNavProp).Query()).Execute(MergeOption.OverwriteChanges)); + + // Assure the entity's child collection wasn't refreshed when getting db values. + member.Accessor.SetValue(updatingEntity, updateValues, null); + } + else + { + dbCollection = (IEnumerable)member.Accessor.GetValue(dataStoreEntity, null); + } + Type dbCollectionType = dbCollection.GetType(); Type innerElementType; @@ -129,6 +167,10 @@ private static void RecursiveGraphUpdate(DbContext context, object dataStoreEnti // Removal of dbItem's left in the collection foreach (var dbItem in dbHash.Values) { + // Removing dbItem, assure it's children will me deleted. + foreach (var subMember in member.Members) + RecursiveGraphUpdate(context, dbItem, dbItem, subMember); + // Own the collection so remove it completely. if (member.IsOwned) context.Set(ObjectContext.GetObjectType(dbItem.GetType())).Remove(dbItem); @@ -153,7 +195,13 @@ private static void RecursiveGraphUpdate(DbContext context, object dataStoreEnti } else // not collection { - var dbvalue = member.Accessor.GetValue(dataStoreEntity, null); + var actualEntityIsOnTheContext = updatingEntity == dataStoreEntity; + var dbvalue = (actualEntityIsOnTheContext && member.IsOwned) + ? singleOrDefault(context.Entry(updatingEntity).Reference(memberNavProp).Query()) + : member.Accessor.GetValue(dataStoreEntity, null); + + + var newvalue = member.Accessor.GetValue(updatingEntity, null); if (dbvalue == null && newvalue == null) // No value return; @@ -161,6 +209,14 @@ private static void RecursiveGraphUpdate(DbContext context, object dataStoreEnti // If we own the collection then we need to update the entities otherwise simple relationship update if (!member.IsOwned) { + + if (dbvalue != null) + { + context.Entry(dbvalue).Reload(); + context.Entry(dbvalue).State = EntityState.Unchanged; + } + + if (newvalue == null) { member.Accessor.SetValue(dataStoreEntity, null, null);