From 25cd7aef27e5b1ae1d57b4fd7e44b5be1b7df0b3 Mon Sep 17 00:00:00 2001 From: Matt Morrissette Date: Sun, 12 Jun 2022 17:09:44 -0700 Subject: [PATCH] Add support for CSharpHelper for List literals Fixes #19274 Also relates to https://github.com/npgsql/efcore.pg/pull/2402 --- .../Design/Internal/CSharpHelper.cs | 91 +++++++++++++++++++ .../Design/Internal/CSharpHelperTest.cs | 19 ++++ 2 files changed, 110 insertions(+) diff --git a/src/EFCore.Design/Design/Internal/CSharpHelper.cs b/src/EFCore.Design/Design/Internal/CSharpHelper.cs index 9ee6e914bc6..9b4c0407dab 100644 --- a/src/EFCore.Design/Design/Internal/CSharpHelper.cs +++ b/src/EFCore.Design/Design/Internal/CSharpHelper.cs @@ -737,6 +737,73 @@ public virtual string Literal(object?[,] values) return builder.ToString(); } + /// + /// 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(IList values, bool vertical = false) + => List(typeof(T), values, vertical); + + private string List(Type type, IEnumerable values, bool vertical = false) + { + var builder = new IndentedStringBuilder(); + + builder.Append("new List<") + .Append(Reference(type)) + .Append("> {"); + + if (vertical) + { + builder.AppendLine(); + builder.IncrementIndent(); + } + else + { + builder.Append(" "); + } + + var first = true; + foreach (var value in values) + { + if (first) + { + first = false; + } + else + { + builder.Append(","); + + if (vertical) + { + builder.AppendLine(); + } + else + { + builder.Append(" "); + } + } + + builder.Append(UnknownLiteral(value)); + } + + if (vertical) + { + builder.AppendLine(); + builder.DecrementIndent(); + } + else + { + builder.Append(" "); + } + + builder.Append("}"); + + + return builder.ToString(); + } + /// /// 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 @@ -844,6 +911,11 @@ public virtual string UnknownLiteral(object? value) return Array(literalType.GetElementType()!, array); } + if (value is IList list && value.GetType().IsGenericType && value.GetType().GetGenericTypeDefinition() == typeof(List<>)) + { + return List(value.GetType().GetGenericArguments()[0], list); + } + var mapping = _typeMappingSource.FindMapping(literalType); if (mapping != null) { @@ -878,6 +950,25 @@ private bool HandleExpression(Expression expression, StringBuilder builder, bool HandleList(((NewArrayExpression)expression).Expressions, builder, simple: true); + builder + .Append(" }"); + + return true; + case ExpressionType.ListInit: + if (((ListInitExpression)expression).Initializers.Any(_ => _.Arguments.Count != 1)) + { + // if there is one more than one element in the arguments we can't make a literal cleanly + return false; + } + + builder + .Append("new ") + .Append(Reference(expression.Type)) + .Append(" { "); + + HandleList(((ListInitExpression)expression) + .Initializers.Select(_ => _.Arguments.First()), builder, simple: true); + builder .Append(" }"); diff --git a/test/EFCore.Design.Tests/Design/Internal/CSharpHelperTest.cs b/test/EFCore.Design.Tests/Design/Internal/CSharpHelperTest.cs index d03c74c1aa9..f1189ed6496 100644 --- a/test/EFCore.Design.Tests/Design/Internal/CSharpHelperTest.cs +++ b/test/EFCore.Design.Tests/Design/Internal/CSharpHelperTest.cs @@ -121,6 +121,12 @@ public void Literal_works_when_many_ByteArray() new byte[] { 1, 2 }, "new byte[] { 1, 2 }"); + [ConditionalFact] + public void Literal_works_when_string_list() + => Literal_works( + new List { "one", "two" }, + @"new List { ""one"", ""two"" }"); + [ConditionalFact] public void Literal_works_when_multiline_string() => Literal_works( @@ -607,6 +613,19 @@ public void Literal_with_add() new CSharpHelper(typeMapping).UnknownLiteral(new SimpleTestType())); } + [ConditionalFact] + public void Literal_with_list_of_string_init() + { + var typeMapping = CreateTypeMappingSource( + v => Expression.ListInit( + Expression.New(typeof(List<>).MakeGenericType(typeof(string))), + Expression.Constant("one"), Expression.Constant("two"))); + + Assert.Equal( + @"new List { ""one"", ""two"" }", + new CSharpHelper(typeMapping).UnknownLiteral(new SimpleTestType())); + } + [ConditionalFact] public void Literal_with_unsupported_node_throws() {