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);
            }
        }
Example #2
0
        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);
            }
        }
Example #5
0
        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();
        }
Example #6
0
        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 });
            }
        }
Example #12
0
        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);
        }
Example #13
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");
            }
        }
Example #16
0
        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!");
            }
        }
Example #18
0
        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);
            }
        }
Example #20
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.");
            }
        }
Example #24
0
        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.");
            }
        }
Example #25
0
        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.");
            }
        }
Example #27
0
        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);
            }
        }