From 6e1047b0b52a0fccd254fbd8c2acbd20e98ad5ff Mon Sep 17 00:00:00 2001 From: Matt Morrissette Date: Tue, 28 Jun 2022 13:38:59 -0700 Subject: [PATCH] Add support for CSharpHelper for Dictionary literals Fixes #19274 --- .../Design/Internal/CSharpHelper.cs | 61 +++++++++++++++++-- src/EFCore/Design/ICSharpHelper.cs | 16 +++++ .../Design/Internal/CSharpHelperTest.cs | 38 +++++++++++- 3 files changed, 108 insertions(+), 7 deletions(-) diff --git a/src/EFCore.Design/Design/Internal/CSharpHelper.cs b/src/EFCore.Design/Design/Internal/CSharpHelper.cs index 3e65e805bac..d5b303e232d 100644 --- a/src/EFCore.Design/Design/Internal/CSharpHelper.cs +++ b/src/EFCore.Design/Design/Internal/CSharpHelper.cs @@ -758,12 +758,57 @@ private string List(Type type, IEnumerable values, bool vertical = false) .Append(Reference(type)) .Append(">"); + return HandleEnumerable(builder, vertical, values, value => + { + builder.Append(UnknownLiteral(value)); + }); + } + + /// + /// 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. + /// + public virtual string Literal(Dictionary dict, bool vertical = false) + where TKey : notnull + => Dictionary(typeof(TKey), typeof(TValue), dict, vertical); + + private string Dictionary(Type keyType, Type valueType, IDictionary dict, bool vertical = false) + { + var builder = new IndentedStringBuilder(); + + builder.Append("new Dictionary<") + .Append(Reference(keyType)) + .Append(", ") + .Append(Reference(valueType)) + .Append(">"); + + return HandleEnumerable(builder, vertical, dict.Keys, key => + { + builder.Append("[") + .Append(UnknownLiteral(key)) + .Append("] = ") + .Append(UnknownLiteral(dict[key])); + }); + } + + private static string HandleEnumerable(IndentedStringBuilder builder, bool vertical, IEnumerable values, Action handleValue) + { var first = true; foreach (var value in values) { if (first) { - builder.Append(" {"); + if (vertical) + { + builder.AppendLine(); + } + else + { + builder.Append(" "); + } + builder.Append("{"); if (vertical) { builder.AppendLine(); @@ -789,7 +834,7 @@ private string List(Type type, IEnumerable values, bool vertical = false) } } - builder.Append(UnknownLiteral(value)); + handleValue(value); } if (first) @@ -921,9 +966,17 @@ public virtual string UnknownLiteral(object? value) return Array(literalType.GetElementType()!, array); } - if (value is IList list && value.GetType().IsGenericType && value.GetType().GetGenericTypeDefinition() == typeof(List<>)) + var valueType = value.GetType(); + if (valueType.IsGenericType && !valueType.IsGenericTypeDefinition) { - return List(value.GetType().GetGenericArguments()[0], list); + var genericArguments = valueType.GetGenericArguments(); + switch (value) + { + case IList list when genericArguments.Length == 1 && valueType.GetGenericTypeDefinition() == typeof(List<>): + return List(genericArguments[0], list); + case IDictionary dict when genericArguments.Length == 2 && valueType.GetGenericTypeDefinition() == typeof(Dictionary<,>): + return Dictionary(genericArguments[0], genericArguments[1], dict); + } } var mapping = _typeMappingSource.FindMapping(literalType); diff --git a/src/EFCore/Design/ICSharpHelper.cs b/src/EFCore/Design/ICSharpHelper.cs index cbb5a11ee5e..e529f473ee7 100644 --- a/src/EFCore/Design/ICSharpHelper.cs +++ b/src/EFCore/Design/ICSharpHelper.cs @@ -263,6 +263,22 @@ string Literal(T? value) /// The literal. string Literal(T[] values, bool vertical = false); + /// + /// Generates a list literal. + /// + /// The list. + /// A value indicating whether to layout the literal vertically. + /// The literal. + string Literal(List values, bool vertical = false); + + /// + /// Generates a dictionary literal. + /// + /// The dictionary. + /// A value indicating whether to layout the literal vertically. + /// The literal. + string Literal(Dictionary values, bool vertical = false) where TKey : notnull; + /// /// Generates a valid C# namespace from the specified parts. /// diff --git a/test/EFCore.Design.Tests/Design/Internal/CSharpHelperTest.cs b/test/EFCore.Design.Tests/Design/Internal/CSharpHelperTest.cs index 59402d024a8..70237ba4cac 100644 --- a/test/EFCore.Design.Tests/Design/Internal/CSharpHelperTest.cs +++ b/test/EFCore.Design.Tests/Design/Internal/CSharpHelperTest.cs @@ -140,10 +140,42 @@ public void Literal_works_when_list_of_mixed_objects() @"new List { 1, ""two"" }"); [ConditionalFact] - public void Literal_works_when_list_with_ctor_arguments() + public void Literal_works_when_list_vertical() + => Assert.Equal( + @"new List +{ + 1, + ""two"" +}".ReplaceLineEndings(), new CSharpHelper(TypeMappingSource).Literal( + new List { 1, "two" }, true)); + + [ConditionalFact] + public void Literal_works_when_empty_dictionary() + => Literal_works( + new Dictionary(), + @"new Dictionary()"); + + [ConditionalFact] + public void Literal_works_when_dictionary_with_single_element() => Literal_works( - new List(new [] { "one" }) { "two", "three" }, - @"new List { ""one"", ""two"", ""three"" }"); + new Dictionary { ["one"] = "value" }, + @"new Dictionary { [""one""] = ""value"" }"); + + [ConditionalFact] + public void Literal_works_when_dictionary_of_mixed_objects() + => Literal_works( + new Dictionary { ["one"] = 1, ["two"] = "Two" }, + @"new Dictionary { [""one""] = 1, [""two""] = ""Two"" }"); + + [ConditionalFact] + public void Literal_works_when_dictionary_vertical() + => Assert.Equal( + @"new Dictionary +{ + [1] = 1, + [2] = ""Two"" +}".ReplaceLineEndings(), new CSharpHelper(TypeMappingSource).Literal( + new Dictionary { [1] = 1, [2] = "Two" }, true)); [ConditionalFact] public void Literal_works_when_multiline_string()