public void TestBookCountAuthorsOk() { //SETUP var options = SqliteInMemory.CreateOptions <EfCoreContext>(); using (var context = new EfCoreContext(options)) { context.Database.EnsureCreated(); context.SeedDatabaseFourBooks(); } using (var context = new EfCoreContext(options)) { //ATTEMPT var books = context.Books .Include(r => r.AuthorsLink) .ThenInclude(r => r.Author) .ToList(); //VERIFY books.Count.ShouldEqual(4); books.SelectMany(x => x.AuthorsLink.Select(y => y.Author)).Distinct().Count().ShouldEqual(3); } }
public async Task TestUpdateViaStatedMethodBad() { //SETUP var options = SqliteInMemory.CreateOptions <EfCoreContext>(); using (var context = new EfCoreContext(options)) { context.Database.EnsureCreated(); context.SeedDatabaseFourBooks(); var utData = context.SetupSingleDtoAndEntities <Tests.Dtos.ChangePubDateDto>(); var service = new CrudServicesAsync(context, utData.ConfigAndMapper, new CreateNewDBContextHelper(() => new EfCoreContext(options))); //ATTEMPT var dto = new Tests.Dtos.ChangePubDateDto { BookId = 4, PublishedOn = new DateTime(2000, 1, 1) }; var ex = await Assert.ThrowsAsync <InvalidOperationException>(() => service.UpdateAndSaveAsync(dto, nameof(Book.AddReview))); //VERIFY ex.Message.ShouldStartWith("Could not find a method of name AddReview. The method that fit the properties in the DTO/VM are:"); } }
public void ExampleIdentityResolutionBad() { //SETUP var options = SqliteInMemory .CreateOptions <EfCoreContext>(); using var context = new EfCoreContext(options); context.Database.EnsureCreated(); context.SeedDatabaseFourBooks(); //ATTEMPT var book = context.Books.First(); book.Price = 123; // Should call context.SaveChanges() //VERIFY var verifyBook = context.Books.First(); //!!! THIS IS WRONG !!! THIS IS WRONG verifyBook.Price.ShouldEqual(123); }
public void TestReadBookWithInclude() { //SETUP int bookId; var options = SqliteInMemory.CreateOptions <EfCoreContext>(); using (var context = new EfCoreContext(options)) { context.Database.EnsureCreated(); context.SeedDatabaseFourBooks(); bookId = context.Books.Single(x => x.Reviews.Any()).BookId; } using (var context = new EfCoreContext(options)) { //ATTEMPT var bookWithReviews = context.Books .Include(x => x.Reviews) .Single(x => x.BookId == bookId); //VERIFY bookWithReviews.Reviews.Count.ShouldEqual(2); } }
public void TestReadEachSeparately() { //SETUP int bookId; var options = SqliteInMemory.CreateOptions <EfCoreContext>(); using var context = new EfCoreContext(options); context.Database.EnsureCreated(); context.SeedDatabaseFourBooks(); bookId = context.Books.Single(x => x.Reviews.Any()).BookId; context.ChangeTracker.Clear(); //ATTEMPT var book = context.Books.ToList(); var reviews = context.Set <Review>().ToList(); var authorsLinks = context.Set <BookAuthor>().ToList(); var authors = context.Authors.ToList(); //VERIFY book.Last().Reviews.Count.ShouldEqual(2); book.Last().AuthorsLink.Single().Author.ShouldNotBeNull(); }
public void TestIncludeThenTwoLevelManuallyLoad() { //SETUP var options = SqliteInMemory.CreateOptions <EfCoreContext>(); using (var context = new EfCoreContext(options)) { context.Database.EnsureCreated(); context.SeedDatabaseFourBooks(); var includeStrings = typeof(AddNewAuthorToBookUsingIncludesDto) .GetCustomAttributes(typeof(IncludeThenAttribute), true).Cast <IncludeThenAttribute>() .Select(x => x.IncludeNames).ToList(); //ATTEMPT var query = ApplyAnyIncludeStringsAtDbSetLevel <AddReviewWithIncludeDto>(context.Books); var books = query.ToList(); //VERIFY var names = books.SelectMany(x => x.AuthorsLink.Select(y => y.Author.Name)).ToArray(); names.ShouldEqual(new string[] { "Martin Fowler", "Martin Fowler", "Eric Evans", "Future Person" }); } }
public void TestReadJustBookTableOk() { //SETUP var options = SqliteInMemory.CreateOptions <EfCoreContext>(); using (var context = new EfCoreContext(options)) { context.Database.EnsureCreated(); context.SeedDatabaseFourBooks(); } using (var context = new EfCoreContext(options) ) //dispose first DbContext and create new one. That way the read isn't effected by the setup code { //ATTEMPT var book = context.Books.First(); //VERIFY book.AuthorsLink.ShouldBeNull(); book.Reviews.ShouldBeNull(); book.Promotion.ShouldBeNull(); } }
public void TestAddReviewToBookNoIncludeOk() { //SETUP var options = SqliteInMemory.CreateOptions <EfCoreContext>(); using (var context = new EfCoreContext(options)) { context.Database.EnsureCreated(); context.SeedDatabaseFourBooks(); } using (var context = new EfCoreContext(options)) { //ATTEMPT var book = context.Books.First(); book.AddReview(5, "comment", "user", context); context.SaveChanges(); //VERIFY book.Reviews.Count().ShouldEqual(1); context.Set <Review>().Count().ShouldEqual(3); } }
public void TestRemoveReviewBookOk() { //SETUP var options = SqliteInMemory.CreateOptions <EfCoreContext>(); using (var context = new EfCoreContext(options)) { context.Database.EnsureCreated(); context.SeedDatabaseFourBooks(); } using (var context = new EfCoreContext(options)) { //ATTEMPT var book = context.Books.Include(x => x.Reviews).Single(x => x.Reviews.Count() == 2); book.RemoveReview(book.Reviews.LastOrDefault()); context.SaveChanges(); //VERIFY book.Reviews.Count().ShouldEqual(1); context.Set <Review>().Count().ShouldEqual(1); } }
public void TestAddPromotionBookOk() { //SETUP var options = SqliteInMemory.CreateOptions <EfCoreContext>(); using (var context = new EfCoreContext(options)) { context.Database.EnsureCreated(); context.SeedDatabaseFourBooks(); } using (var context = new EfCoreContext(options)) { //ATTEMPT var book = context.Books.First(); var status = book.AddPromotion(book.OrgPrice / 2, "Half price today"); context.SaveChanges(); //VERIFY status.HasErrors.ShouldBeFalse(); book.ActualPrice.ShouldEqual(book.OrgPrice / 2); } }
public async Task TestProjectBookTitleManyWithConfigOk() { //SETUP var options = SqliteInMemory.CreateOptions <EfCoreContext>(); using (var context = new EfCoreContext(options)) { context.Database.EnsureCreated(); context.SeedDatabaseFourBooks(); var utData = context.SetupSingleDtoAndEntities <BookTitleAndCount>(); var service = new CrudServices(context, utData.Wrapped); //ATTEMPT var list = await service.ReadManyNoTracked <BookTitleAndCount>().ToListAsync(); //VERIFY service.IsValid.ShouldBeTrue(service.GetAllErrors()); list.Count.ShouldEqual(4); list.Select(x => x.Title).ShouldEqual(new[] { "Refactoring", "Patterns of Enterprise Application Architecture", "Domain-Driven Design", "Quantum Networking" }); list.Select(x => x.ReviewsCount).ShouldEqual(new [] { 0, 0, 0, 2 }); } }
public void TestDirectSelectBookListDtoOk() { //SETUP var options = SqliteInMemory.CreateOptions <EfCoreContext>(); using var context = new EfCoreContext(options); context.Database.EnsureCreated(); context.SeedDatabaseFourBooks(); //ATTEMPT var dtos = context.Books.Select(p => new BookListDto { BookId = p.BookId, Title = p.Title, Price = p.Price, PublishedOn = p.PublishedOn, }).ToList(); //VERIFY dtos.Last().BookId.ShouldNotEqual(0); dtos.Last().Title.ShouldNotBeNull(); dtos.Last().Price.ShouldNotEqual(0); }
public void TestProjectBookTitleManyOk() { //SETUP var options = SqliteInMemory.CreateOptions <EfCoreContext>(); using (var context = new EfCoreContext(options)) { context.Database.EnsureCreated(); context.SeedDatabaseFourBooks(); var utData = context.SetupSingleDtoAndEntities <BookTitle>(); var service = new CrudServices(context, utData.ConfigAndMapper); //ATTEMPT var list = service.ReadManyWithPreQueryNoTracked <Book, BookTitle>(books => books.Where(x => x.AuthorsLink.Select(y => y.Author.Name).Contains("Martin Fowler"))).ToList(); //VERIFY service.IsValid.ShouldBeTrue(service.GetAllErrors()); list.Count.ShouldEqual(2); list.Select(x => x.Title).ShouldEqual(new [] { "Refactoring", "Patterns of Enterprise Application Architecture" }); } }
public void UpdateAuthorBadId() { //SETUP var options = SqliteInMemory.CreateOptions <EfCoreContext>(); using (var context = new EfCoreContext(options)) { context.Database.EnsureCreated(); context.SeedDatabaseFourBooks(); //ATTEMPT var author = new Author { AuthorId = 999999, Name = "Future Person 2" }; context.Authors.Update(author); var ex = Assert.Throws <DbUpdateConcurrencyException>(() => context.SaveChanges()); //VERIFY ex.Message.StartsWith("Database operation expected to affect 1 row(s) but actually affected 0 row(s).").ShouldBeTrue(); } }
public void TestAddExtraAuthorOk() { //SETUP var options = SqliteInMemory.CreateOptions <EfCoreContext>(); using (var context = new EfCoreContext(options)) { context.Database.EnsureCreated(); context.SeedDatabaseFourBooks(); //ATTEMPT var book = context.Books .Include(p => p.AuthorsLink) .Single(p => p.Title == "Quantum Networking"); var newAuthor = context.Authors .Single(p => p.Name == "Martin Fowler"); book.AuthorsLink.Add(new BookAuthor { Book = book, Author = newAuthor, Order = (byte)book.AuthorsLink.Count }); context.SaveChanges(); //VERIFY var bookAgain = context.Books .Include(p => p.AuthorsLink) .Single(p => p.BookId == book.BookId); bookAgain.AuthorsLink.ShouldNotBeNull(); bookAgain.AuthorsLink.Count.ShouldEqual(2); var authorsInOrder = bookAgain.AuthorsLink.OrderBy(p => p.Order).ToList(); authorsInOrder.First().Author.Name.ShouldEqual("Future Person"); authorsInOrder.Last().Author.Name.ShouldEqual("Martin Fowler"); } }
public void TestConnectedUpdateNoExistingRelationshipOk() { //SETUP var options = SqliteInMemory.CreateOptions <EfCoreContext>(); using var context = new EfCoreContext(options); context.Database.EnsureCreated(); context.SeedDatabaseFourBooks(); //ATTEMPT //NOTE: I know that the first book does not have a review! var book = context.Books //#A .Include(p => p.Reviews) //#A .First(); //#A book.Reviews.Add(new Review //#B { //#B VoterName = "Unit Test", //#B NumStars = 5, //#B Comment = "Great book!" //#B }); //#B context.SaveChanges(); //#C /********************************************************** #A This finds the first book and loads it with any reviews it might have #B I add a new review to this book #C The SaveChanges method calls DetectChanges, which finds that the Reviews property has changed, and from there finds the new Review, which it adds to the Review table * *******************************************************/ //VERIFY var bookAgain = context.Books .Include(p => p.Reviews) .Single(p => p.BookId == book.BookId); bookAgain.Reviews.ShouldNotBeNull(); bookAgain.Reviews.Count.ShouldEqual(1); }
public void TestConnectedUpdateNoExistingRelationshipOk() { //SETUP var options = SqliteInMemory.CreateOptions <EfCoreContext>(); using (var context = new EfCoreContext(options)) { context.Database.EnsureCreated(); context.SeedDatabaseFourBooks(); //ATTEMPT var book = context.Books //#A .Include(p => p.Promotion) //#B .First(p => p.Promotion == null); book.Promotion = new PriceOffer //#C { //#C NewPrice = book.Price / 2, //#C PromotionalText = "Half price today!" //#C }; //#C context.SaveChanges(); //#D /********************************************************** #A Finds a book. In this example it doesn’t have an existing promotion, but it would also work if there was an existing promotion #B While the include isn't needed because I am loading something without a Promotion it is good practice to include it, as you should load any any relationships if you are going to change a relationship #C I add a new PriceOffer to this book #D The SaveChanges method calls DetectChanges, which find that the Promotion property has changed, so it adds that entry to the PricerOffers table * *******************************************************/ //VERIFY var bookAgain = context.Books .Include(p => p.Promotion) .Single(p => p.BookId == book.BookId); bookAgain.Promotion.ShouldNotBeNull(); bookAgain.Promotion.PromotionalText.ShouldEqual("Half price today!"); } }
public void TestDeleteBookInLineItemFails() { //SETUP var options = SqliteInMemory.CreateOptions <EfCoreContext>(); using var context = new EfCoreContext(options); context.Database.EnsureCreated(); context.SeedDatabaseFourBooks(); var userId = Guid.NewGuid(); var order = new Order { CustomerId = userId, LineItems = new List <LineItem> { new LineItem { ChosenBook = context.Books.First(), LineNum = 0, BookPrice = 123, NumBooks = 1 } } }; context.Orders.Add(order); context.SaveChanges(); context.ChangeTracker.Clear(); //ATTEMPT context.Books.Remove(context.Books.First()); var ex = Assert.Throws <DbUpdateException>(() => context.SaveChanges()); //VERIFY ex.InnerException.Message.ShouldEqual("SQLite Error 19: 'FOREIGN KEY constraint failed'."); }
public void TestLoadOnlyBookAndDelete() { //SETUP var options = SqliteInMemory.CreateOptions <EfCoreContext>(); using (var context = new EfCoreContext(options)) { context.Database.EnsureCreated(); context.SeedDatabaseFourBooks(); } using (var context = new EfCoreContext(options)) { //ATTEMPT var book = context.Books .Single(p => p.Title == "Quantum Networking"); context.Remove(book); context.SaveChanges(); //VERIFY context.Books.Count().ShouldEqual(3); context.Set <BookAuthor>().Count().ShouldEqual(3); context.Set <Review>().Count().ShouldEqual(0); } }
public async Task TestProjectSingleWhereBad() { //SETUP var options = SqliteInMemory.CreateOptions <EfCoreContext>(); using (var context = new EfCoreContext(options)) { context.Database.EnsureCreated(); context.SeedDatabaseFourBooks(); var utData = context.SetupSingleDtoAndEntities <BookTitleAndCount>(); var service = new CrudServicesAsync(context, utData.ConfigAndMapper); //ATTEMPT var ex = await Assert.ThrowsAsync <InvalidOperationException>(() => service.ReadSingleAsync <BookTitleAndCount>(x => true)); //VERIFY #if NETCOREAPP2_1 ex.Message.ShouldEqual("Source sequence contains more than one element."); #elif NETCOREAPP3_0 ex.Message.ShouldEqual("Enumerator failed to MoveNextAsync."); #endif } }
public void TestComplexAutoMapperOk() { //SETUP var showLog = false; var options = SqliteInMemory.CreateOptionsWithLogging <EfCoreContext>(log => { if (showLog) { _output.WriteLine(log.Message); } }); using (var context = new EfCoreContext(options)) { context.Database.EnsureCreated(); var lastBook = context.SeedDatabaseFourBooks().Last(); var config = CreateFromProfileConfig(); //ATTEMPT showLog = true; var dto = context.Books .ProjectTo <BookListDto>(config) .Single(x => x.BookId == lastBook.BookId); //VERIFY dto.BookId.ShouldEqual(lastBook.BookId); dto.Title.ShouldEqual(lastBook.Title); dto.PublishedOn.ShouldEqual(lastBook.PublishedOn); dto.Price.ShouldEqual(lastBook.Price); dto.ActualPrice.ShouldEqual(lastBook.Promotion.NewPrice); dto.PromotionPromotionalText.ShouldEqual(lastBook.Promotion.PromotionalText); dto.AuthorsOrdered.ShouldEqual("Future Person"); dto.ReviewsCount.ShouldEqual(2); dto.ReviewsAverageVotes.ShouldEqual(5.0); } }
public void ValidateOk() { //SETUP var userId = Guid.NewGuid(); var options = SqliteInMemory.CreateOptions <EfCoreContext>(); using (var context = new EfCoreContext(options, new FakeUserIdService(userId))) { context.Database.EnsureCreated(); context.SeedDatabaseFourBooks(); } using (var context = new EfCoreContext(options, new FakeUserIdService(userId))) { //ATTEMPT var order = new Order { CustomerId = userId, LineItems = new List <LineItem> { new LineItem { BookId = context.Books.First().BookId, LineNum = 1, BookPrice = 123, NumBooks = 1 } } }; context.Orders.Add(order); var errors = context.SaveChangesWithValidation(); //VERIFY errors.Any().ShouldBeFalse(); context.Orders.Count().ShouldEqual(1); } }
public void TestAddOrderNoAcceptBad() { //SETUP var options = SqliteInMemory.CreateOptions <EfCoreContext>(); using (var context = new EfCoreContext(options)) { context.Database.EnsureCreated(); context.SeedDatabaseFourBooks(); var service = new PlaceOrderAction(new PlaceOrderDbAccess(context)); var dto = new PlaceOrderInDto { AcceptTAndCs = false, }; //ATTEMPT var order = service.BizAction(dto); //VERIFY service.HasErrors.ShouldBeTrue(); service.Errors.Single().ErrorResult.ErrorMessage.ShouldEqual("You must accept the T&Cs to place an order."); } }
public void TestPlaceOrderBad() { //SETUP var options = EfInMemory.CreateNewContextOptions(); using (var context = new EfCoreContext(options)) { context.SeedDatabaseFourBooks(); } using (var context = new EfCoreContext(options)) { var mockCookieRequests = new MockHttpCookieAccess(CheckoutCookie.CheckoutCookieName, $"{Guid.NewGuid()},1,2"); var service = new PlaceOrderService(mockCookieRequests.CookiesIn, mockCookieRequests.CookiesOut, context); //ATTEMPT var orderId = service.PlaceOrder(false); context.SaveChanges(); //VERIFY orderId.ShouldEqual(0); service.Errors.Count.ShouldEqual(1); service.Errors.First().ErrorMessage.ShouldEqual("You must accept the T&Cs to place an order."); } }
public void TestReadSingleWhereNullIsError(int bookId, bool shouldBeValid) { //SETUP var options = SqliteInMemory.CreateOptions <EfCoreContext>(); using (var context = new EfCoreContext(options)) { context.Database.EnsureCreated(); context.SeedDatabaseFourBooks(); var utData = context.SetupSingleDtoAndEntities <BookTitle>(); var service = new CrudServices(context, utData.ConfigAndMapper); //ATTEMPT var book = service.ReadSingle <Book>(x => x.BookId == bookId); //VERIFY service.IsValid.ShouldEqual(shouldBeValid); if (!service.IsValid) { service.GetAllErrors().ShouldEqual("Sorry, I could not find the Book you were looking for."); } } }
public void TestPlaceOrderBad() { //SETUP var userId = Guid.NewGuid(); var options = SqliteInMemory.CreateOptions <EfCoreContext>(); using (var context = new EfCoreContext(options, new FakeUserIdService(userId))) { context.Database.EnsureCreated(); context.SeedDatabaseFourBooks(); var mockCookieRequests = new MockHttpCookieAccess(BasketCookie.BasketCookieName, $"{Guid.NewGuid()},1,2"); var service = new PlaceOrderServiceTransact(mockCookieRequests.CookiesIn, mockCookieRequests.CookiesOut, context); //ATTEMPT var orderId = service.PlaceOrder(false); context.SaveChanges(); //VERIFY orderId.ShouldEqual(0); service.Errors.Count.ShouldEqual(1); service.Errors.First().ErrorMessage.ShouldEqual("You must accept the T&Cs to place an order."); } }
public void TestEagerLoadBookAllOk() { //SETUP var options = SqliteInMemory.CreateOptions <EfCoreContext>(); using var context = new EfCoreContext(options); context.Database.EnsureCreated(); context.SeedDatabaseFourBooks(); context.ChangeTracker.Clear(); //ATTEMPT var firstBook = context.Books .Include(book => book.AuthorsLink) //#A .ThenInclude(bookAuthor => bookAuthor.Author) //#B .Include(book => book.Reviews) //#C .Include(book => book.Tags) //#D .Include(book => book.Promotion) //#E .First(); //#F /********************************************************* #A The first Include() gets a collection of BookAuthor #B The ThenInclude() gets the next link, in this case the link to the Author #C The Include() gets a collection of Reviews, which may be an empty collection #D This loads the Tags. Note that this directly accesses the Tags #E This loads any optional PriceOffer class, if one is assigned #F This takes the first book * *******************************************************/ //VERIFY firstBook.AuthorsLink.ShouldNotBeNull(); firstBook.AuthorsLink.First() .Author.ShouldNotBeNull(); firstBook.Reviews.ShouldNotBeNull(); }
public void TestCallUpdateBookWithExistingAuthorWithTwoIncludes() { //SETUP var options = SqliteInMemory.CreateOptions <EfCoreContext>(); using (var context = new EfCoreContext(options)) { context.Database.EnsureCreated(); var bookSeeded = context.SeedDatabaseFourBooks(); var bookId = bookSeeded.First().BookId; var authorId = bookSeeded.Last().AuthorsLink.First().AuthorId; context.ChangeTracker.Clear(); var utData = context.SetupSingleDtoAndEntities <AddNewAuthorToBookUsingIncludesDto>(); var service = new CrudServices(context, utData.ConfigAndMapper); //ATTEMPT var dto = new AddNewAuthorToBookUsingIncludesDto { BookId = bookId, AddThisAuthor = context.Authors.SingleOrDefault(x => x.AuthorId == authorId), Order = 2 }; service.UpdateAndSave(dto); //VERIFY service.IsValid.ShouldBeTrue(service.GetAllErrors()); service.Message.ShouldEqual("Successfully updated the Book"); var bookAuthorsName = context.Books .Where(x => x.BookId == bookId) .SelectMany(x => x.AuthorsLink.Select(y => y.Author.Name)) .ToArray(); bookAuthorsName.ShouldEqual(new string[] { "Martin Fowler", "Future Person" }); } }
public void TestEagerLoadBookAllOk() { //SETUP var options = SqliteInMemory.CreateOptions <EfCoreContext>(); using (var context = new EfCoreContext(options)) { context.Database.EnsureCreated(); context.SeedDatabaseFourBooks(); } using (var context = new EfCoreContext(options)) { //ATTEMPT var book = context.Books .Include(r => r.AuthorsLink) //#A .ThenInclude(r => r.Author) //#B .Include(r => r.Reviews) //#C .Include(r => r.Promotion) //#D .First(); //#E /********************************************************* #A The first Include() gets a collection of BookAuthor #B The ThenInclude() gets the next link, in this case the link to the Author #C The Include() gets a collection of Reviews, which may be an empty collection #D This loads any optional PriceOffer class, if one is assigned #E This takes the first book * *******************************************************/ //VERIFY book.AuthorsLink.ShouldNotBeNull(); book.AuthorsLink.First() .Author.ShouldNotBeNull(); book.Reviews.ShouldNotBeNull(); } }
public void TestCreateBookWithExistingAuthor_CheckThreeStagesOk() { //SETUP var options = SqliteInMemory.CreateOptions <EfCoreContext>(); using (var context = new EfCoreContext(options)) { context.Database.EnsureCreated(); context.SeedDatabaseFourBooks(); } using (var context = new EfCoreContext(options)) { //STAGE1 var author = context.Authors.First(); var bookAuthor = new BookAuthor { Author = author }; var book = new Book { Title = "Test Book", AuthorsLink = new List <BookAuthor> { bookAuthor } }; //Read the states context.Entry(author).State.ShouldEqual(EntityState.Unchanged); context.Entry(bookAuthor).State.ShouldEqual(EntityState.Detached); context.Entry(book).State.ShouldEqual(EntityState.Detached); //These navigational links have not been set up book.AuthorsLink.Single().Book.ShouldBeNull(); author.BooksLink.ShouldBeNull(); //tracked PK/FK values not set context.Entry(book).Property(nameof(Book.BookId)).CurrentValue.ShouldEqual(0); context.Entry(bookAuthor).Property(nameof(BookAuthor.BookId)).CurrentValue.ShouldEqual(0); context.Entry(bookAuthor).Property(nameof(BookAuthor.AuthorId)).CurrentValue.ShouldEqual(0); //STAGE2 context.Add(book); //Read the states context.Entry(author).State.ShouldEqual(EntityState.Unchanged); context.Entry(bookAuthor).State.ShouldEqual(EntityState.Added); context.Entry(book).State.ShouldEqual(EntityState.Added); //Extra Navigational links filled in after call to Add book.AuthorsLink.Single().Book.ShouldEqual(book); author.BooksLink.Single().ShouldEqual(book.AuthorsLink.Single()); //Real PKs/FKs book.BookId.ShouldEqual(0); book.AuthorsLink.Single().BookId.ShouldEqual(0); book.AuthorsLink.Single().AuthorId.ShouldEqual(author.AuthorId); //tracked PK/FK values of new entities should be negative for Added classes, or actual PK if read in/Unchanged var tempBookId = (int)context.Entry(bookAuthor).Property(nameof(BookAuthor.BookId)).CurrentValue; (tempBookId < 0).ShouldBeTrue(); context.Entry(book).Property(nameof(Book.BookId)).CurrentValue.ShouldEqual(tempBookId); ((int)context.Entry(bookAuthor).Property(nameof(BookAuthor.AuthorId)).CurrentValue).ShouldEqual(author.AuthorId); //STAGE3 context.SaveChanges(); context.Entry(author).State.ShouldEqual(EntityState.Unchanged); context.Entry(bookAuthor).State.ShouldEqual(EntityState.Unchanged); context.Entry(book).State.ShouldEqual(EntityState.Unchanged); book.BookId.ShouldEqual(5); book.AuthorsLink.Single().BookId.ShouldEqual(book.BookId); book.AuthorsLink.Single().AuthorId.ShouldEqual(author.AuthorId); } }