Skip to content

Commit f9b1971

Browse files
committed
Add support for CSharpHelper for List literals
Fixes #19274 Also relates to npgsql/efcore.pg#2402
1 parent 27a83b9 commit f9b1971

2 files changed

Lines changed: 140 additions & 0 deletions

File tree

src/EFCore.Design/Design/Internal/CSharpHelper.cs

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -737,6 +737,73 @@ public virtual string Literal(object?[,] values)
737737
return builder.ToString();
738738
}
739739

740+
/// <summary>
741+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
742+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
743+
/// any release. You should only use it directly in your code with extreme caution and knowing that
744+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
745+
/// </summary>
746+
public virtual string Literal<T>(IList<T> values, bool vertical = false)
747+
=> List(typeof(T), values, vertical);
748+
749+
private string List(Type type, IEnumerable values, bool vertical = false)
750+
{
751+
var builder = new IndentedStringBuilder();
752+
753+
builder.Append("new List<")
754+
.Append(Reference(type))
755+
.Append("> {");
756+
757+
if (vertical)
758+
{
759+
builder.AppendLine();
760+
builder.IncrementIndent();
761+
}
762+
else
763+
{
764+
builder.Append(" ");
765+
}
766+
767+
var first = true;
768+
foreach (var value in values)
769+
{
770+
if (first)
771+
{
772+
first = false;
773+
}
774+
else
775+
{
776+
builder.Append(",");
777+
778+
if (vertical)
779+
{
780+
builder.AppendLine();
781+
}
782+
else
783+
{
784+
builder.Append(" ");
785+
}
786+
}
787+
788+
builder.Append(UnknownLiteral(value));
789+
}
790+
791+
if (vertical)
792+
{
793+
builder.AppendLine();
794+
builder.DecrementIndent();
795+
}
796+
else
797+
{
798+
builder.Append(" ");
799+
}
800+
801+
builder.Append("}");
802+
803+
804+
return builder.ToString();
805+
}
806+
740807
/// <summary>
741808
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
742809
/// 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)
844911
return Array(literalType.GetElementType()!, array);
845912
}
846913

914+
if (value is IList list && value.GetType().IsGenericType && value.GetType().GetGenericTypeDefinition() == typeof(List<>))
915+
{
916+
return List(value.GetType().GetGenericArguments()[0], list);
917+
}
918+
847919
var mapping = _typeMappingSource.FindMapping(literalType);
848920
if (mapping != null)
849921
{
@@ -878,6 +950,35 @@ private bool HandleExpression(Expression expression, StringBuilder builder, bool
878950

879951
HandleList(((NewArrayExpression)expression).Expressions, builder, simple: true);
880952

953+
builder
954+
.Append(" }");
955+
956+
return true;
957+
case ExpressionType.ListInit:
958+
var listExpr = (ListInitExpression)expression;
959+
if (listExpr.Initializers.Any(_ => _.Arguments.Count != 1))
960+
{
961+
// if there is one more than one element in the arguments we can't make a literal cleanly
962+
return false;
963+
}
964+
965+
builder
966+
.Append("new ")
967+
.Append(Reference(expression.Type));
968+
969+
if (listExpr.NewExpression.Arguments.Count > 0 && !HandleArguments(listExpr.NewExpression.Arguments, builder))
970+
{
971+
return false;
972+
}
973+
974+
builder
975+
.Append(" { ");
976+
977+
if (!HandleList(listExpr.Initializers.Select(_ => _.Arguments.First()), builder, simple: true))
978+
{
979+
return false;
980+
}
981+
881982
builder
882983
.Append(" }");
883984

test/EFCore.Design.Tests/Design/Internal/CSharpHelperTest.cs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,18 @@ public void Literal_works_when_many_ByteArray()
121121
new byte[] { 1, 2 },
122122
"new byte[] { 1, 2 }");
123123

124+
[ConditionalFact]
125+
public void Literal_works_when_string_list_no_arguments()
126+
=> Literal_works(
127+
new List<string> { "one", "two" },
128+
@"new List<string> { ""one"", ""two"" }");
129+
130+
[ConditionalFact]
131+
public void Literal_works_when_string_list_with_arguments()
132+
=> Literal_works(
133+
new List<string>(new [] { "one" }) { "two", "three" },
134+
@"new List<string> { ""one"", ""two"", ""three"" }");
135+
124136
[ConditionalFact]
125137
public void Literal_works_when_multiline_string()
126138
=> Literal_works(
@@ -607,6 +619,33 @@ public void Literal_with_add()
607619
new CSharpHelper(typeMapping).UnknownLiteral(new SimpleTestType()));
608620
}
609621

622+
[ConditionalFact]
623+
public void Literal_with_list_of_string_init_no_arguments()
624+
{
625+
var typeMapping = CreateTypeMappingSource<SimpleTestType>(
626+
v => Expression.ListInit(
627+
Expression.New(typeof(List<>).MakeGenericType(typeof(string))),
628+
Expression.Constant("one"), Expression.Constant("two")));
629+
630+
Assert.Equal(
631+
@"new List<string> { ""one"", ""two"" }",
632+
new CSharpHelper(typeMapping).UnknownLiteral(new SimpleTestType()));
633+
}
634+
635+
[ConditionalFact]
636+
public void Literal_with_list_of_string_init_with_arguments()
637+
{
638+
var constructor = typeof(List<>).MakeGenericType((typeof(string))).GetConstructor(new[] { typeof(IEnumerable<string>) })!;
639+
var typeMapping = CreateTypeMappingSource<SimpleTestType>(
640+
v => Expression.ListInit(
641+
Expression.New(constructor, Expression.NewArrayInit(typeof(string), Expression.Constant("one"))),
642+
Expression.Constant("one"), Expression.Constant("two")));
643+
644+
Assert.Equal(
645+
@"new List<string>(new string[] { ""one"" }) { ""one"", ""two"" }",
646+
new CSharpHelper(typeMapping).UnknownLiteral(new SimpleTestType()));
647+
}
648+
610649
[ConditionalFact]
611650
public void Literal_with_unsupported_node_throws()
612651
{

0 commit comments

Comments
 (0)