diff --git a/src/EFCore/Diagnostics/CoreEventId.cs b/src/EFCore/Diagnostics/CoreEventId.cs index 36cb4c8ded5..09c7fa839f8 100644 --- a/src/EFCore/Diagnostics/CoreEventId.cs +++ b/src/EFCore/Diagnostics/CoreEventId.cs @@ -66,6 +66,7 @@ private enum Id QueryExecutionPlanned, PossibleUnintendedCollectionNavigationNullComparisonWarning, PossibleUnintendedReferenceComparisonWarning, + InvalidIncludePathError, // Infrastructure events SensitiveDataLoggingEnabledWarning = CoreBaseId + 400, @@ -196,6 +197,20 @@ public static readonly EventId PossibleUnintendedCollectionNavigationNullCompari public static readonly EventId PossibleUnintendedReferenceComparisonWarning = MakeQueryId(Id.PossibleUnintendedReferenceComparisonWarning); + /// + /// + /// Invalid include path '{navigationChain}', couldn't find navigation for '{navigationName}'. + /// + /// + /// This event is in the category. + /// + /// + /// This event uses the payload when used with a . + /// + /// + public static readonly EventId InvalidIncludePathError + = MakeQueryId(Id.InvalidIncludePathError); + private static readonly string _infraPrefix = DbLoggerCategory.Infrastructure.Name + "."; private static EventId MakeInfraId(Id id) => new EventId((int)id, _infraPrefix + id); diff --git a/src/EFCore/Diagnostics/CoreLoggerExtensions.cs b/src/EFCore/Diagnostics/CoreLoggerExtensions.cs index c9046172cdf..acbf40e86e6 100644 --- a/src/EFCore/Diagnostics/CoreLoggerExtensions.cs +++ b/src/EFCore/Diagnostics/CoreLoggerExtensions.cs @@ -531,7 +531,7 @@ public static void PossibleUnintendedReferenceComparisonWarning( if (diagnostics.ShouldLog(definition)) { - definition.Log(diagnostics,left, right); + definition.Log(diagnostics, left, right); } if (diagnostics.NeedsEventData(definition, out var diagnosticSourceEnabled, out var simpleLogEnabled)) @@ -553,6 +553,37 @@ private static string PossibleUnintendedReferenceComparisonWarning(EventDefiniti return d.GenerateMessage(p.Left, p.Right); } + public static void InvalidIncludePathError( + [NotNull] this IDiagnosticsLogger diagnostics, + [NotNull] string navigationChain, + [NotNull] string navigationName) + { + var definition = CoreResources.LogInvalidIncludePath(diagnostics); + if (diagnostics.ShouldLog(definition)) + { + definition.Log(diagnostics, navigationChain, navigationName); + } + + if (diagnostics.NeedsEventData(definition, out var diagnosticSourceEnabled, out var simpleLogEnabled)) + { + var eventData = new InvalidIncludePathEventData( + definition, + InvalidIncludePathError, + navigationChain, + navigationName); + + diagnostics.DispatchEventData(definition, eventData, diagnosticSourceEnabled, simpleLogEnabled); + } + } + + private static string InvalidIncludePathError(EventDefinitionBase definition, EventData payload) + { + var d = (EventDefinition)definition; + var p = (InvalidIncludePathEventData)payload; + + return d.GenerateMessage(p.NavigationChain, p.NavigationName); + } + /// /// Logs for the event. /// diff --git a/src/EFCore/Diagnostics/Internal/InvalidIncludePathEventData.cs b/src/EFCore/Diagnostics/Internal/InvalidIncludePathEventData.cs new file mode 100644 index 00000000000..4f9e8b684e3 --- /dev/null +++ b/src/EFCore/Diagnostics/Internal/InvalidIncludePathEventData.cs @@ -0,0 +1,44 @@ +// 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 System.Diagnostics; +using JetBrains.Annotations; + +namespace Microsoft.EntityFrameworkCore.Diagnostics +{ + /// + /// A event payload class for events that have + /// invalid include path information. + /// + public class InvalidIncludePathEventData : EventData + { + /// + /// Constructs the event payload. + /// + /// The event definition. + /// A delegate that generates a log message for this event. + /// Navigation chain included to this point. + /// The name of the invalid navigation. + public InvalidIncludePathEventData( + [NotNull] EventDefinitionBase eventDefinition, + [NotNull] Func messageGenerator, + [NotNull] string navigationChain, + [NotNull] string navigationName) + : base(eventDefinition, messageGenerator) + { + NavigationChain = navigationChain; + NavigationName = navigationName; + } + + /// + /// Navigation chain included to this point. + /// + public virtual string NavigationChain { get; } + + /// + /// The name of the invalid navigation. + /// + public virtual string NavigationName { get; } + } +} diff --git a/src/EFCore/Diagnostics/LoggingDefinitions.cs b/src/EFCore/Diagnostics/LoggingDefinitions.cs index 9880968e271..82a3f4de886 100644 --- a/src/EFCore/Diagnostics/LoggingDefinitions.cs +++ b/src/EFCore/Diagnostics/LoggingDefinitions.cs @@ -376,6 +376,15 @@ public abstract class LoggingDefinitions [EntityFrameworkInternal] public EventDefinitionBase LogPossibleUnintendedReferenceComparison; + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + public EventDefinitionBase LogInvalidIncludePath; + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in diff --git a/src/EFCore/Infrastructure/CoreOptionsExtension.cs b/src/EFCore/Infrastructure/CoreOptionsExtension.cs index 770389c8466..aee74f75d4d 100644 --- a/src/EFCore/Infrastructure/CoreOptionsExtension.cs +++ b/src/EFCore/Infrastructure/CoreOptionsExtension.cs @@ -48,7 +48,8 @@ private WarningsConfiguration _warningsConfiguration = new WarningsConfiguration() .TryWithExplicit(CoreEventId.ManyServiceProvidersCreatedWarning, WarningBehavior.Throw) .TryWithExplicit(CoreEventId.LazyLoadOnDisposedContextWarning, WarningBehavior.Throw) - .TryWithExplicit(CoreEventId.DetachedLazyLoadingWarning, WarningBehavior.Throw); + .TryWithExplicit(CoreEventId.DetachedLazyLoadingWarning, WarningBehavior.Throw) + .TryWithExplicit(CoreEventId.InvalidIncludePathError, WarningBehavior.Throw); /// /// Creates a new set of options with everything set to default values. diff --git a/src/EFCore/Properties/CoreStrings.Designer.cs b/src/EFCore/Properties/CoreStrings.Designer.cs index cf4bafc35de..2d3560d7c2a 100644 --- a/src/EFCore/Properties/CoreStrings.Designer.cs +++ b/src/EFCore/Properties/CoreStrings.Designer.cs @@ -3478,6 +3478,30 @@ public static EventDefinition LogPossibleUnintendedReferenceComp return (EventDefinition)definition; } + /// + /// Invalid include path '{navigationChain}', couldn't find navigation for '{navigationName}'. + /// + public static EventDefinition LogInvalidIncludePath([NotNull] IDiagnosticsLogger logger) + { + var definition = ((LoggingDefinitions)logger.Definitions).LogInvalidIncludePath; + if (definition == null) + { + definition = LazyInitializer.EnsureInitialized( + ref ((LoggingDefinitions)logger.Definitions).LogInvalidIncludePath, + () => new EventDefinition( + logger.Options, + CoreEventId.InvalidIncludePathError, + LogLevel.Error, + "CoreEventId.InvalidIncludePathError", + level => LoggerMessage.Define( + level, + CoreEventId.InvalidIncludePathError, + _resourceManager.GetString("InvalidIncludePath")))); + } + + return (EventDefinition)definition; + } + /// /// The same entity is being tracked as different weak entity types '{dependent1}' and '{dependent2}'. If a property value changes it will result in two store changes, which might not be the desired outcome. /// diff --git a/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.cs b/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.cs index fbaf27181a0..bae24432a3f 100644 --- a/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.cs +++ b/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.cs @@ -789,7 +789,7 @@ private NavigationExpansionExpression ProcessInclude(NavigationExpansionExpressi if (includeTreeNodes.Count == 0) { - throw new InvalidOperationException(CoreStrings.InvalidIncludePath(navigationChain, navigationName)); + _queryCompilationContext.Logger.InvalidIncludePathError(navigationChain, navigationName); } } } diff --git a/test/EFCore.Specification.Tests/Query/NorthwindMiscellaneousQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/NorthwindMiscellaneousQueryTestBase.cs index c8fc367adcb..f44f0131546 100644 --- a/test/EFCore.Specification.Tests/Query/NorthwindMiscellaneousQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/NorthwindMiscellaneousQueryTestBase.cs @@ -5864,5 +5864,13 @@ public virtual async Task Null_parameter_name_works(bool isAsync) Assert.Empty(result); } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task String_include_on_incorrect_property_throws(bool async) + { + return Assert.ThrowsAsync( + async () => await AssertQuery(async, ss => ss.Set().Include("OrderDetails"))); + } } } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs index 76a80b0fb45..42dd81259b2 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs @@ -7207,6 +7207,75 @@ private class MyModel20097 : IHaveId20097 #endregion + #region Issue20609 + + [ConditionalFact] + public virtual void Can_ignore_invalid_include_path_error() + { + using var context = CreateContext20609(); + var result = context.Set().Include("SubB").ToList(); + } + + public class BaseClass + { + public string Id { get; set; } + } + + public class ClassA : BaseClass + { + public SubA SubA { get; set; } + } + + public class ClassB : BaseClass + { + public SubB SubB { get; set; } + } + + public class SubA + { + public int Id { get; set; } + } + + public class SubB + { + public int Id { get; set; } + } + + private BugContext20609 CreateContext20609() + { + var testStore = SqlServerTestStore.CreateInitialized("QueryBugsTest", multipleActiveResultSets: true); + var options = Fixture.AddOptions(testStore.AddProviderOptions(new DbContextOptionsBuilder())) + .EnableDetailedErrors() + .EnableServiceProviderCaching(false) + .ConfigureWarnings(x => x.Ignore(CoreEventId.InvalidIncludePathError)) + .Options; + + var context = new BugContext20609(options); + context.Database.EnsureCreatedResiliently(); + + return context; + } + + private class BugContext20609 : DbContext + { + public BugContext20609(DbContextOptions options) + : base(options) + { + } + + public DbSet BaseClasses { get; set; } + public DbSet SubAs { get; set; } + public DbSet SubBs { get; set; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity().HasBaseType().HasOne(x => x.SubA).WithMany(); + modelBuilder.Entity().HasBaseType().HasOne(x => x.SubB).WithMany(); + } + } + + #endregion + private DbContextOptions _options; private SqlServerTestStore CreateTestStore(