Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Document ToInMemoryQuery and re-introduce sample code #3745

Merged
merged 1 commit into from
Mar 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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