Skip to content

Commit

Permalink
Document ToInMemoryQuery and re-introduce sample code (#3745)
Browse files Browse the repository at this point in the history
Fixes #3744
  • Loading branch information
roji authored Mar 1, 2022
1 parent e8e7e34 commit 0bf3ee3
Show file tree
Hide file tree
Showing 9 changed files with 56 additions and 11 deletions.
2 changes: 1 addition & 1 deletion entity-framework/core/modeling/entity-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <xref:Microsoft.EntityFrameworkCore.InMemoryEntityTypeBuilderExtensions.ToInMemoryQuery%2A>. See the [in-memory provider docs](xref:core/testing/testing-without-the-database#in-memory-provider) for more information.
## Table-valued function mapping

Expand Down
2 changes: 1 addition & 1 deletion entity-framework/core/modeling/keyless-entity-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <xref:Microsoft.EntityFrameworkCore.InMemoryEntityTypeBuilderExtensions.ToInMemoryQuery%2A>. See the [in-memory provider docs](xref:core/testing/testing-without-the-database#in-memory-provider) for more information.
14 changes: 12 additions & 2 deletions entity-framework/core/testing/testing-without-the-database.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,16 +48,26 @@ 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

As discussed in the [testing overview page](xref:core/testing/choosing-a-testing-strategy#inmemory-as-a-database-fake), using the in-memory provider for testing is strongly discouraged; [consider using SQLite instead](#sqlite-in-memory), or [implementing the repository pattern](#repository-pattern). If you've decided to use in-memory, here is a typical test class constructor that sets up and seeds a new in-memory database before each test:

[!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 <xref:Microsoft.EntityFrameworkCore.Storage.InMemoryDatabaseRoot> 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 <xref:Microsoft.EntityFrameworkCore.InMemoryEntityTypeBuilderExtensions.ToInMemoryQuery%2A>:

[!code-csharp[Main](../../../samples/core/Testing/TestingWithoutTheDatabase/InMemoryBloggingControllerTest.cs?name=ToInMemoryQuery)]
18 changes: 16 additions & 2 deletions samples/core/Testing/BusinessLogic/BloggingContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,22 @@ namespace EF.Testing.BusinessLogic
{
public class BloggingContext : DbContext
{
private readonly Action<BloggingContext, ModelBuilder> _modelCustomizer;

#region Constructors
public BloggingContext()
{
}

public BloggingContext(DbContextOptions<BloggingContext> options)
public BloggingContext(DbContextOptions<BloggingContext> options, Action<BloggingContext, ModelBuilder> modelCustomizer = null)
: base(options)
{
_modelCustomizer = modelCustomizer;
}

#endregion

public DbSet<Blog> Blogs => Set<Blog>();
public DbSet<UrlResource> UrlResources => Set<UrlResource>();

#region OnConfiguring
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
Expand All @@ -29,5 +32,16 @@ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
}
}
#endregion

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<UrlResource>().HasNoKey()
.ToView("AllResources");

if (_modelCustomizer is not null)
{
_modelCustomizer(this, modelBuilder);
}
}
}
}
2 changes: 1 addition & 1 deletion samples/core/Testing/BusinessLogic/BusinessLogic.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.2" />
</ItemGroup>

</Project>
7 changes: 7 additions & 0 deletions samples/core/Testing/BusinessLogic/UrlResource.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace EF.Testing.BusinessLogic
{
public class UrlResource
{
public string Url { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<UrlResource>()
.ToInMemoryQuery(() => context.Blogs.Select(b => new UrlResource { Url = b.Url }));
#endregion
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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" },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="5.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="6.0.2" />

<PackageReference Include="Moq" Version="4.16.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
Expand Down

0 comments on commit 0bf3ee3

Please sign in to comment.