Skip to content

Commit

Permalink
Fixing several doc issues.
Browse files Browse the repository at this point in the history
Fixes #486
Fixes #756
Fixes #1311
Fixes #2307
Fixes #2511
Fixes #2731
Fixes #2823
  • Loading branch information
maumar committed Nov 9, 2020
1 parent 81b7481 commit 286796f
Show file tree
Hide file tree
Showing 14 changed files with 252 additions and 10 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion entity-framework/core/modeling/inheritance.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ By default, EF maps the inheritance using the *table-per-hierarchy* (TPH) patter

The model above is mapped to the following database schema (note the implicitly-created `Discriminator` column, which identifies which type of `Blog` is stored in each row).

![image](_static/inheritance-tph-data.png)
![Screenshot of the results of querying the Blog entity hierarchy using table-per-hierarchy pattern](_static/inheritance-tph-data.png)

You can configure the name and type of the discriminator column and the values that are used to identify each type in the hierarchy:

Expand Down
12 changes: 12 additions & 0 deletions entity-framework/core/modeling/owned-entities.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ If the `ShippingAddress` property is private in the `Order` type, you can use th

[!code-csharp[OwnsOneString](../../../samples/core/Modeling/OwnedEntities/OwnedEntityContext.cs?name=OwnsOneString)]

The model above is mapped to the following database schema:

![Sceenshot of the database model for entity containing owned reference](_static/owned-entities-ownsone.png)

See the [full sample project](https://github.com/dotnet/EntityFramework.Docs/tree/master/samples/core/Modeling/OwnedEntities) for more context.

> [!TIP]
Expand Down Expand Up @@ -63,6 +67,10 @@ To configure a different primary key call `HasKey`.

[!code-csharp[OwnsMany](../../../samples/core/Modeling/OwnedEntities/OwnedEntityContext.cs?name=OwnsMany)]

The model above is mapped to the following database schema:

![Sceenshot of the database model for entity containing owned collection](_static/owned-entities-ownsmany.png)

## Mapping owned types with table splitting

When using relational databases, by default reference owned types are mapped to the same table as the owner. This requires splitting the table in two: some columns will be used to store the data of the owner, and some columns will be used to store data of the owned entity. This is a common feature known as [table splitting](xref:core/modeling/table-splitting).
Expand Down Expand Up @@ -114,6 +122,10 @@ It is also possible to achieve this result using `OwnedAttribute` on both `Order

In addition, notice the `Navigation` call. In EFCore 5.0, navigation properties to owned types can be further configured [as for non-owned navigation properties](xref:core/modeling/relationships#configuring-navigation-properties).

The model above is mapped to the following database schema:

![Sceenshot of the database model for entity containing nested owned references](_static/owned-entities-nested.png)

## Storing owned types in separate tables

Also unlike EF6 complex types, owned types can be stored in a separate table from the owner. In order to override the convention that maps an owned type to the same table as the owner, you can simply call `ToTable` and provide a different table name. The following example will map `OrderDetails` and its two addresses to a separate table from `DetailedOrder`:
Expand Down
12 changes: 11 additions & 1 deletion entity-framework/core/querying/complex-query-operators.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Language Integrated Query (LINQ) contains many complex operators, which combine
## Join

The LINQ Join operator allows you to connect two data sources based on the key selector for each source, generating a tuple of values when the key matches. It naturally translates to `INNER JOIN` on relational databases. While the LINQ Join has outer and inner key selectors, the database requires a single join condition. So EF Core generates a join condition by comparing the outer key selector to the inner key selector for equality. Further, if the key selectors are anonymous types, EF Core generates a join condition to compare equality component wise.
The LINQ Join operator allows you to connect two data sources based on the key selector for each source, generating a tuple of values when the key matches. It naturally translates to `INNER JOIN` on relational databases. While the LINQ Join has outer and inner key selectors, the database requires a single join condition. So EF Core generates a join condition by comparing the outer key selector to the inner key selector for equality.

[!code-csharp[Main](../../../samples/core/Querying/ComplexQuery/Program.cs#Join)]

Expand All @@ -24,6 +24,16 @@ FROM [PersonPhoto] AS [p0]
INNER JOIN [Person] AS [p] ON [p0].[PersonPhotoId] = [p].[PhotoId]
```

Further, if the key selectors are anonymous types, EF Core generates a join condition to compare equality component-wise.

[!code-csharp[Main](../../../samples/core/Querying/ComplexQuery/Program.cs#JoinComposite)]

```sql
SELECT [p].[PersonId], [p].[Name], [p].[PhotoId], [p0].[PersonPhotoId], [p0].[Caption], [p0].[Photo]
FROM [PersonPhoto] AS [p0]
INNER JOIN [Person] AS [p] ON ([p0].[PersonPhotoId] = [p].[PhotoId] AND ([p0].[Caption] = N'SN'))
```

## GroupJoin

The LINQ GroupJoin operator allows you to connect two data sources similar to Join, but it creates a group of inner values for matching outer elements. Executing a query like the following example generates a result of `Blog` & `IEnumerable<Post>`. Since databases (especially relational databases) don't have a way to represent a collection of client-side objects, GroupJoin doesn't translate to the server in many cases. It requires you to get all of the data from the server to do GroupJoin without a special selector (first query below). But if the selector is limiting data being selected then fetching all of the data from the server may cause performance issues (second query below). That's why EF Core doesn't translate GroupJoin.
Expand Down
17 changes: 17 additions & 0 deletions entity-framework/core/querying/filters.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,23 @@ The predicate expressions passed to the `HasQueryFilter` calls will now automati

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.

To illustrate this configure query filters in `OnModelCreating` in the following way:
[!code-csharp[Main](../../../samples/core/Querying/QueryFilters/FilteredBloggingContextRequired.cs#NavigationInFilter)]

Next, query for all `Blog` entities:
[!code-csharp[Main](../../../samples/core/Querying/QueryFilters/FilteredBloggingContextRequired.cs#QueriesNavigation)]

This query produces the following SQL, which applies query filters defined for both `Blog` and `Post` entities:

```sql
SELECT [b].[BlogId], [b].[Name], [b].[Url]
FROM [Blogs] AS [b]
WHERE (
SELECT COUNT(*)
FROM [Posts] AS [p]
WHERE ([p].[Title] LIKE N'%fish%') AND ([b].[BlogId] = [p].[BlogId])) > 0
```

> [!NOTE]
> 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.
Expand Down
5 changes: 4 additions & 1 deletion entity-framework/core/querying/related-data/eager.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ You may want to include multiple related entities for one of the entities that i
> [!NOTE]
> This feature is introduced in EF Core 5.0.
When applying Include to load related data, you can apply certain enumerable operations on the included collection navigation, which allows for filtering and sorting of the results.
When applying Include to load related data, you can add certain enumerable operations to the included collection navigation, which allows for filtering and sorting of the results.

Supported operations are: `Where`, `OrderBy`, `OrderByDescending`, `ThenBy`, `ThenByDescending`, `Skip`, and `Take`.

Expand Down Expand Up @@ -74,6 +74,9 @@ var orders = context.Orders.Where(o => o.Id > 1000).ToList();
var filtered = context.Customers.Include(c => c.Orders.Where(o => o.Id > 5000)).ToList();
```

> [!NOTE]
> In case of tracking queries, the navigation on which filtered include was applied is considered to be loaded. This means that EF Core will not attempt to re-load it's values using [explicit loading](xref:core/querying/related-data/explicit) or [lazy loading](xref:core/querying/related-data/lazy), even though some elements could still be missing.
## Include on derived types

You can include related data from navigation defined only on a derived type using `Include` and `ThenInclude`.
Expand Down
89 changes: 89 additions & 0 deletions entity-framework/core/what-is-new/ef-core-5.0/breaking-changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ The following API and behavior changes have the potential to break existing appl
| [IndexBuilder.HasName is now obsolete](#index-obsolete) | Low |
| [A pluarlizer is now included for scaffolding reverse engineered models](#pluralizer) | Low |
| [INavigationBase replaces INavigation in some APIs to support skip navigations](#inavigationbase) | Low |
| [Some queries with correlated collection that also use `Distinct` or `GroupBy` are no longer supported](#collection-distinct-groupby) | Low |
| [Using a collection of Queryable type in projection is not supported](#queryable-projection) | Low |

## Medium-impact changes

Expand Down Expand Up @@ -450,3 +452,90 @@ Most of the functionality between normal and skip navigations is the same. Howev
#### Mitigations

In many cases applications can switch to using the new base interface with no other changes. However, in cases where the navigation is used to access foreign key properties, application code should either be constrained to only normal navigations, or updated to do the appropriate thing for both normal and skip navigations.

<a name="collection-distinct-groupby"></a>

### Some queries with correlated collection that also use `Distinct` or `GroupBy` are no longer supported

[Tracking Issue #15873](https://github.com/dotnet/efcore/issues/15873)
**Old behavior**

Previously, queries involving correlated collections followed by `GroupBy`, as well as some queries using `Distinct` we allowed to execute.

GroupBy example:

```csharp
context.Parents
.Select(p => p.Children
.GroupBy(c => c.School)
.Select(g => g.Key))
```

`Distinct` example - specifically `Distinct` queries where inner collection projection doesn't contain the primary key:

```csharp
context.Parents
.Select(p => p.Children
.Select(c => c.School)
.Distinct())
```

These queries could return incorrect results if the inner collection contained any duplicates, but worked correctly if all the elements in the inner collection were unique.

**New behavior**

These queries are no loger suppored. Exception is thrown indicating that we don't have enough information to correctly build the results.

**Why**

For correlated collection scenarios we need to know entity's primary key in order to assign collection entities to the correct parent. When inner collection doesn't use `GroupBy` or `Distinct`, the missing primary key can simply be added to the projection. However in case of `GroupBy` and `Distinct` it can't be done because it would change the result of `GroupBy` or `Distinct` operation.

**Mitigations**

Rewrite the query to not use `GroupBy` or `Distinct` operations on the inner collection, and perform these operations on the client instead.

```csharp
context.Parents
.Select(p => p.Children.Select(c => c.School))
.ToList()
.Select(x => x.GroupBy(c => c).Select(g => g.Key))
```

```csharp
context.Parents
.Select(p => p.Children.Select(c => c.School))
.ToList()
.Select(x => x.Distinct())
```

<a name="queryable-projection"></a>

### Using a collection of Queryable type in projection is not supported

[Tracking Issue #16314](https://github.com/dotnet/efcore/issues/16314)
**Old behavior**

Previously, it was possible to use collection of a Queryable type inside the projection in some cases, for example as an argument to a `List<T>` constructor:

```csharp
context.Blogs
.Select(b => new List<Post>(context.Posts.Where(p => p.BlogId == b.Id)))
```

**New behavior**

These queries are no loger suppored. Exception is thrown indicating that we can't create an object of Queryable type and suggesting how this could be fixed.

**Why**

We can't materialize an object of a Queryable type, so they would automatically be created using `List<T>` type instead. This would often cause an exception due to type mismatch which was not very clear and could be surprising to some users. We decided to recognize the pattern and throw a more meaningful exception.

**Mitigations**

Add `ToList()` call after the Queryable object in the projection:

```csharp
context.Blogs.Select(b => context.Posts.Where(p => p.BlogId == b.Id).ToList())
```
11 changes: 11 additions & 0 deletions samples/core/Querying/ComplexQuery/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,17 @@ on photo.PersonPhotoId equals person.PhotoId
#endregion
}

using (var context = new BloggingContext())
{
#region JoinComposite
var query = from photo in context.Set<PersonPhoto>()
join person in context.Set<Person>()
on new { Id = (int?)photo.PersonPhotoId, Caption = photo.Caption }
equals new { Id = person.PhotoId, Caption = "SN" }
select new { person, photo };
#endregion
}

using (var context = new BloggingContext())
{
#region GroupJoin
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
modelBuilder.Entity<Blog>().HasQueryFilter(b => b.Url.Contains("fish"));
#endregion
}
else if (setup == "NavigationInFilter")
{
#region NavigationInFilter
modelBuilder.Entity<Blog>().HasMany(b => b.Posts).WithOne(p => p.Blog);
modelBuilder.Entity<Blog>().HasQueryFilter(b => b.Posts.Count > 0);
modelBuilder.Entity<Post>().HasQueryFilter(p => p.Title.Contains("fish"));
#endregion
}
else
{
// The relationship is still required but there is a matching filter configured on dependent side too,
Expand All @@ -44,10 +52,6 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
modelBuilder.Entity<Post>().HasQueryFilter(p => p.Blog.Url.Contains("fish"));
#endregion
}



}
}

}
63 changes: 63 additions & 0 deletions samples/core/Querying/QueryFilters/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ static void Main(string[] args)
QueryFiltersBasicExample();
QueryFiltersWithNavigationsExample();
QueryFiltersWithRequiredNavigationExample();
QueryFiltersUsingNavigationExample();
}

static void QueryFiltersBasicExample()
Expand Down Expand Up @@ -260,5 +261,67 @@ private static void QueryFiltersWithRequiredNavigationExample()
}
}
}

private static void QueryFiltersUsingNavigationExample()
{
using (var db = new FilteredBloggingContextRequired())
{
db.Database.EnsureDeleted();
db.Database.EnsureCreated();

#region SeedDataNavigation
db.Blogs.Add(
new Blog
{
Url = "http://sample.com/blogs/fish",
Posts = new List<Post>
{
new Post { Title = "Fish care 101" },
new Post { Title = "Caring for tropical fish" },
new Post { Title = "Types of ornamental fish" }
}
});

db.Blogs.Add(
new Blog
{
Url = "http://sample.com/blogs/cats",
Posts = new List<Post>
{
new Post { Title = "Cat care 101" },
new Post { Title = "Caring for tropical cats" },
new Post { Title = "Types of ornamental cats" }
}
});

db.Blogs.Add(
new Blog
{
Url = "http://sample.com/blogs/catfish",
Posts = new List<Post>
{
new Post { Title = "Catfish care 101" },
new Post { Title = "History of the catfish name" }
}
});
#endregion

db.SaveChanges();
}

Console.WriteLine("Query filters using navigations demo");
using (var db = new FilteredBloggingContextRequired())
{
#region QueriesNavigation
var filteredBlogs = db.Blogs.ToList();
#endregion
var filteredBlogsInclude = db.Blogs.Include(b => b.Posts).ToList();
if (filteredBlogs.Count == 2 && filteredBlogsInclude.Count == 2)
{
Console.WriteLine("Blogs without any Posts are also filtered out. Posts must contain 'fish' in title.");
Console.WriteLine("Filters are applied recursively, so Blogs that do have Posts, but those Posts don't contain 'fish' in the title will also be filtered out.");
}
}
}
}
}
7 changes: 7 additions & 0 deletions samples/core/Querying/RawSQL/BloggingContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
.HasData(
new Blog { BlogId = 1, Url = @"https://devblogs.microsoft.com/dotnet", Rating = 5 },
new Blog { BlogId = 2, Url = @"https://mytravelblog.com/", Rating = 4 });

modelBuilder.Entity<Post>()
.HasData(
new Post { PostId = 1, BlogId = 1, Title = "What's new", Content = "Lorem ipsum dolor sit amet", Rating = 5 },
new Post { PostId = 2, BlogId = 2, Title = "Around the World in Eighty Days", Content = "consectetur adipiscing elit", Rating = 5 },
new Post { PostId = 3, BlogId = 2, Title = "Glamping *is* the way", Content = "sed do eiusmod tempor incididunt", Rating = 4 },
new Post { PostId = 4, BlogId = 2, Title = "Travel in the time of pandemic", Content = "ut labore et dolore magna aliqua", Rating = 3 });
}

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
Expand Down
Loading

0 comments on commit 286796f

Please sign in to comment.