Expand Up @@ -7,12 +7,54 @@

// ReSharper disable ReturnValueOfPureMethodIsNotUsed
// ReSharper disable StringEndsWithIsCultureSpecific

namespace Microsoft.EntityFrameworkCore.Specification.Tests
{
public abstract class InheritanceTestBase<TFixture> : IClassFixture<TFixture>
where TFixture : InheritanceFixtureBase, new()
{
[Fact]
public virtual void Can_query_when_shared_column()
{
using (var context = CreateContext())
{
var coke = context.Set<Coke>().Single();
Assert.Equal(6, coke.SugarGrams);
Assert.Equal(4, coke.CaffeineGrams);
Assert.Equal(5, coke.Carbination);

var lilt = context.Set<Lilt>().Single();
Assert.Equal(4, lilt.SugarGrams);
Assert.Equal(7, lilt.Carbination);

var tea = context.Set<Tea>().Single();
Assert.True(tea.HasMilk);
Assert.Equal(1, tea.CaffeineGrams);
}
}

[Fact]
public virtual void Can_query_all_types_when_shared_column()
{
using (var context = CreateContext())
{
var drinks = context.Set<Drink>().ToList();
Assert.Equal(3, drinks.Count);

var coke = drinks.OfType<Coke>().Single();
Assert.Equal(6, coke.SugarGrams);
Assert.Equal(4, coke.CaffeineGrams);
Assert.Equal(5, coke.Carbination);

var lilt = drinks.OfType<Lilt>().Single();
Assert.Equal(4, lilt.SugarGrams);
Assert.Equal(7, lilt.Carbination);

var tea = drinks.OfType<Tea>().Single();
Assert.True(tea.HasMilk);
Assert.Equal(1, tea.CaffeineGrams);
}
}

[Fact]
public virtual void Can_use_of_type_animal()
{
Expand Down
Expand Up @@ -131,15 +131,20 @@
<Compile Include="TestModels\InheritanceRelationships\ReferenceOnDerived.cs" />
<Compile Include="TestModels\Inheritance\Animal.cs" />
<Compile Include="TestModels\Inheritance\Bird.cs" />
<Compile Include="TestModels\Inheritance\Coke.cs" />
<Compile Include="TestModels\Inheritance\Country.cs" />
<Compile Include="TestModels\Inheritance\Daisy.cs" />
<Compile Include="TestModels\Inheritance\Drink.cs" />
<Compile Include="TestModels\Inheritance\Eagle.cs" />
<Compile Include="TestModels\Inheritance\Flower.cs" />
<Compile Include="TestModels\Inheritance\InheritanceContext.cs" />
<Compile Include="TestModels\Inheritance\ISugary.cs" />
<Compile Include="TestModels\Inheritance\Kiwi.cs" />
<Compile Include="TestModels\Inheritance\Lilt.cs" />
<Compile Include="TestModels\Inheritance\Plant.cs" />
<Compile Include="TestModels\Inheritance\PlantGenus.cs" />
<Compile Include="TestModels\Inheritance\Rose.cs" />
<Compile Include="TestModels\Inheritance\Tea.cs" />
<Compile Include="TestModels\MonsterContext.cs" />
<Compile Include="TestModels\MonsterContext`.cs" />
<Compile Include="TestModels\MonsterModel.cs" />
Expand Down
Expand Up @@ -9,8 +9,13 @@
using Microsoft.EntityFrameworkCore.Specification.Tests.TestUtilities.Xunit;
using Xunit;

// ReSharper disable InconsistentNaming
// ReSharper disable PossibleMultipleEnumeration
// ReSharper disable ReplaceWithSingleCallToFirstOrDefault
// ReSharper disable ReplaceWithSingleCallToAny
// ReSharper disable ReplaceWithSingleCallToFirst
// ReSharper disable StringStartsWithIsCultureSpecific
// ReSharper disable UseCollectionCountProperty

// ReSharper disable AccessToDisposedClosure
// ReSharper disable PossibleUnintendedReferenceComparison

Expand Down Expand Up @@ -43,8 +48,7 @@ var orders
}
}

// issue 4539
////[ConditionalFact]
[ConditionalFact]
public virtual void Select_Where_Navigation_Scalar_Equals_Navigation_Scalar()
{
using (var context = CreateContext())
Expand All @@ -59,8 +63,7 @@ var orders
}
}

// issue 4539
////[ConditionalFact]
[ConditionalFact]
public virtual void Select_Where_Navigation_Scalar_Equals_Navigation_Scalar_Projected()
{
using (var context = CreateContext())
Expand Down Expand Up @@ -186,8 +189,7 @@ public virtual void Select_Where_Navigation_Null_Deep()
entryCount: 6);
}

// issue 4539
////[ConditionalFact]
[ConditionalFact]
public virtual void Select_Where_Navigation_Equals_Navigation()
{
using (var context = CreateContext())
Expand Down Expand Up @@ -616,11 +618,11 @@ public virtual void Collection_select_nav_prop_first_or_default_then_nav_prop_ne
);
}

// #5427
////[ConditionalFact]
[ConditionalFact]
public virtual void Collection_select_nav_prop_first_or_default_then_nav_prop_nested_with_orderby()
{
AssertQuery<Customer, Order, string>(
// ReSharper disable once StringStartsWithIsCultureSpecific
(cs, os) => cs.Where(e => e.CustomerID.StartsWith("A"))
.Select(c => os.OrderBy(o => o.CustomerID).FirstOrDefault(o =>o.CustomerID == "ALFKI").Customer.City));
}
Expand Down Expand Up @@ -728,13 +730,10 @@ where c.Orders.Select(o => o.OrderID)
entryCount: 1);
}

private int ClientMethod(int argument)
{
return argument;
}
// ReSharper disable once MemberCanBeMadeStatic.Local
private int ClientMethod(int argument) => argument;

// issue #4547
////[ConditionalFact]
[ConditionalFact]
public virtual void Navigation_in_subquery_referencing_outer_query()
{
using (var context = CreateContext())
Expand Down Expand Up @@ -794,15 +793,13 @@ public virtual void Where_nav_prop_group_by()
});
}

// issue #3676
//// [ConditionalFact]
[ConditionalFact]
public virtual void Let_group_by_nav_prop()
{
AssertQuery<OrderDetail, IGrouping<string, OrderDetail>>(
ods => from od in ods
let customer = od.Order.CustomerID
group od by customer
into odg
group od by customer into odg
select odg,
asserter: (l2oItems, efItems) =>
{
Expand Down Expand Up @@ -836,32 +833,25 @@ protected virtual void ClearLog()
bool assertOrder = false,
int entryCount = 0,
Action<IList<object>, IList<object>> asserter = null)
where TItem : class
{
AssertQuery(query, query, assertOrder, entryCount, asserter);
}
where TItem : class
=> AssertQuery(query, query, assertOrder, entryCount, asserter);

protected void AssertQuery<TItem1, TItem2, TResult>(
Func<IQueryable<TItem1>, IQueryable<TItem2>, IQueryable<TResult>> query,
bool assertOrder = false,
int entryCount = 0,
Action<IList<TResult>, IList<TResult>> asserter = null)
where TItem1 : class
where TItem2 : class
{
AssertQuery(query, query, assertOrder, entryCount, asserter);
}

where TItem2 : class
=> AssertQuery(query, query, assertOrder, entryCount, asserter);

protected void AssertQuery<TItem, TResult>(
Func<IQueryable<TItem>, IQueryable<TResult>> query,
bool assertOrder = false,
int entryCount = 0,
Action<IList<TResult>, IList<TResult>> asserter = null)
where TItem : class
{
AssertQuery(query, query, assertOrder, entryCount, asserter);
}
where TItem : class
=> AssertQuery(query, query, assertOrder, entryCount, asserter);

protected void AssertQuery<TItem>(
Func<IQueryable<TItem>, IQueryable<object>> efQuery,
Expand Down
Expand Up @@ -12,6 +12,8 @@
using Microsoft.EntityFrameworkCore.Query.Internal;
using Microsoft.Extensions.DependencyInjection;
using Xunit;
// ReSharper disable InconsistentNaming
// ReSharper disable AccessToDisposedClosure

// ReSharper disable ReplaceWithSingleCallToCount
// ReSharper disable StringStartsWithIsCultureSpecific
Expand Down Expand Up @@ -1712,6 +1714,7 @@ public virtual void Where_ternary_boolean_condition_with_false_as_result()
var flag = new Random().Next(0, 2) == 1;

AssertQuery<Product>(ps => ps
// ReSharper disable once SimplifyConditionalTernaryExpression
.Where(p => flag ? p.UnitsInStock >= 20 : false),
entryCount: flag ? 51 : 0);
}
Expand Down Expand Up @@ -3153,7 +3156,7 @@ public virtual void Let_any_subquery_anonymous()
select new { c, hasOrders });
}

// TODO: Need to figure out how to do this
// TODO: Need to figure out how to do this
// [ConditionalFact]
// public virtual void GroupBy_anonymous()
// {
Expand Down Expand Up @@ -4623,6 +4626,72 @@ public virtual void GroupJoin_DefaultIfEmpty3()
select o);
}


[ConditionalFact]
public virtual void GroupJoin_Where()
{
AssertQuery<Customer, Order>((cs, os) =>
from c in cs
join o in os on c.CustomerID equals o.CustomerID into orders
from o in orders
where o.CustomerID == "ALFKI"
select o);
}

[ConditionalFact]
public virtual void GroupJoin_DefaultIfEmpty_Where_OrderBy()
{
AssertQuery<Customer, Order>((cs, os) =>
from c in cs
join o in os on c.CustomerID equals o.CustomerID into orders
from o in orders
where o.CustomerID == "ALFKI" || c.CustomerID == "ANATR"
orderby c.City
select o);
}

[ConditionalFact]
public virtual void GroupJoin_with_different_outer_elements_with_same_key()
{
AssertQuery<Order, Customer>((os, cs) =>
os.GroupJoin(cs,
o => o.CustomerID,
c => c.CustomerID,
(o, cg) => new
{
o.OrderID,
Name = cg.Select(c => c.ContactName).FirstOrDefault()
}));
}

[ConditionalFact]
public virtual void GroupJoin_with_different_outer_elements_with_same_key_with_predicate()
{
AssertQuery<Order, Customer>((os, cs) =>
os.Where(o => o.OrderID > 11500).GroupJoin(cs,
o => o.CustomerID,
c => c.CustomerID,
(o, cg) => new
{
o.OrderID,
Name = cg.Select(c => c.ContactName).FirstOrDefault()
}));
}

[ConditionalFact]
public virtual void GroupJoin_with_different_outer_elements_with_same_key_projected_from_another_entity()
{
AssertQuery<OrderDetail, Customer>((ods, cs) =>
ods.Select(od => od.Order).GroupJoin(cs,
o => o.CustomerID,
c => c.CustomerID,
(o, cg) => new
{
o.OrderID,
Name = cg.Select(c => c.ContactName).FirstOrDefault()
}));
}

[ConditionalFact]
public virtual void SelectMany_Joined()
{
Expand Down
Expand Up @@ -36,6 +36,8 @@ public Gear()
public string LeaderNickname { get; set; }
public int LeaderSquadId { get; set; }

public bool HasSoulPatch { get; set; }

[NotMapped]
public bool IsMarcus => Nickname == "Marcus";
}
Expand Down
Expand Up @@ -189,6 +189,7 @@ public static void Seed(GearsOfWarContext context)
{
Nickname = "Dom",
FullName = "Dominic Santiago",
HasSoulPatch = false,
SquadId = deltaSquad.Id,
Rank = MilitaryRank.Corporal,
AssignedCity = ephyra,
Expand All @@ -201,6 +202,7 @@ public static void Seed(GearsOfWarContext context)
{
Nickname = "Cole Train",
FullName = "Augustus Cole",
HasSoulPatch = false,
SquadId = deltaSquad.Id,
Rank = MilitaryRank.Private,
CityOrBirthName = hanover.Name,
Expand All @@ -213,6 +215,7 @@ public static void Seed(GearsOfWarContext context)
{
Nickname = "Paduk",
FullName = "Garron Paduk",
HasSoulPatch = false,
SquadId = kiloSquad.Id,
Rank = MilitaryRank.Private,
CityOrBirthName = unknown.Name,
Expand All @@ -224,6 +227,7 @@ public static void Seed(GearsOfWarContext context)
{
Nickname = "Baird",
FullName = "Damon Baird",
HasSoulPatch = true,
SquadId = deltaSquad.Id,
Rank = MilitaryRank.Corporal,
CityOrBirthName = unknown.Name,
Expand All @@ -237,6 +241,7 @@ public static void Seed(GearsOfWarContext context)
{
Nickname = "Marcus",
FullName = "Marcus Fenix",
HasSoulPatch = true,
SquadId = deltaSquad.Id,
Rank = MilitaryRank.Sergeant,
CityOrBirthName = jacinto.Name,
Expand Down
@@ -0,0 +1,12 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

namespace Microsoft.EntityFrameworkCore.Specification.Tests.TestModels.Inheritance
{
public class Coke : Drink, ISugary
{
public int SugarGrams { get; set; }
public int CaffeineGrams { get; set; }
public int Carbination { get; set; }
}
}
@@ -1,12 +1,10 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

namespace Microsoft.EntityFrameworkCore.Microbenchmarks.Core
namespace Microsoft.EntityFrameworkCore.Specification.Tests.TestModels.Inheritance
{
public interface ITestCondition
public class Drink
{
bool IsMet { get; }

string SkipReason { get; }
public int Id { get; set; }
}
}
@@ -0,0 +1,10 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

namespace Microsoft.EntityFrameworkCore.Specification.Tests.TestModels.Inheritance
{
public interface ISugary
{
int SugarGrams { get; set; }
}
}
@@ -0,0 +1,11 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

namespace Microsoft.EntityFrameworkCore.Specification.Tests.TestModels.Inheritance
{
public class Lilt : Drink, ISugary
{
public int SugarGrams { get; set; }
public int Carbination { get; set; }
}
}
@@ -0,0 +1,11 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

namespace Microsoft.EntityFrameworkCore.Specification.Tests.TestModels.Inheritance
{
public class Tea : Drink
{
public bool HasMilk { get; set; }
public int CaffeineGrams { get; set; }
}
}
@@ -1,5 +1,5 @@
{
"version": "1.0.0",
"version": "1.0.1",
"description": "Shared test suite for Entity Framework Core database providers.",
"packOptions": {
"tags": [
Expand All @@ -20,7 +20,7 @@
},
"dependencies": {
"System.Runtime.InteropServices.RuntimeInformation": "4.0.0",
"Microsoft.EntityFrameworkCore.InMemory": "1.0.0",
"Microsoft.EntityFrameworkCore.InMemory": "1.0.1",
"Microsoft.Extensions.RuntimeEnvironment.Sources": {
"version": "1.0.0-rtm-21431",
"type": "build"
Expand Down
Expand Up @@ -228,7 +228,7 @@ FROM sys.extended_properties
AND minor_id = 0
AND class = 1
AND name = N'microsoft_database_tools_support') " +
$"AND t.name <> '{HistoryRepository.DefaultTableName}'" + TemporalTableWhereClause;
$"AND t.name <> '{HistoryRepository.DefaultTableName}'" + TemporalTableWhereClause; // Interpolation okay; strings
using (var reader = command.ExecuteReader())
{
while (reader.Read())
Expand Down
Expand Up @@ -3,6 +3,7 @@

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Internal;
Expand Down Expand Up @@ -174,7 +175,8 @@ private void VisitTypeMapping(PropertyBuilder propertyBuilder, ColumnModel colum
&& dateTimePrecision.Value != DefaultTimeTimePrecision)
{
propertyBuilder.Metadata.SetMaxLength(null);
propertyBuilder.HasColumnType($"{column.DataType}({dateTimePrecision.Value})");
propertyBuilder.HasColumnType(
string.Format(CultureInfo.InvariantCulture, "{0}({1})", column.DataType, dateTimePrecision.Value));
}
else if (!HasTypeAlias(column))
{
Expand Down Expand Up @@ -226,7 +228,7 @@ private static bool HasTypeAlias(ColumnModel column)
return new Tuple<string, int?>(
unqualifiedTypeName
+ "("
+ (maxLength?.ToString() ?? "max")
+ (maxLength?.ToString(CultureInfo.InvariantCulture) ?? "max")
+ ")",
null);
}
Expand Down
@@ -1,5 +1,5 @@
{
"version": "1.0.0",
"version": "1.0.1",
"description": "Design-time Entity Framework Core Functionality for Microsoft SQL Server.",
"packOptions": {
"tags": [
Expand All @@ -20,8 +20,8 @@
}
},
"dependencies": {
"Microsoft.EntityFrameworkCore.SqlServer": "1.0.0",
"Microsoft.EntityFrameworkCore.Relational.Design": "1.0.0"
"Microsoft.EntityFrameworkCore.SqlServer": "1.0.1",
"Microsoft.EntityFrameworkCore.Relational.Design": "1.0.1"
},
"frameworks": {
"net451": {},
Expand Down
Expand Up @@ -9,7 +9,7 @@
[assembly: AssemblyMetadata("Serviceable", "True")]
[assembly: DesignTimeProviderServices(
typeName: "Microsoft.EntityFrameworkCore.Scaffolding.Internal.SqlServerDesignTimeServices",
assemblyName: "Microsoft.EntityFrameworkCore.SqlServer.Design, Version=1.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
assemblyName: "Microsoft.EntityFrameworkCore.SqlServer.Design, Version=1.0.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
packageName: "Microsoft.EntityFrameworkCore.SqlServer.Design")]
[assembly: AssemblyCompany("Microsoft Corporation.")]
[assembly: AssemblyCopyright("© Microsoft Corporation. All rights reserved.")]
Expand Down
Expand Up @@ -74,7 +74,7 @@ public override void EscapeIdentifier(StringBuilder builder, string identifier)
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public override string DelimitIdentifier(string identifier)
=> $"[{EscapeIdentifier(Check.NotEmpty(identifier, nameof(identifier)))}]";
=> $"[{EscapeIdentifier(Check.NotEmpty(identifier, nameof(identifier)))}]"; // Interpolation okay; strings

/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
Expand Down Expand Up @@ -111,7 +111,7 @@ protected override void GenerateLiteralValue(StringBuilder builder, byte[] value
/// </summary>
protected override string GenerateLiteralValue(string value, RelationalTypeMapping typeMapping = null)
=> typeMapping == null || typeMapping.IsUnicode
? $"N'{EscapeLiteral(Check.NotNull(value, nameof(value)))}'"
? $"N'{EscapeLiteral(Check.NotNull(value, nameof(value)))}'" // Interpolation okay; strings
: $"'{EscapeLiteral(Check.NotNull(value, nameof(value)))}'";

/// <summary>
Expand All @@ -130,13 +130,13 @@ protected override void GenerateLiteralValue(StringBuilder builder, string value
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
protected override string GenerateLiteralValue(DateTime value)
=> $"'{value.ToString(DateTimeFormat, CultureInfo.InvariantCulture)}'";
=> $"'{value.ToString(DateTimeFormat, CultureInfo.InvariantCulture)}'"; // Interpolation okay; strings

/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
protected override string GenerateLiteralValue(DateTimeOffset value)
=> $"'{value.ToString(DateTimeOffsetFormat, CultureInfo.InvariantCulture)}'";
=> $"'{value.ToString(DateTimeOffsetFormat, CultureInfo.InvariantCulture)}'"; // Interpolation okay; strings
}
}
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Text;
using JetBrains.Annotations;
Expand Down Expand Up @@ -149,7 +150,7 @@ public class SqlServerUpdateSqlGenerator : UpdateSqlGenerator, ISqlServerUpdateS
AppendValues(
commandStringBuilder,
modificationCommands[i].ColumnModifications.Where(o => o.IsWrite).ToList(),
i.ToString());
i.ToString(CultureInfo.InvariantCulture));
}
commandStringBuilder
.AppendLine(SqlGenerationHelper.StatementTerminator)
Expand Down Expand Up @@ -265,7 +266,7 @@ public override ResultSetMapping AppendUpdateOperation(StringBuilder commandStri
commandStringBuilder
.Append(" USING ")
.Append(toInsertTableName)
.Append(toInsertTableIndex)
.Append(toInsertTableIndex.ToString(CultureInfo.InvariantCulture))
.Append(" AS ").Append(toInsertTableAlias).AppendLine(" ON 1=0")
.AppendLine("WHEN NOT MATCHED THEN");

Expand Down Expand Up @@ -472,6 +473,6 @@ protected override void AppendIdentityWhereCondition(StringBuilder commandString
protected override void AppendRowsAffectedWhereCondition(StringBuilder commandStringBuilder, int expectedRowsAffected)
=> Check.NotNull(commandStringBuilder, nameof(commandStringBuilder))
.Append("@@ROWCOUNT = ")
.Append(expectedRowsAffected);
.Append(expectedRowsAffected.ToString(CultureInfo.InvariantCulture));
}
}
4 changes: 2 additions & 2 deletions src/Microsoft.EntityFrameworkCore.SqlServer/project.json
@@ -1,5 +1,5 @@
{
"version": "1.0.0",
"version": "1.0.1",
"description": "Microsoft SQL Server database provider for Entity Framework Core.",
"packOptions": {
"tags": [
Expand All @@ -26,7 +26,7 @@
}
},
"dependencies": {
"Microsoft.EntityFrameworkCore.Relational": "1.0.0"
"Microsoft.EntityFrameworkCore.Relational": "1.0.1"
},
"frameworks": {
"net451": {},
Expand Down
Expand Up @@ -101,7 +101,7 @@ private void GetTables()
"SELECT name FROM sqlite_master" +
" WHERE type = 'table'" +
" AND name <> 'sqlite_sequence'" +
$" AND name <> '{HistoryRepository.DefaultTableName}'";
$" AND name <> '{HistoryRepository.DefaultTableName}'"; // Interpolation okay; strings

using (var reader = command.ExecuteReader())
{
Expand Down Expand Up @@ -137,7 +137,7 @@ private void GetColumns()
{
using (var command = _connection.CreateCommand())
{
command.CommandText = $"PRAGMA table_info(\"{table.Name.Replace("\"", "\"\"")}\");";
command.CommandText = $"PRAGMA table_info(\"{table.Name.Replace("\"", "\"\"")}\");"; // Interpolation okay; strings

using (var reader = command.ExecuteReader())
{
Expand Down Expand Up @@ -180,7 +180,7 @@ private void GetIndexes()
{
using (var indexInfo = _connection.CreateCommand())
{
indexInfo.CommandText = $"PRAGMA index_list(\"{table.Name.Replace("\"", "\"\"")}\");";
indexInfo.CommandText = $"PRAGMA index_list(\"{table.Name.Replace("\"", "\"\"")}\");"; // Interpolation okay; strings

using (var reader = indexInfo.ExecuteReader())
{
Expand All @@ -203,7 +203,7 @@ private void GetIndexes()
foreach (var index in table.Indexes)
{
var indexColumns = _connection.CreateCommand();
indexColumns.CommandText = $"PRAGMA index_info(\"{index.Name.Replace("\"", "\"\"")}\");";
indexColumns.CommandText = $"PRAGMA index_info(\"{index.Name.Replace("\"", "\"\"")}\");"; // Interpolation okay; strings

index.IndexColumns = new List<IndexColumnModel>();
using (var reader = indexColumns.ExecuteReader())
Expand Down Expand Up @@ -245,7 +245,7 @@ private void GetForeignKeys()
{
using (var fkList = _connection.CreateCommand())
{
fkList.CommandText = $"PRAGMA foreign_key_list(\"{dependentTable.Name.Replace("\"", "\"\"")}\");";
fkList.CommandText = $"PRAGMA foreign_key_list(\"{dependentTable.Name.Replace("\"", "\"\"")}\");"; // Interpolation okay; strings

var tableForeignKeys = new Dictionary<int, ForeignKeyModel>();

Expand Down
6 changes: 3 additions & 3 deletions src/Microsoft.EntityFrameworkCore.Sqlite.Design/project.json
@@ -1,5 +1,5 @@
{
"version": "1.0.0",
"version": "1.0.1",
"description": "Design-time Entity Framework Core functionality for SQLite",
"packOptions": {
"tags": [
Expand All @@ -20,8 +20,8 @@
}
},
"dependencies": {
"Microsoft.EntityFrameworkCore.Relational.Design": "1.0.0",
"Microsoft.EntityFrameworkCore.Sqlite": "1.0.0"
"Microsoft.EntityFrameworkCore.Relational.Design": "1.0.1",
"Microsoft.EntityFrameworkCore.Sqlite": "1.0.1"
},
"frameworks": {
"net451": {},
Expand Down
Expand Up @@ -9,7 +9,7 @@
[assembly: AssemblyMetadata("Serviceable", "True")]
[assembly: DesignTimeProviderServices(
typeName: "Microsoft.EntityFrameworkCore.Scaffolding.Internal.SqliteDesignTimeServices",
assemblyName: "Microsoft.EntityFrameworkCore.Sqlite.Design, Version=1.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
assemblyName: "Microsoft.EntityFrameworkCore.Sqlite.Design, Version=1.0.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
packageName: "Microsoft.EntityFrameworkCore.Sqlite.Design")]
[assembly: AssemblyCompany("Microsoft Corporation.")]
[assembly: AssemblyCopyright("© Microsoft Corporation. All rights reserved.")]
Expand Down
Expand Up @@ -62,13 +62,27 @@ public override void DelimitIdentifier(StringBuilder builder, string name, strin
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
protected override string GenerateLiteralValue(DateTime value)
=> $"'{value.ToString(DateTimeFormat, CultureInfo.InvariantCulture)}'";
=> $"'{value.ToString(DateTimeFormat, CultureInfo.InvariantCulture)}'"; // Interpolation okay; strings

/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
protected override string GenerateLiteralValue(DateTimeOffset value)
=> $"'{value.ToString(DateTimeOffsetFormat, CultureInfo.InvariantCulture)}'";
=> $"'{value.ToString(DateTimeOffsetFormat, CultureInfo.InvariantCulture)}'"; // Interpolation okay; strings

/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
protected override string GenerateLiteralValue(Guid value)
=> GenerateLiteralValue(value.ToByteArray());

/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
protected override void GenerateLiteralValue(StringBuilder builder, Guid value)
=> GenerateLiteralValue(builder, value.ToByteArray());
}
}
4 changes: 2 additions & 2 deletions src/Microsoft.EntityFrameworkCore.Sqlite/project.json
@@ -1,5 +1,5 @@
{
"version": "1.0.0",
"version": "1.0.1",
"description": "SQLite database provider for Entity Framework Core.",
"packOptions": {
"tags": [
Expand All @@ -26,7 +26,7 @@
}
},
"dependencies": {
"Microsoft.EntityFrameworkCore.Relational": "1.0.0",
"Microsoft.EntityFrameworkCore.Relational": "1.0.1",
"Microsoft.Data.Sqlite": "1.0.0"
},
"frameworks": {
Expand Down
Expand Up @@ -26,7 +26,7 @@ public class StateManager : IStateManager

private readonly LazyRef<IDictionary<object, IList<Tuple<INavigation, InternalEntityEntry>>>> _referencedUntrackedEntities
= new LazyRef<IDictionary<object, IList<Tuple<INavigation, InternalEntityEntry>>>>(
() => new Dictionary<object, IList<Tuple<INavigation, InternalEntityEntry>>>());
() => new Dictionary<object, IList<Tuple<INavigation, InternalEntityEntry>>>(ReferenceEqualityComparer.Instance));

private IIdentityMap _identityMap0;
private IIdentityMap _identityMap1;
Expand Down
24 changes: 16 additions & 8 deletions src/Microsoft.EntityFrameworkCore/Infrastructure/DatabaseFacade.cs
Expand Up @@ -18,6 +18,8 @@ namespace Microsoft.EntityFrameworkCore.Infrastructure
public class DatabaseFacade : IInfrastructure<IServiceProvider>
{
private readonly DbContext _context;
private IDatabaseCreator _databaseCreator;
private IDbContextTransactionManager _transactionManager;

/// <summary>
/// Initializes a new instance of the <see cref="DatabaseFacade" /> class. Instances of this class are typically
Expand Down Expand Up @@ -46,7 +48,7 @@ public DatabaseFacade([NotNull] DbContext context)
/// </para>
/// </summary>
/// <returns> True if the database is created, false if it already existed. </returns>
public virtual bool EnsureCreated() => this.GetService<IDatabaseCreator>().EnsureCreated();
public virtual bool EnsureCreated() => DatabaseCreator.EnsureCreated();

/// <summary>
/// <para>
Expand All @@ -67,7 +69,7 @@ public DatabaseFacade([NotNull] DbContext context)
/// false if it already existed.
/// </returns>
public virtual Task<bool> EnsureCreatedAsync(CancellationToken cancellationToken = default(CancellationToken))
=> this.GetService<IDatabaseCreator>().EnsureCreatedAsync(cancellationToken);
=> DatabaseCreator.EnsureCreatedAsync(cancellationToken);

/// <summary>
/// <para>
Expand All @@ -80,7 +82,7 @@ public virtual Task<bool> EnsureCreatedAsync(CancellationToken cancellationToken
/// </para>
/// </summary>
/// <returns> True if the database is deleted, false if it did not exist. </returns>
public virtual bool EnsureDeleted() => this.GetService<IDatabaseCreator>().EnsureDeleted();
public virtual bool EnsureDeleted() => DatabaseCreator.EnsureDeleted();

/// <summary>
/// <para>
Expand All @@ -98,7 +100,7 @@ public virtual Task<bool> EnsureCreatedAsync(CancellationToken cancellationToken
/// false if it did not exist.
/// </returns>
public virtual Task<bool> EnsureDeletedAsync(CancellationToken cancellationToken = default(CancellationToken))
=> this.GetService<IDatabaseCreator>().EnsureDeletedAsync(cancellationToken);
=> DatabaseCreator.EnsureDeletedAsync(cancellationToken);

/// <summary>
/// Starts a new transaction.
Expand All @@ -107,7 +109,7 @@ public virtual Task<bool> EnsureDeletedAsync(CancellationToken cancellationToken
/// A <see cref="IDbContextTransaction" /> that represents the started transaction.
/// </returns>
public virtual IDbContextTransaction BeginTransaction()
=> this.GetService<IDbContextTransactionManager>().BeginTransaction();
=> TransactionManager.BeginTransaction();

/// <summary>
/// Asynchronously starts a new transaction.
Expand All @@ -118,19 +120,19 @@ public virtual IDbContextTransaction BeginTransaction()
/// that represents the started transaction.
/// </returns>
public virtual Task<IDbContextTransaction> BeginTransactionAsync(CancellationToken cancellationToken = default(CancellationToken))
=> this.GetService<IDbContextTransactionManager>().BeginTransactionAsync(cancellationToken);
=> TransactionManager.BeginTransactionAsync(cancellationToken);

/// <summary>
/// Applies the outstanding operations in the current transaction to the database.
/// </summary>
public virtual void CommitTransaction()
=> this.GetService<IDbContextTransactionManager>().CommitTransaction();
=> TransactionManager.CommitTransaction();

/// <summary>
/// Discards the outstanding operations in the current transaction.
/// </summary>
public virtual void RollbackTransaction()
=> this.GetService<IDbContextTransactionManager>().RollbackTransaction();
=> TransactionManager.RollbackTransaction();

/// <summary>
/// <para>
Expand All @@ -142,5 +144,11 @@ public virtual void RollbackTransaction()
/// </para>
/// </summary>
IServiceProvider IInfrastructure<IServiceProvider>.Instance => ((IInfrastructure<IServiceProvider>)_context).Instance;

private IDbContextTransactionManager TransactionManager
=> _transactionManager ?? (_transactionManager = this.GetService<IDbContextTransactionManager>());

private IDatabaseCreator DatabaseCreator
=> _databaseCreator ?? (_databaseCreator = this.GetService<IDatabaseCreator>());
}
}
34 changes: 34 additions & 0 deletions src/Microsoft.EntityFrameworkCore/Internal/CoreStrings.cs
@@ -0,0 +1,34 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using JetBrains.Annotations;

namespace Microsoft.EntityFrameworkCore.Internal
{
public partial class CoreStrings
{
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[Obsolete("This overload is obsolete and will be removed in the 1.1.0 release.", error: true)]
public static string ReferencedShadowKey(
[CanBeNull] object key,
[CanBeNull] object referencingEntityTypeWithNavigation,
[CanBeNull] object referencedEntityTypeWithNaviagation)
=> string.Empty;

/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[Obsolete("This method is obsolete and will be removed in the 1.1.0 release.", error: true)]
public static string ReferencedShadowKeyWithoutNavigations(
[CanBeNull] object key,
[CanBeNull] object entityType,
[CanBeNull] object foreignKey,
[CanBeNull] object referencingEntityType)
=> string.Empty;
}
}
Expand Up @@ -24,6 +24,8 @@ public class DbContextServices : IDbContextServices
private LazyRef<IModel> _modelFromSource;
private LazyRef<IDatabaseProviderServices> _providerServices;
private bool _inOnModelCreating;
private ILoggerFactory _loggerFactory;
private IMemoryCache _memoryCache;

/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
Expand Down Expand Up @@ -85,14 +87,14 @@ private IModel CreateModel()
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public virtual ILoggerFactory LoggerFactory
=> CoreOptions?.LoggerFactory ?? _scopedProvider?.GetRequiredService<ILoggerFactory>();
=> _loggerFactory ?? (_loggerFactory = CoreOptions?.LoggerFactory ?? _scopedProvider?.GetRequiredService<ILoggerFactory>());

/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public virtual IMemoryCache MemoryCache
=> CoreOptions?.MemoryCache ?? _scopedProvider?.GetRequiredService<IMemoryCache>();
=> _memoryCache ?? (_memoryCache = CoreOptions?.MemoryCache ?? _scopedProvider?.GetRequiredService<IMemoryCache>());

private CoreOptionsExtension CoreOptions
=> _contextOptions?.FindExtension<CoreOptionsExtension>();
Expand Down
41 changes: 32 additions & 9 deletions src/Microsoft.EntityFrameworkCore/Internal/ModelValidator.cs
Expand Up @@ -27,8 +27,8 @@ public abstract class ModelValidator : IModelValidator
public virtual void Validate(IModel model)
{
EnsureNoShadowEntities(model);
EnsureNoShadowKeys(model);
EnsureNonNullPrimaryKeys(model);
EnsureNoShadowKeys(model);
EnsureClrInheritance(model);
EnsureChangeTrackingStrategy(model);
}
Expand All @@ -52,15 +52,38 @@ protected virtual void EnsureNoShadowEntities([NotNull] IModel model)
/// </summary>
protected virtual void EnsureNoShadowKeys([NotNull] IModel model)
{
var messages = KeyConvention.GetShadowKeyExceptionMessage(model, key => key.Properties.Any(p => p.IsShadowProperty));
if (messages == null)
{
return;
}

foreach (var message in messages)
foreach (var entityType in model.GetEntityTypes().Where(t => t.ClrType != null))
{
ShowWarning(message);
foreach (var key in entityType.GetDeclaredKeys())
{
if (key.Properties.Any(p => p.IsShadowProperty))
{
var referencingFk = key.GetReferencingForeignKeys().FirstOrDefault();
var conventionalKey = key as Key;
if (referencingFk != null
&& conventionalKey != null
&& ConfigurationSource.Convention.Overrides(conventionalKey.GetConfigurationSource()))
{
ShowError(CoreStrings.ReferencedShadowKey(
referencingFk.DeclaringEntityType.DisplayName() +
(referencingFk.DependentToPrincipal == null
? ""
: "." + referencingFk.DependentToPrincipal.Name),
entityType.DisplayName() +
(referencingFk.PrincipalToDependent == null
? ""
: "." + referencingFk.PrincipalToDependent.Name),
Property.Format(referencingFk.Properties, includeTypes: true),
Property.Format(entityType.FindPrimaryKey().Properties, includeTypes: true)));
continue;
}

ShowWarning(CoreStrings.ShadowKey(
Property.Format(key.Properties),
entityType.DisplayName(),
Property.Format(key.Properties)));
}
}
}
}

Expand Down
5 changes: 3 additions & 2 deletions src/Microsoft.EntityFrameworkCore/Internal/TypeExtensions.cs
Expand Up @@ -3,6 +3,7 @@

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using System.Text;
using JetBrains.Annotations;
Expand Down Expand Up @@ -105,7 +106,7 @@ private static void ProcessNestedGenericTypes(Type t, StringBuilder sb, bool ful
}

var name = part.Substring(0, num);
var numberOfGenericTypeArgs = int.Parse(part.Substring(num + 1));
var numberOfGenericTypeArgs = int.Parse(part.Substring(num + 1), CultureInfo.InvariantCulture);
sb.Append(fullName ? name : genericSimpleName.Substring(0, genericSimpleName.IndexOf('`')));
AppendGenericArguments(genericArguments, index, numberOfGenericTypeArgs, sb, fullName);
return;
Expand All @@ -117,7 +118,7 @@ private static void ProcessNestedGenericTypes(Type t, StringBuilder sb, bool ful
if (num != -1)
{
var name = part.Substring(0, num);
var numberOfGenericTypeArgs = int.Parse(part.Substring(num + 1));
var numberOfGenericTypeArgs = int.Parse(part.Substring(num + 1), CultureInfo.InvariantCulture);
if (fullName || (i == totalParts - 1))
{
sb.Append(name);
Expand Down
Expand Up @@ -63,7 +63,6 @@ public virtual ConventionSet CreateConventionSet()

conventionSet.ModelBuiltConventions.Add(new ModelCleanupConvention());
conventionSet.ModelBuiltConventions.Add(keyAttributeConvention);
conventionSet.ModelBuiltConventions.Add(keyConvention);
conventionSet.ModelBuiltConventions.Add(new PropertyMappingValidationConvention());
conventionSet.ModelBuiltConventions.Add(new RelationshipValidationConvention());

Expand Down
Expand Up @@ -30,7 +30,7 @@ public override InternalPropertyBuilder Apply(InternalPropertyBuilder propertyBu
var entityType = propertyBuilder.Metadata.DeclaringEntityType;
if (entityType.BaseType != null)
{
throw new InvalidOperationException(CoreStrings.KeyAttributeOnDerivedEntity(entityType.DisplayName(), propertyBuilder.Metadata.Name));
return propertyBuilder;
}

var entityTypeBuilder = entityType.Builder;
Expand Down Expand Up @@ -64,20 +64,33 @@ public override InternalPropertyBuilder Apply(InternalPropertyBuilder propertyBu
public virtual InternalModelBuilder Apply(InternalModelBuilder modelBuilder)
{
var entityTypes = modelBuilder.Metadata.GetEntityTypes();
foreach (var entityType in entityTypes.Where(et => et.BaseType == null))
foreach (var entityType in entityTypes)
{
var currentPrimaryKey = entityType.FindPrimaryKey();
if ((currentPrimaryKey != null)
&& (currentPrimaryKey.Properties.Count > 1))
if (entityType.BaseType == null)
{
var newKey = entityType.Builder.PrimaryKey(
new List<string> { currentPrimaryKey.Properties.First().Name }, ConfigurationSource.DataAnnotation);
if (newKey != null)
var currentPrimaryKey = entityType.FindPrimaryKey();
if (currentPrimaryKey != null
&& currentPrimaryKey.Properties.Count > 1
&& entityType.GetPrimaryKeyConfigurationSource() == ConfigurationSource.DataAnnotation)
{
throw new InvalidOperationException(CoreStrings.CompositePKWithDataAnnotation(entityType.DisplayName()));
}
}
else
{
foreach (var declaredProperty in entityType.GetDeclaredProperties())
{
var propertyInfo = declaredProperty.PropertyInfo;
var attributes = propertyInfo?.GetCustomAttributes<KeyAttribute>(true);
if (attributes?.Any() == true)
{
throw new InvalidOperationException(
CoreStrings.KeyAttributeOnDerivedEntity(entityType.DisplayName(), declaredProperty.Name));
}
}
}
}

return modelBuilder;
}
}
Expand Down
Expand Up @@ -5,21 +5,20 @@
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Utilities;

namespace Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal
{
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public class KeyConvention
: IKeyConvention, IPrimaryKeyConvention, IForeignKeyConvention, IForeignKeyRemovedConvention, IModelConvention
{
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public virtual InternalKeyBuilder Apply(InternalKeyBuilder keyBuilder)
Expand All @@ -30,7 +29,7 @@ public virtual InternalKeyBuilder Apply(InternalKeyBuilder keyBuilder)
}

/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public virtual InternalRelationshipBuilder Apply(InternalRelationshipBuilder relationshipBuilder)
Expand All @@ -46,7 +45,7 @@ public virtual InternalRelationshipBuilder Apply(InternalRelationshipBuilder rel
}

/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public virtual void Apply(InternalEntityTypeBuilder entityTypeBuilder, ForeignKey foreignKey)
Expand All @@ -57,7 +56,7 @@ public virtual void Apply(InternalEntityTypeBuilder entityTypeBuilder, ForeignKe
}

/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public virtual bool Apply(InternalKeyBuilder keyBuilder, Key previousPrimaryKey)
Expand Down Expand Up @@ -88,7 +87,7 @@ private static void SetValueGeneration(IEnumerable<Property> properties)
}

/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public virtual Property FindValueGeneratedOnAddProperty(
Expand Down Expand Up @@ -126,79 +125,19 @@ private void SetIdentity(IReadOnlyList<Property> properties, EntityType entityTy
}

/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[Obsolete("This method is obsolete and will be removed in the 1.1.0 release.", error: true)]
public virtual InternalModelBuilder Apply(InternalModelBuilder modelBuilder)
{
var messages = GetShadowKeyExceptionMessage(
modelBuilder.Metadata,
key => key.Properties.Any(p => p.IsShadowProperty
&& ConfigurationSource.Convention.Overrides(((Property)p).GetConfigurationSource())));
if (messages != null
&& messages.Any())
{
throw new InvalidOperationException(messages.First());
}

return modelBuilder;
}
=> modelBuilder;

/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[Obsolete("This method is obsolete and will be removed in the 1.1.0 release.", error: true)]
public static List<string> GetShadowKeyExceptionMessage([NotNull] IModel model, [NotNull] Func<IKey, bool> keyPredicate)
{
List<string> messages = null;
foreach (var entityType in model.GetEntityTypes())
{
foreach (var key in entityType.GetDeclaredKeys())
{
if (keyPredicate(key))
{
string message;
var referencingFk = key.GetReferencingForeignKeys().FirstOrDefault();
if (referencingFk != null)
{
if (referencingFk.DependentToPrincipal == null
&& referencingFk.PrincipalToDependent == null)
{
message = CoreStrings.ReferencedShadowKeyWithoutNavigations(
Property.Format(key.Properties),
entityType.DisplayName(),
Property.Format(referencingFk.Properties),
referencingFk.DeclaringEntityType.DisplayName());
}
else
{
message = CoreStrings.ReferencedShadowKey(
Property.Format(key.Properties),
entityType.DisplayName() +
(referencingFk.PrincipalToDependent == null
? ""
: "." + referencingFk.PrincipalToDependent.Name),
referencingFk.DeclaringEntityType.DisplayName() +
(referencingFk.DependentToPrincipal == null
? ""
: "." + referencingFk.DependentToPrincipal.Name));
}
}
else
{
message = CoreStrings.ShadowKey(
Property.Format(key.Properties),
entityType.DisplayName(),
Property.Format(key.Properties));
}

messages = messages ?? new List<string>();
messages.Add(message);
}
}
}

return messages;
}
=> new List<string>(0);
}
}
Expand Up @@ -25,10 +25,7 @@ public virtual InternalPropertyBuilder Apply(InternalPropertyBuilder propertyBui
{
Check.NotNull(propertyBuilder, nameof(propertyBuilder));

var clrType = propertyBuilder.Metadata.DeclaringEntityType.ClrType;
var propertyName = propertyBuilder.Metadata.Name;

var propertyInfo = clrType?.GetRuntimeProperties().FirstOrDefault(pi => pi.Name == propertyName);
var propertyInfo = propertyBuilder.Metadata.PropertyInfo;
var attributes = propertyInfo?.GetCustomAttributes<TAttribute>(true);
if (attributes != null)
{
Expand Down
Expand Up @@ -1001,7 +1001,7 @@ private ForeignKey RemoveForeignKey([NotNull] ForeignKey foreignKey, bool runCon
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public virtual IEnumerable<ForeignKey> GetReferencingForeignKeys()
=> _baseType?.GetDeclaredReferencingForeignKeys().Concat(GetDeclaredReferencingForeignKeys())
=> _baseType?.GetReferencingForeignKeys().Concat(GetDeclaredReferencingForeignKeys())
?? GetDeclaredReferencingForeignKeys();

/// <summary>
Expand Down
Expand Up @@ -544,6 +544,7 @@ public virtual EntityType ResolveEntityTypeInHierarchy([NotNull] EntityType enti
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public override string ToString()
// Interpolation okay; strings/debug output
=> $"'{DeclaringEntityType.DisplayName()}' {Property.Format(Properties)} -> '{PrincipalEntityType.DisplayName()}' {Property.Format(PrincipalKey.Properties)}";

/// <summary>
Expand Down
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Reflection;
using JetBrains.Annotations;
Expand Down Expand Up @@ -968,16 +969,20 @@ private void RemovePropertyIfUnused(Property property)
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public virtual InternalIndexBuilder HasIndex([NotNull] IReadOnlyList<string> propertyNames, ConfigurationSource configurationSource)
=> HasIndex(GetOrCreateProperties(propertyNames, configurationSource), null, configurationSource);
=> HasIndex(GetOrCreateProperties(propertyNames, configurationSource), configurationSource);

/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public virtual InternalIndexBuilder HasIndex([NotNull] IReadOnlyList<PropertyInfo> clrProperties, ConfigurationSource configurationSource)
=> HasIndex(GetOrCreateProperties(clrProperties, configurationSource), null, configurationSource);
=> HasIndex(GetOrCreateProperties(clrProperties, configurationSource), configurationSource);

private InternalIndexBuilder HasIndex(IReadOnlyList<Property> properties, bool? unique, ConfigurationSource configurationSource)
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public virtual InternalIndexBuilder HasIndex([CanBeNull] IReadOnlyList<Property> properties, ConfigurationSource configurationSource)
{
if (properties == null)
{
Expand All @@ -993,18 +998,18 @@ private InternalIndexBuilder HasIndex(IReadOnlyList<Property> properties, bool?
}
else if (existingIndex.DeclaringEntityType != Metadata)
{
return existingIndex.DeclaringEntityType.Builder.HasIndex(existingIndex, properties, unique, configurationSource);
return existingIndex.DeclaringEntityType.Builder.HasIndex(existingIndex, properties, configurationSource);
}

var indexBuilder = HasIndex(existingIndex, properties, unique, configurationSource);
var indexBuilder = HasIndex(existingIndex, properties, configurationSource);

detachedIndexes?.Attach();

return indexBuilder;
}

private InternalIndexBuilder HasIndex(
Index index, IReadOnlyList<Property> properties, bool? unique, ConfigurationSource configurationSource)
Index index, IReadOnlyList<Property> properties, ConfigurationSource configurationSource)
{
if (index == null)
{
Expand All @@ -1015,12 +1020,7 @@ private InternalIndexBuilder HasIndex(IReadOnlyList<Property> properties, bool?
index.UpdateConfigurationSource(configurationSource);
}

if (unique.HasValue)
{
index.SetIsUnique(unique.Value, configurationSource);
}

return index.Builder;
return index?.Builder;
}

/// <summary>
Expand Down Expand Up @@ -1233,7 +1233,8 @@ private static IndexBuildersSnapshot DetachIndexes(IEnumerable<Index> indexesToD
foreignKey.UpdateConfigurationSource(configurationSource);
principalType.UpdateConfigurationSource(configurationSource);

HasIndex(dependentProperties, foreignKey.IsUnique, ConfigurationSource.Convention);
HasIndex(dependentProperties, ConfigurationSource.Convention)
?.IsUnique(foreignKey.IsUnique, ConfigurationSource.Convention);

var value = foreignKey.Builder;
if (runConventions)
Expand Down Expand Up @@ -1574,7 +1575,7 @@ private static Property CreateUniqueProperty(string baseName, Type propertyType,
var index = -1;
while (true)
{
var name = baseName + (++index > 0 ? index.ToString() : "");
var name = baseName + (++index > 0 ? index.ToString(CultureInfo.InvariantCulture) : "");
var entityType = entityTypeBuilder.Metadata;
if (entityType.FindPropertiesInHierarchy(name).Any()
|| (entityType.ClrType?.GetRuntimeProperties().FirstOrDefault(p => p.Name == name) != null))
Expand Down
Expand Up @@ -2257,6 +2257,14 @@ private bool CanInvert(ConfigurationSource? configurationSource)
temporaryProperty.UpdateConfigurationSource(ConfigurationSource.DataAnnotation);
}

var temporaryKeyProperties = principalProperties?.Where(p => p.GetConfigurationSource() == ConfigurationSource.Convention
&& p.IsShadowProperty).ToList();
var keyTempIndex = temporaryKeyProperties != null
&& temporaryKeyProperties.Any()
&& principalEntityType.FindIndex(temporaryKeyProperties) == null
? principalEntityType.Builder.HasIndex(temporaryKeyProperties, ConfigurationSource.Convention).Metadata
: null;

if (Metadata.Builder != null)
{
RemoveForeignKey(Metadata, removedNavigations, removedForeignKeys);
Expand Down Expand Up @@ -2420,6 +2428,11 @@ private bool CanInvert(ConfigurationSource? configurationSource)
}
}

if (keyTempIndex?.Builder != null)
{
keyTempIndex.DeclaringEntityType.Builder.RemoveIndex(keyTempIndex, ConfigurationSource.Convention);
}

return newRelationshipBuilder;
}

Expand Down
Expand Up @@ -454,8 +454,11 @@ private void SetFlag(bool value, PropertyFlags flag)
}
}

internal static string Format(IEnumerable<IProperty> properties)
=> "{" + string.Join(", ", properties.Select(p => "'" + p.Name + "'")) + "}";
internal static string Format(IEnumerable<IProperty> properties, bool includeTypes = false)
=> "{"
+ string.Join(", ",
properties.Select(p => "'" + p.Name + "'" + (includeTypes ? " : " + p.ClrType.DisplayName(fullName: false) : "")))
+ "}";

IEntityType IPropertyBase.DeclaringEntityType => DeclaringEntityType;
IMutableEntityType IMutableProperty.DeclaringEntityType => DeclaringEntityType;
Expand Down
Expand Up @@ -131,6 +131,7 @@
<Compile Include="ChangeTracking\ObservableHashSet.cs" />
<Compile Include="ChangeTracking\PropertyEntry.cs" />
<Compile Include="ChangeTracking\PropertyEntry`.cs" />
<Compile Include="Internal\CoreStrings.cs" />
<Compile Include="DbContext.cs" />
<Compile Include="DbContextOptions.cs" />
<Compile Include="DbContextOptionsBuilder.cs" />
Expand Down
4 changes: 2 additions & 2 deletions src/Microsoft.EntityFrameworkCore/Properties/AssemblyInfo.cs
@@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Reflection;
Expand All @@ -9,7 +9,7 @@

#if CSPROJ

[assembly: AssemblyInformationalVersion("1.0.0-csproj")]
[assembly: AssemblyInformationalVersion("1.0.1-csproj")]

#endif

Expand Down
7 changes: 2 additions & 5 deletions src/Microsoft.EntityFrameworkCore/Properties/CoreStrings.resx
Expand Up @@ -148,7 +148,7 @@
<value>The collection argument '{argumentName}' must contain at least one element.</value>
</data>
<data name="EntityRequiresKey" xml:space="preserve">
<value>The entity type '{entityType}' requires a key to be defined.</value>
<value>The entity type '{entityType}' requires a primary key to be defined.</value>
</data>
<data name="KeyPropertiesWrongEntity" xml:space="preserve">
<value>The specified key properties {key} are not declared on the entity type '{entityType}'. Ensure key properties are declared on the target entity type.</value>
Expand Down Expand Up @@ -349,10 +349,7 @@
<value>The entity type '{type}' provided for the argument '{argumentName}' must be a reference type.</value>
</data>
<data name="ReferencedShadowKey" xml:space="preserve">
<value>The key {key} contains properties in shadow state and is referenced by a relationship from '{referencingEntityTypeWithNavigation}' to '{referencedEntityTypeWithNavigation}'. Configure a non-shadow principal key for this relationship.</value>
</data>
<data name="ReferencedShadowKeyWithoutNavigations" xml:space="preserve">
<value>The key {key} on entity type '{entityType}' contains properties in shadow state and is referenced by the foreign key {foreignKey} from entity type '{referencingEntityType}'. Configure a non-shadow principal key for this relationship.</value>
<value>The relationship from '{referencingEntityTypeOrNavigation}' to '{referencedEntityTypeOrNavigation}' with foreign key properties {foreignKeyPropertiesWithTypes} cannot target the primary key {primaryKeyPropertiesWithTypes} because it is not compatible. Configure a principal key or a set of compatible foreign key properties for this relationship.</value>
</data>
<data name="ShadowKey" xml:space="preserve">
<value>The key {key} on entity type '{entityType}' contains properties in shadow state - {shadowProperties}.</value>
Expand Down
23 changes: 16 additions & 7 deletions src/Microsoft.EntityFrameworkCore/Query/EntityQueryModelVisitor.cs
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
Expand Down Expand Up @@ -547,9 +548,15 @@ var lastTrackingModifier
return;
}

var groupResultOperator
= queryModel.ResultOperators.OfType<GroupResultOperator>().LastOrDefault();

var outputExpression
= groupResultOperator?.ElementSelector ?? queryModel.SelectClause.Selector;

var entityTrackingInfos
= _entityResultFindingExpressionVisitorFactory.Create(QueryCompilationContext)
.FindEntitiesInResult(queryModel.SelectClause.Selector);
.FindEntitiesInResult(outputExpression);

if (entityTrackingInfos.Any())
{
Expand All @@ -561,21 +568,21 @@ var entityTrackingInfos
if (resultItemTypeInfo.IsGenericType
&& (resultItemTypeInfo.GetGenericTypeDefinition() == typeof(IGrouping<,>)
|| resultItemTypeInfo.GetGenericTypeDefinition() == typeof(IAsyncGrouping<,>)))

{
trackingMethod
= LinqOperatorProvider.TrackGroupedEntities
.MakeGenericMethod(
resultItemType.GenericTypeArguments[0],
resultItemType.GenericTypeArguments[1],
queryModel.SelectClause.Selector.Type);
resultItemType.GenericTypeArguments[1]);
}
else
{
trackingMethod
= LinqOperatorProvider.TrackEntities
.MakeGenericMethod(
resultItemType,
queryModel.SelectClause.Selector.Type);
outputExpression.Type);
}

_expression
Expand All @@ -586,13 +593,13 @@ var entityTrackingInfos
Expression.Constant(entityTrackingInfos),
Expression.Constant(
_getEntityAccessors
.MakeGenericMethod(queryModel.SelectClause.Selector.Type)
.MakeGenericMethod(outputExpression.Type)
.Invoke(
null,
new object[]
{
entityTrackingInfos,
queryModel.SelectClause.Selector
outputExpression
})));
}
}
Expand Down Expand Up @@ -1076,7 +1083,9 @@ var createTransparentIdentifierMethodInfo
IQuerySource fromClause, QueryModel queryModel, int index, Type transparentIdentifierType)
{
CurrentParameter
= Expression.Parameter(transparentIdentifierType, $"t{_transparentParameterCounter++}");
= Expression.Parameter(
transparentIdentifierType,
string.Format(CultureInfo.InvariantCulture, "t{0}", _transparentParameterCounter++));

var outerAccessExpression
= AccessOuterTransparentField(transparentIdentifierType, CurrentParameter);
Expand Down

Large diffs are not rendered by default.

Expand Up @@ -33,7 +33,7 @@ public override Expression Visit(Expression expression)
}
}

return base.Visit(expression);
return expression;
}

private static readonly MethodInfo _resultMethodInfo
Expand Down
Expand Up @@ -62,9 +62,7 @@ public class IncludeSpecification
/// <returns>
/// A string that represents this object.
/// </returns>
public override string ToString()
{
return $"{QuerySource.ItemName}.{NavigationPath.Select(n => n.Name).Join(".")}";
}
public override string ToString()
=> $"{QuerySource.ItemName}.{NavigationPath.Select(n => n.Name).Join(".")}"; // Interpolation okay; strings
}
}
Expand Up @@ -231,16 +231,15 @@ public async Task<bool> MoveNext(CancellationToken cancellationToken)

[UsedImplicitly]
// ReSharper disable once InconsistentNaming
private static IAsyncEnumerable<TrackingGrouping<TKey, TOut, TIn>> _TrackGroupedEntities<TKey, TOut, TIn>(
IAsyncEnumerable<IGrouping<TKey, TOut>> groupings,
private static IAsyncEnumerable<TrackingGrouping<TKey, TElement>> _TrackGroupedEntities<TKey, TElement>(
IAsyncEnumerable<IGrouping<TKey, TElement>> groupings,
QueryContext queryContext,
IList<EntityTrackingInfo> entityTrackingInfos,
IList<Func<TIn, object>> entityAccessors)
where TIn : class
IList<Func<TElement, object>> entityAccessors)
{
return _Select(
groupings,
g => new TrackingGrouping<TKey, TOut, TIn>(
g => new TrackingGrouping<TKey, TElement>(
g,
queryContext,
entityTrackingInfos,
Expand All @@ -253,19 +252,18 @@ public async Task<bool> MoveNext(CancellationToken cancellationToken)
/// </summary>
public virtual MethodInfo TrackGroupedEntities => _trackGroupedEntities;

internal class TrackingGrouping<TKey, TOut, TIn> : IGrouping<TKey, TOut>
where TIn : class
internal class TrackingGrouping<TKey, TElement> : IGrouping<TKey, TElement>
{
private readonly IGrouping<TKey, TOut> _grouping;
private readonly IGrouping<TKey, TElement> _grouping;
private readonly QueryContext _queryContext;
private readonly IList<EntityTrackingInfo> _entityTrackingInfos;
private readonly IList<Func<TIn, object>> _entityAccessors;
private readonly IList<Func<TElement, object>> _entityAccessors;

public TrackingGrouping(
IGrouping<TKey, TOut> grouping,
IGrouping<TKey, TElement> grouping,
QueryContext queryContext,
IList<EntityTrackingInfo> entityTrackingInfos,
IList<Func<TIn, object>> entityAccessors)
IList<Func<TElement, object>> entityAccessors)
{
_grouping = grouping;
_queryContext = queryContext;
Expand All @@ -275,7 +273,7 @@ internal class TrackingGrouping<TKey, TOut, TIn> : IGrouping<TKey, TOut>

public TKey Key => _grouping.Key;

IEnumerator<TOut> IEnumerable<TOut>.GetEnumerator()
IEnumerator<TElement> IEnumerable<TElement>.GetEnumerator()
{
_queryContext.BeginTrackingQuery();

Expand All @@ -285,7 +283,7 @@ IEnumerator<TOut> IEnumerable<TOut>.GetEnumerator()
{
for (var i = 0; i < _entityTrackingInfos.Count; i++)
{
var entity = _entityAccessors[i](result as TIn);
var entity = _entityAccessors[i](result);

if (entity != null)
{
Expand All @@ -298,7 +296,7 @@ IEnumerator<TOut> IEnumerable<TOut>.GetEnumerator()
}
}

IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable<TOut>)this).GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable<TElement>)this).GetEnumerator();
}

private static readonly MethodInfo _toSequence
Expand Down Expand Up @@ -574,10 +572,19 @@ private sealed class SelectAsyncEnumerator : IAsyncEnumerator<TResult>

public void Dispose() => _source.Dispose();

public Task<bool> MoveNext(CancellationToken cancellationToken)
=> _source.MoveNext(cancellationToken);
public async Task<bool> MoveNext(CancellationToken cancellationToken)
{
if (await _source.MoveNext(cancellationToken))
{
Current = _selector(_source.Current);

public TResult Current => _selector(_source.Current);
return true;
}

return false;
}

public TResult Current { get; private set; }
}
}

Expand Down
Expand Up @@ -209,16 +209,15 @@ public bool MoveNext()

[UsedImplicitly]
// ReSharper disable once InconsistentNaming
private static IEnumerable<IGrouping<TKey, TOut>> _TrackGroupedEntities<TKey, TOut, TIn>(
IEnumerable<IGrouping<TKey, TOut>> groupings,
private static IEnumerable<IGrouping<TKey, TElement>> _TrackGroupedEntities<TKey, TElement>(
IEnumerable<IGrouping<TKey, TElement>> groupings,
QueryContext queryContext,
IList<EntityTrackingInfo> entityTrackingInfos,
IList<Func<TIn, object>> entityAccessors)
where TIn : class
IList<Func<TElement, object>> entityAccessors)
{
return groupings
.Select(g =>
new TrackingGrouping<TKey, TOut, TIn>(
new TrackingGrouping<TKey, TElement>(
g,
queryContext,
entityTrackingInfos,
Expand All @@ -231,19 +230,18 @@ public bool MoveNext()
/// </summary>
public virtual MethodInfo TrackGroupedEntities => _trackGroupedEntities;

private class TrackingGrouping<TKey, TOut, TIn> : IGrouping<TKey, TOut>
where TIn : class
private class TrackingGrouping<TKey, TElement> : IGrouping<TKey, TElement>
{
private readonly IGrouping<TKey, TOut> _grouping;
private readonly IGrouping<TKey, TElement> _grouping;
private readonly QueryContext _queryContext;
private readonly IList<EntityTrackingInfo> _entityTrackingInfos;
private readonly IList<Func<TIn, object>> _entityAccessors;
private readonly IList<Func<TElement, object>> _entityAccessors;

public TrackingGrouping(
IGrouping<TKey, TOut> grouping,
IGrouping<TKey, TElement> grouping,
QueryContext queryContext,
IList<EntityTrackingInfo> entityTrackingInfos,
IList<Func<TIn, object>> entityAccessors)
IList<Func<TElement, object>> entityAccessors)
{
_grouping = grouping;
_queryContext = queryContext;
Expand All @@ -253,7 +251,7 @@ private class TrackingGrouping<TKey, TOut, TIn> : IGrouping<TKey, TOut>

public TKey Key => _grouping.Key;

public IEnumerator<TOut> GetEnumerator()
public IEnumerator<TElement> GetEnumerator()
{
_queryContext.BeginTrackingQuery();

Expand All @@ -263,7 +261,7 @@ public IEnumerator<TOut> GetEnumerator()
{
for (var i = 0; i < _entityTrackingInfos.Count; i++)
{
var entity = _entityAccessors[i](result as TIn);
var entity = _entityAccessors[i](result);

if (entity != null)
{
Expand Down
23 changes: 21 additions & 2 deletions src/Microsoft.EntityFrameworkCore/Query/QueryCompilationContext.cs
Expand Up @@ -324,9 +324,28 @@ public virtual IReadOnlyList<IReadOnlyList<INavigation>> GetTrackableIncludes([N
.Create(queryModelVisitor)
.FindQuerySourcesRequiringMaterialization(queryModel);

foreach (var groupJoinClause in queryModel.BodyClauses.OfType<GroupJoinClause>())
var groupJoinClauses = queryModel.BodyClauses.OfType<GroupJoinClause>().ToList();
if (groupJoinClauses.Any())
{
_querySourcesRequiringMaterialization.Add(groupJoinClause.JoinClause);
_querySourcesRequiringMaterialization.Add(queryModel.MainFromClause);
foreach (var groupJoinClause in groupJoinClauses)
{
_querySourcesRequiringMaterialization.Add(groupJoinClause.JoinClause);

var subQueryInnerSequence = groupJoinClause.JoinClause.InnerSequence as SubQueryExpression;
if (subQueryInnerSequence != null)
{
var subQuerySourcesRequiringMaterialization
= _requiresMaterializationExpressionVisitorFactory
.Create(queryModelVisitor)
.FindQuerySourcesRequiringMaterialization(subQueryInnerSequence.QueryModel);

foreach (var subQuerySource in subQuerySourcesRequiringMaterialization)
{
_querySourcesRequiringMaterialization.Add(subQuerySource);
}
}
}
}
}

Expand Down
Expand Up @@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Globalization;
using System.Threading;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Internal;
Expand Down Expand Up @@ -73,7 +74,7 @@ public virtual TValue Next<TValue>([NotNull] Func<long> getNewLowValue)
}
}

return (TValue)Convert.ChangeType(newValue.Low, typeof(TValue));
return (TValue)Convert.ChangeType(newValue.Low, typeof(TValue), CultureInfo.InvariantCulture);
}

private HiLoValue GetNextValue()
Expand Down
2 changes: 1 addition & 1 deletion src/Microsoft.EntityFrameworkCore/project.json
@@ -1,5 +1,5 @@
{
"version": "1.0.0",
"version": "1.0.1",
"description": "Entity Framework Core is a lightweight and extensible version of the popular Entity Framework data access technology.\r\n\r\nCommonly Used Types:\r\nMicrosoft.EntityFrameworkCore.DbContext\r\nMicrosoft.EntityFrameworkCore.DbSet",
"packOptions": {
"tags": [
Expand Down
Expand Up @@ -170,7 +170,7 @@ public async Task SaveChanges_logs_concurrent_access(bool async)
{
context.Blogs.Add(new BloggingContext.Blog(false) { Url = "http://sample.com" });

((IInfrastructure<IServiceProvider>)context).Instance.GetService<IConcurrencyDetector>().EnterCriticalSection();
context.GetService<IConcurrencyDetector>().EnterCriticalSection();

Exception ex;
if (async)
Expand Down
Expand Up @@ -11,7 +11,7 @@
},
"dependencies": {
"dotnet-test-xunit": "1.0.0-rc3-000000-01",
"Microsoft.EntityFrameworkCore.Specification.Tests": "1.0.0"
"Microsoft.EntityFrameworkCore.Specification.Tests": "1.0.1"
},
"testRunner": "xunit",
"frameworks": {
Expand Down
Expand Up @@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Specification.Tests;
using Microsoft.Extensions.DependencyInjection;

Expand All @@ -18,9 +19,22 @@ public DataAnnotationInMemoryFixture()
_serviceProvider = new ServiceCollection()
.AddEntityFrameworkInMemoryDatabase()
.AddSingleton(TestInMemoryModelSource.GetFactory(OnModelCreating))
.AddSingleton<ThrowingModelValidator>()
.BuildServiceProvider();
}

public override ModelValidator ThrowingValidator
=> _serviceProvider.GetService<ThrowingModelValidator>();

// ReSharper disable once ClassNeverInstantiated.Local
private class ThrowingModelValidator : ModelValidator
{
protected override void ShowWarning(string message)
{
throw new InvalidOperationException(message);
}
}

public override InMemoryTestStore CreateTestStore()
{
return InMemoryTestStore.GetOrCreateShared(DatabaseName, () =>
Expand Down
Expand Up @@ -5,6 +5,7 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.Internal;
using Microsoft.Extensions.DependencyInjection;
Expand Down Expand Up @@ -331,6 +332,170 @@ public class Child3101
public string Name { get; set; }
}

[Fact]
public virtual void Repro5456_include_group_join_is_per_query_context()
{
using (CreateScratch<MyContext5456>(Seed5456))
{
Parallel.For(0, 10, i =>
{
using (var ctx = new MyContext5456())
{
var result = ctx.Posts.Where(x => x.Blog.Id > 1).Include(x => x.Blog).ToList();

Assert.Equal(198, result.Count);
}
});
}
}

[Fact]
public virtual void Repro5456_include_group_join_is_per_query_context_async()
{
using (CreateScratch<MyContext5456>(Seed5456))
{
Parallel.For(0, 10, async i =>
{
using (var ctx = new MyContext5456())
{
var result = await ctx.Posts.Where(x => x.Blog.Id > 1).Include(x => x.Blog).ToListAsync();

Assert.Equal(198, result.Count);
}
});
}
}

[Fact]
public virtual void Repro5456_multiple_include_group_join_is_per_query_context()
{
using (CreateScratch<MyContext5456>(Seed5456))
{
Parallel.For(0, 10, i =>
{
using (var ctx = new MyContext5456())
{
var result = ctx.Posts.Where(x => x.Blog.Id > 1).Include(x => x.Blog).Include(x => x.Comments).ToList();

Assert.Equal(198, result.Count);
}
});
}
}

[Fact]
public virtual void Repro5456_multiple_include_group_join_is_per_query_context_async()
{
using (CreateScratch<MyContext5456>(Seed5456))
{
Parallel.For(0, 10, async i =>
{
using (var ctx = new MyContext5456())
{
var result = await ctx.Posts.Where(x => x.Blog.Id > 1).Include(x => x.Blog).Include(x => x.Comments).ToListAsync();

Assert.Equal(198, result.Count);
}
});
}
}

[Fact]
public virtual void Repro5456_multi_level_include_group_join_is_per_query_context()
{
using (CreateScratch<MyContext5456>(Seed5456))
{
Parallel.For(0, 10, i =>
{
using (var ctx = new MyContext5456())
{
var result = ctx.Posts.Where(x => x.Blog.Id > 1).Include(x => x.Blog).ThenInclude(b => b.Author).ToList();

Assert.Equal(198, result.Count);
}
});
}
}

[Fact]
public virtual void Repro5456_multi_level_include_group_join_is_per_query_context_async()
{
using (CreateScratch<MyContext5456>(Seed5456))
{
Parallel.For(0, 10, async i =>
{
using (var ctx = new MyContext5456())
{
var result = await ctx.Posts.Where(x => x.Blog.Id > 1).Include(x => x.Blog).ThenInclude(b => b.Author).ToListAsync();

Assert.Equal(198, result.Count);
}
});
}
}

private void Seed5456(MyContext5456 context)
{
for (var i = 0; i < 100; i++)
{
context.Add(
new Blog5456
{
Posts = new List<Post5456>
{
new Post5456
{
Comments = new List<Comment5456>
{
new Comment5456(),
new Comment5456()
}
},
new Post5456()
},
Author = new Author5456()
});
}
context.SaveChanges();
}

public class MyContext5456 : DbContext
{
public DbSet<Blog5456> Blogs { get; set; }
public DbSet<Post5456> Posts { get; set; }
public DbSet<Comment5456> Comments { get; set; }
public DbSet<Author5456> Authors { get; set; }

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseInMemoryDatabase();
}

public class Blog5456
{
public int Id { get; set; }
public List<Post5456> Posts { get; set; }
public Author5456 Author { get; set; }
}

public class Author5456
{
public int Id { get; set; }
public List<Blog5456> Blogs { get; set; }
}

public class Post5456
{
public int Id { get; set; }
public Blog5456 Blog { get; set; }
public List<Comment5456> Comments { get; set; }
}

public class Comment5456
{
public int Id { get; set; }
public Post5456 Blog { get; set; }
}

private static InMemoryTestStore CreateScratch<TContext>(Action<TContext> Seed)
where TContext : DbContext, new()
=> InMemoryTestStore.CreateScratch(
Expand Down
Expand Up @@ -5,7 +5,7 @@
},
"dependencies": {
"dotnet-test-xunit": "1.0.0-rc3-000000-01",
"Microsoft.EntityFrameworkCore.Specification.Tests": "1.0.0"
"Microsoft.EntityFrameworkCore.Specification.Tests": "1.0.1"
},
"testRunner": "xunit",
"frameworks": {
Expand Down

This file was deleted.

This file was deleted.

This file was deleted.