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

Fix some issues #2613

Merged
merged 1 commit into from
Sep 5, 2020
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
24 changes: 12 additions & 12 deletions entity-framework/core/querying/filters.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,23 @@ uid: core/querying/filters
> [!NOTE]
> This feature was introduced in EF Core 2.0.

Global query filters are LINQ query predicates (a boolean expression typically passed to the LINQ *Where* query operator) applied to Entity Types in the metadata model (usually in *OnModelCreating*). Such filters are automatically applied to any LINQ queries involving those Entity Types, including Entity Types referenced indirectly, such as through the use of Include or direct navigation property references. Some common applications of this feature are:
Global query filters are LINQ query predicates applied to Entity Types in the metadata model (usually in *OnModelCreating*). A query predicate is a boolean expression typically passed to the LINQ *Where* query operator. EF Core applies such filters automatically to any LINQ queries involving those Entity Types. EF Core also applies them to Entity Types, referenced indirectly through use of Include or navigation property. Some common applications of this feature are:

* **Soft delete** - An Entity Type defines an *IsDeleted* property.
* **Multi-tenancy** - An Entity Type defines a *TenantId* property.

## Example

The following example shows how to use Global Query Filters to implement soft-delete and multi-tenancy query behaviors in a simple blogging model.
The following example shows how to use Global Query Filters to implement multi-tenancy and soft-delete query behaviors in a simple blogging model.

> [!TIP]
> You can view a [multi-tenancy sample](https://github.com/dotnet/EntityFramework.Docs/tree/master/samples/core/QueryFilters) and [samples using navigations](https://github.com/dotnet/EntityFramework.Docs/tree/master/samples/core/QueryFiltersNavigations) on GitHub.
> You can view a [multi-tenancy sample](https://github.com/dotnet/EntityFramework.Docs/tree/master/samples/core/QueryFilters) and [samples using navigations](https://github.com/dotnet/EntityFramework.Docs/tree/master/samples/core/QueryFiltersNavigations) on GitHub.

First, define the entities:

[!code-csharp[Main](../../../samples/core/QueryFilters/Program.cs#Entities)]

Note the declaration of a _tenantId_ field on the _Blog_ entity. This will be used to associate each Blog instance with a specific tenant. Also defined is an _IsDeleted_ property on the _Post_ entity type. This is used to keep track of whether a _Post_ instance has been "soft-deleted". That is, the instance is marked as deleted without physically removing the underlying data.
Note the declaration of a _tenantId_ field on the _Blog_ entity. This field will be used to associate each Blog instance with a specific tenant. Also defined is an _IsDeleted_ property on the _Post_ entity type. This property is used to keep track of whether a _Post_ instance has been "soft-deleted". That is, the instance is marked as deleted without physically removing the underlying data.

Next, configure the query filters in _OnModelCreating_ using the `HasQueryFilter` API.

Expand All @@ -42,17 +42,17 @@ The predicate expressions passed to the _HasQueryFilter_ calls will now automati

## Use of navigations

Navigations can be used when defining global query filters. They are applied recursively - when navigations used in query filters are translated, query filters defined on referenced entities are also applied, potentially adding more navigations.
You can also use navigations in defining global query filters. Using navigations in query filter will cause query filters to be applied recursively. When EF Core expands navigations used in query filters, it will also apply query filters defined on referenced entities.

> [!NOTE]
> Currently EF Core does not detect cycles in global query filter definitions, so you should be careful when defining them. If specified incorrectly, this could lead to infinite loops during query translation.
> Currently EF Core does not detect cycles in global query filter definitions, so you should be careful when defining them. If specified incorrectly, cycles could lead to infinite loops during query translation.

## Accessing entity with query filter using required navigation

> [!CAUTION]
> Using required navigation to access entity which has global query filter defined may lead to unexpected results.
> Using required navigation to access entity which has global query filter defined may lead to unexpected results.

Required navigation expects the related entity to always be present. If required related entity is filtered out by the query filter, the parent entity could end up in unexpected state. This may result in returning fewer elements than expected.
Required navigation expects the related entity to always be present. If necessary related entity is filtered out by the query filter, the parent entity wouldn't be in result either. So you may get fewer elements than expected in result.

To illustrate the problem, we can use the `Blog` and `Post` entities specified above and the following _OnModelCreating_ method:

Expand All @@ -72,7 +72,7 @@ The problem can be observed when executing two queries:

[!code-csharp[Main](../../../samples/core/QueryFiltersNavigations/Program.cs#Queries)]

With this setup, the first query returns all 6 `Post`s, however the second query only returns 3. This happens because _Include_ method in the second query loads the related `Blog` entities. Since the navigation between `Blog` and `Post` is required, EF Core uses `INNER JOIN` when constructing the query:
With above setup, the first query returns all 6 `Post`s, however the second query only returns 3. This mismatch happens because _Include_ method in the second query loads the related `Blog` entities. Since the navigation between `Blog` and `Post` is required, EF Core uses `INNER JOIN` when constructing the query:

```SQL
SELECT [p].[PostId], [p].[BlogId], [p].[Content], [p].[IsDeleted], [p].[Title], [t].[BlogId], [t].[Name], [t].[Url]
Expand All @@ -84,9 +84,9 @@ INNER JOIN (
) AS [t] ON [p].[BlogId] = [t].[BlogId]
```

Use of the `INNER JOIN` filters out all `Post`s whose related `Blog`s have been removed by a global query filter.
Use of the `INNER JOIN` filters out all `Post`s whose related `Blog`s have been removed by a global query filter.

It can be addressed by using optional navigation instead of required.
It can be addressed by using optional navigation instead of required.
This way the first query stays the same as before, however the second query will now generate `LEFT JOIN` and return 6 results.

```csharp
Expand All @@ -98,7 +98,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
```

Alternative approach is to specify consistent filters on both `Blog` and `Post` entities.
This way matching filters are applied to both `Blog` and `Post`. `Post`s that could end up in unexpected state are removed and both queries return 3 results.
This way matching filters are applied to both `Blog` and `Post`. `Post`s that could end up in unexpected state are removed and both queries return 3 results.

```csharp
protected override void OnModelCreating(ModelBuilder modelBuilder)
Expand Down
14 changes: 7 additions & 7 deletions entity-framework/core/querying/how-query-works.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,28 +13,28 @@ Entity Framework Core uses Language Integrated Query (LINQ) to query data from t

## The life of a query

The following is a high level overview of the process each query goes through.
The following description is a high-level overview of the process each query goes through.

1. The LINQ query is processed by Entity Framework Core to build a representation that is ready to be processed by the database provider
1. The result is cached so that this processing does not need to be done every time the query is executed
2. The result is passed to the database provider
1. The database provider identifies which parts of the query can be evaluated in the database
2. These parts of the query are translated to database specific query language (for example, SQL for a relational database)
2. These parts of the query are translated to database-specific query language (for example, SQL for a relational database)
3. A query is sent to the database and the result set returned (results are values from the database, not entity instances)
3. For each item in the result set
1. If this is a tracking query, EF checks if the data represents an entity already in the change tracker for the context instance
1. If the query is a tracking query, EF checks if the data represents an entity already in the change tracker for the context instance
* If so, the existing entity is returned
* If not, a new entity is created, change tracking is setup, and the new entity is returned
2. If this is a no-tracking query, then a new entity is always created and returned
* If not, a new entity is created, change tracking is set up, and the new entity is returned
2. If the query is a no-tracking query, then a new entity is always created and returned

## When queries are executed

When you call LINQ operators, you are simply building up an in-memory representation of the query. The query is only sent to the database when the results are consumed.
When you call LINQ operators, you're simply building up an in-memory representation of the query. The query is only sent to the database when the results are consumed.

The most common operations that result in the query being sent to the database are:

* Iterating the results in a `for` loop
* Using an operator such as `ToList`, `ToArray`, `Single`, `Count` or the equivalent async overloads
* Using an operator such as `ToList`, `ToArray`, `Single`, `Count`, or the equivalent async overloads

> [!WARNING]
> **Always validate user input:** While EF Core protects against SQL injection attacks by using parameters and escaping literals in queries, it does not validate inputs. Appropriate validation, per the application's requirements, should be performed before values from un-trusted sources are used in LINQ queries, assigned to entity properties, or passed to other EF Core APIs. This includes any user input used to dynamically construct queries. Even when using LINQ, if you are accepting user input to build expressions, you need to make sure that only intended expressions can be constructed.
Loading