diff --git a/entity-framework/core/modeling/entity-types.md b/entity-framework/core/modeling/entity-types.md index bc9c48cb2c..d4dc83bbe4 100644 --- a/entity-framework/core/modeling/entity-types.md +++ b/entity-framework/core/modeling/entity-types.md @@ -98,7 +98,7 @@ Entity types can be mapped to database views using the Fluent API. Mapping to a view will remove the default table mapping, but starting with EF 5.0 the entity type can also be mapped to a table explicitly. In this case the query mapping will be used for queries and the table mapping will be used for updates. > [!TIP] -> To test entity types mapped to views using the in-memory provider map them to a query via `ToInMemoryQuery`. See a [runnable sample](https://github.com/dotnet/EntityFramework.Docs/tree/main/samples/core/Miscellaneous/Testing/ItemsWebApi/) using this technique for more details. +> To test keyless entity types mapped to views using the in-memory provider, map them to a query via . See the [in-memory provider docs](xref:core/testing/testing-without-the-database#in-memory-provider) for more information. ## Table-valued function mapping diff --git a/entity-framework/core/modeling/keyless-entity-types.md b/entity-framework/core/modeling/keyless-entity-types.md index 31ff5cca7d..3c1f089533 100644 --- a/entity-framework/core/modeling/keyless-entity-types.md +++ b/entity-framework/core/modeling/keyless-entity-types.md @@ -95,4 +95,4 @@ Finally, we can query the database view in the standard way: > Note we have also defined a context level query property (DbSet) to act as a root for queries against this type. > [!TIP] -> To test keyless entity types mapped to views using the in-memory provider map them to a query via `ToInMemoryQuery`. See a [runnable sample](https://github.com/dotnet/EntityFramework.Docs/tree/main/samples/core/Miscellaneous/Testing/ItemsWebApi/) using this technique for more details. +> To test keyless entity types mapped to views using the in-memory provider, map them to a query via . See the [in-memory provider docs](xref:core/testing/testing-without-the-database#in-memory-provider) for more information. diff --git a/entity-framework/core/testing/testing-without-the-database.md b/entity-framework/core/testing/testing-without-the-database.md index d3a02fe869..9b3ef15a5e 100644 --- a/entity-framework/core/testing/testing-without-the-database.md +++ b/entity-framework/core/testing/testing-without-the-database.md @@ -48,7 +48,7 @@ To use in-memory SQLite, it's important to understand that a new database is cre Tests can now call `CreateContext`, which returns a context using the connection we set up in the constructor, ensuring we have a clean database with the seeded data. -The full sample code can be viewed [here](https://github.com/dotnet/EntityFramework.Docs/blob/main/samples/core/Testing/TestingWithoutTheDatabase/SqliteInMemoryBloggingControllerTest.cs). +The full sample code for SQLite in-memory testing can be viewed [here](https://github.com/dotnet/EntityFramework.Docs/blob/main/samples/core/Testing/TestingWithoutTheDatabase/SqliteInMemoryBloggingControllerTest.cs). ## In-memory provider @@ -56,8 +56,18 @@ As discussed in the [testing overview page](xref:core/testing/choosing-a-testing [!code-csharp[Main](../../../samples/core/Testing/TestingWithoutTheDatabase/InMemoryBloggingControllerTest.cs?name=Constructor)] +The full sample code for in-memory testing can be viewed [here](https://github.com/dotnet/EntityFramework.Docs/blob/main/samples/core/Testing/TestingWithoutTheDatabase/InMemoryBloggingControllerTest.cs). + +### In-memory database naming + In-memory databases are identified by a simple, string name, and it's possible to connect to the same database several times by providing the same name (this is why the sample above must call `EnsureDeleted` before each test). However, note that in-memory databases are rooted in the context's internal service provider; while in most cases contexts share the same service provider, configuring contexts with different options may trigger the use of a new internal service provider. When that's the case, explicitly pass the same instance of to `UseInMemoryDatabase` for all contexts which should share in-memory databases (this is typically done by having a static `InMemoryDatabaseRoot` field). +### Transactions + Note that by default, if a transaction is started, the in-memory provider will throw an exception since transactions aren't supported. You may wish to have transactions silently ignored instead, by configuring EF Core to ignore `InMemoryEventId.TransactionIgnoredWarning` as in the above sample. However, if your code actually relies on transactional semantics - e.g. depends on rollback actually rolling back changes - your test won't work. -The full sample code can be viewed [here](https://github.com/dotnet/EntityFramework.Docs/blob/main/samples/core/Testing/TestingWithoutTheDatabase/InMemoryBloggingControllerTest.cs). +### Views + +The in-memory provider allows the definition of views via LINQ queries, using : + +[!code-csharp[Main](../../../samples/core/Testing/TestingWithoutTheDatabase/InMemoryBloggingControllerTest.cs?name=ToInMemoryQuery)] diff --git a/samples/core/Testing/BusinessLogic/BloggingContext.cs b/samples/core/Testing/BusinessLogic/BloggingContext.cs index f8bc95a8f8..47439545f4 100644 --- a/samples/core/Testing/BusinessLogic/BloggingContext.cs +++ b/samples/core/Testing/BusinessLogic/BloggingContext.cs @@ -5,19 +5,22 @@ namespace EF.Testing.BusinessLogic { public class BloggingContext : DbContext { + private readonly Action _modelCustomizer; + #region Constructors public BloggingContext() { } - public BloggingContext(DbContextOptions options) + public BloggingContext(DbContextOptions options, Action modelCustomizer = null) : base(options) { + _modelCustomizer = modelCustomizer; } - #endregion public DbSet Blogs => Set(); + public DbSet UrlResources => Set(); #region OnConfiguring protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) @@ -29,5 +32,16 @@ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) } } #endregion + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity().HasNoKey() + .ToView("AllResources"); + + if (_modelCustomizer is not null) + { + _modelCustomizer(this, modelBuilder); + } + } } } diff --git a/samples/core/Testing/BusinessLogic/BusinessLogic.csproj b/samples/core/Testing/BusinessLogic/BusinessLogic.csproj index 00665806a1..265b2ec94d 100644 --- a/samples/core/Testing/BusinessLogic/BusinessLogic.csproj +++ b/samples/core/Testing/BusinessLogic/BusinessLogic.csproj @@ -7,7 +7,7 @@ - + diff --git a/samples/core/Testing/BusinessLogic/UrlResource.cs b/samples/core/Testing/BusinessLogic/UrlResource.cs new file mode 100644 index 0000000000..800fc8e01b --- /dev/null +++ b/samples/core/Testing/BusinessLogic/UrlResource.cs @@ -0,0 +1,7 @@ +namespace EF.Testing.BusinessLogic +{ + public class UrlResource + { + public string Url { get; set; } + } +} diff --git a/samples/core/Testing/TestingWithoutTheDatabase/InMemoryBloggingControllerTest.cs b/samples/core/Testing/TestingWithoutTheDatabase/InMemoryBloggingControllerTest.cs index 3234e855e6..24eab33723 100644 --- a/samples/core/Testing/TestingWithoutTheDatabase/InMemoryBloggingControllerTest.cs +++ b/samples/core/Testing/TestingWithoutTheDatabase/InMemoryBloggingControllerTest.cs @@ -83,6 +83,12 @@ public void UpdateBlogUrl() Assert.Equal("http://blog2_updated.com", blog.Url); } - BloggingContext CreateContext() => new BloggingContext(_contextOptions); + BloggingContext CreateContext() => new BloggingContext(_contextOptions, (context, modelBuilder) => + { + #region ToInMemoryQuery + modelBuilder.Entity() + .ToInMemoryQuery(() => context.Blogs.Select(b => new UrlResource { Url = b.Url })); + #endregion + }); } } diff --git a/samples/core/Testing/TestingWithoutTheDatabase/SqliteInMemoryBloggingControllerTest.cs b/samples/core/Testing/TestingWithoutTheDatabase/SqliteInMemoryBloggingControllerTest.cs index 74ad2d5c8d..e36557d51a 100644 --- a/samples/core/Testing/TestingWithoutTheDatabase/SqliteInMemoryBloggingControllerTest.cs +++ b/samples/core/Testing/TestingWithoutTheDatabase/SqliteInMemoryBloggingControllerTest.cs @@ -30,7 +30,15 @@ public SqliteInMemoryBloggingControllerTest() // Create the schema and seed some data using var context = new BloggingContext(_contextOptions); - context.Database.EnsureCreated(); + if (context.Database.EnsureCreated()) + { + using var viewCommand = context.Database.GetDbConnection().CreateCommand(); + viewCommand.CommandText = @" +CREATE VIEW AllResources AS +SELECT Url +FROM Blogs;"; + viewCommand.ExecuteNonQuery(); + } context.AddRange( new Blog { Name = "Blog1", Url = "http://blog1.com" }, diff --git a/samples/core/Testing/TestingWithoutTheDatabase/TestingWithoutTheDatabase.csproj b/samples/core/Testing/TestingWithoutTheDatabase/TestingWithoutTheDatabase.csproj index c874712af1..5781d7eb46 100644 --- a/samples/core/Testing/TestingWithoutTheDatabase/TestingWithoutTheDatabase.csproj +++ b/samples/core/Testing/TestingWithoutTheDatabase/TestingWithoutTheDatabase.csproj @@ -8,8 +8,8 @@ - - + +