Skip to content

Commit 13bb7d2

Browse files
committed
Handle updating complex type properties in ExecuteUpdate
Closes #32058
1 parent 5ce2e99 commit 13bb7d2

File tree

9 files changed

+669
-163
lines changed

9 files changed

+669
-163
lines changed

src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.ExecuteUpdate.cs

Lines changed: 324 additions & 92 deletions
Large diffs are not rendered by default.

test/EFCore.Relational.Specification.Tests/BulkUpdates/ComplexTypeBulkUpdatesTestBase.cs

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,121 @@ public virtual Task Update_projected_complex_type_via_OrderBy_Skip_throws(bool a
106106
s => s.SetProperty(c => c.ZipCode, 12345),
107107
rowsAffectedCount: 3));
108108

109+
[ConditionalTheory]
110+
[MemberData(nameof(IsAsyncData))]
111+
public virtual Task Update_complex_type_to_parameter(bool async)
112+
{
113+
var newAddress = new Address
114+
{
115+
AddressLine1 = "New AddressLine1",
116+
AddressLine2 = "New AddressLine2",
117+
ZipCode = 99999,
118+
Country = new()
119+
{
120+
Code = "FR",
121+
FullName = "France"
122+
},
123+
Tags = new List<string> { "new_tag1", "new_tag2" }
124+
};
125+
126+
return AssertUpdate(
127+
async,
128+
ss => ss.Set<Customer>(),
129+
c => c,
130+
s => s.SetProperty(x => x.ShippingAddress, newAddress),
131+
rowsAffectedCount: 3);
132+
}
133+
134+
[ConditionalTheory]
135+
[MemberData(nameof(IsAsyncData))]
136+
public virtual Task Update_nested_complex_type_to_parameter(bool async)
137+
{
138+
var newCountry = new Country
139+
{
140+
Code = "FR",
141+
FullName = "France"
142+
};
143+
144+
return AssertUpdate(
145+
async,
146+
ss => ss.Set<Customer>(),
147+
c => c,
148+
s => s.SetProperty(x => x.ShippingAddress.Country, newCountry),
149+
rowsAffectedCount: 3);
150+
}
151+
152+
[ConditionalTheory]
153+
[MemberData(nameof(IsAsyncData))]
154+
public virtual Task Update_complex_type_to_another_database_complex_type(bool async)
155+
=> AssertUpdate(
156+
async,
157+
ss => ss.Set<Customer>(),
158+
c => c,
159+
s => s.SetProperty(x => x.ShippingAddress, x => x.BillingAddress),
160+
rowsAffectedCount: 3);
161+
162+
[ConditionalTheory]
163+
[MemberData(nameof(IsAsyncData))]
164+
public virtual Task Update_complex_type_to_inline_without_lambda(bool async)
165+
=> AssertUpdate(
166+
async,
167+
ss => ss.Set<Customer>(),
168+
c => c,
169+
s => s.SetProperty(x => x.ShippingAddress, new Address
170+
{
171+
AddressLine1 = "New AddressLine1",
172+
AddressLine2 = "New AddressLine2",
173+
ZipCode = 99999,
174+
Country = new()
175+
{
176+
Code = "FR",
177+
FullName = "France"
178+
},
179+
Tags = new List<string> { "new_tag1", "new_tag2" }
180+
}),
181+
rowsAffectedCount: 3);
182+
183+
[ConditionalTheory]
184+
[MemberData(nameof(IsAsyncData))]
185+
public virtual Task Update_complex_type_to_inline_with_lambda(bool async)
186+
=> AssertUpdate(
187+
async,
188+
ss => ss.Set<Customer>(),
189+
c => c,
190+
s => s.SetProperty(x => x.ShippingAddress, x => new Address
191+
{
192+
AddressLine1 = "New AddressLine1",
193+
AddressLine2 = "New AddressLine2",
194+
ZipCode = 99999,
195+
Country = new()
196+
{
197+
Code = "FR",
198+
FullName = "France"
199+
},
200+
Tags = new List<string> { "new_tag1", "new_tag2" }
201+
}),
202+
rowsAffectedCount: 3);
203+
204+
[ConditionalTheory]
205+
[MemberData(nameof(IsAsyncData))]
206+
public virtual Task Update_complex_type_to_another_database_complex_type_with_subquery(bool async)
207+
=> AssertUpdate(
208+
async,
209+
ss => ss.Set<Customer>().OrderBy(c => c.Id).Skip(1),
210+
c => c,
211+
s => s.SetProperty(x => x.ShippingAddress, x => x.BillingAddress),
212+
rowsAffectedCount: 2);
213+
214+
[ConditionalTheory]
215+
[MemberData(nameof(IsAsyncData))]
216+
public virtual Task Update_collection_inside_complex_type(bool async)
217+
=> AssertUpdate(
218+
async,
219+
ss => ss.Set<Customer>(),
220+
c => c,
221+
s => s.SetProperty(x => x.ShippingAddress.Tags, new List<string> { "new_tag1", "new_tag2" }),
222+
rowsAffectedCount: 3);
223+
109224
private void ClearLog()
110225
=> Fixture.TestSqlLoggerFactory.Clear();
111226
}

test/EFCore.Relational.Specification.Tests/TestUtilities/TestSqlLoggerFactory.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,7 @@ void RewriteSourceWithNewBaseline(string fileName, int lineNumber)
254254
indentBuilder.Append(" ");
255255
var indent = indentBuilder.ToString();
256256
var newBaseLine = $@"Assert{(forUpdate ? "ExecuteUpdate" : "")}Sql(
257-
{string.Join("," + Environment.NewLine + indent + "//" + Environment.NewLine, SqlStatements.Skip(offset).Take(count).Select(sql => "\"\"\"" + Environment.NewLine + sql + Environment.NewLine + "\"\"\""))})";
257+
{string.Join("," + Environment.NewLine + indent + "//" + Environment.NewLine, SqlStatements.Skip(offset).Take(count).Select(sql => indent + "\"\"\"" + Environment.NewLine + sql + Environment.NewLine + "\"\"\""))})";
258258
var numNewlinesInRewritten = newBaseLine.Count(c => c is '\n' or '\r');
259259

260260
writer.Write(newBaseLine);

test/EFCore.Specification.Tests/Query/ComplexTypeQueryTestBase.cs

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -143,8 +143,15 @@ public virtual Task Complex_type_equals_constant(bool async)
143143
{
144144
AddressLine1 = "804 S. Lakeshore Road",
145145
ZipCode = 38654,
146-
Country = new Country { FullName = "United States", Code = "US" }
147-
}));
146+
Country = new Country { FullName = "United States", Code = "US" },
147+
Tags = new List<string> { "foo", "bar" }
148+
}),
149+
ss => ss.Set<Customer>().Where(
150+
c =>
151+
c.ShippingAddress.AddressLine1 == "804 S. Lakeshore Road"
152+
&& c.ShippingAddress.ZipCode == 38654
153+
&& c.ShippingAddress.Country == new Country { FullName = "United States", Code = "US" }
154+
&& c.ShippingAddress.Tags.SequenceEqual(new List<string> { "foo", "bar" })));
148155

149156
[ConditionalTheory]
150157
[MemberData(nameof(IsAsyncData))]
@@ -154,12 +161,19 @@ public virtual Task Complex_type_equals_parameter(bool async)
154161
{
155162
AddressLine1 = "804 S. Lakeshore Road",
156163
ZipCode = 38654,
157-
Country = new Country { FullName = "United States", Code = "US" }
164+
Country = new Country { FullName = "United States", Code = "US" },
165+
Tags = new List<string> { "foo", "bar" }
158166
};
159167

160168
return AssertQuery(
161169
async,
162-
ss => ss.Set<Customer>().Where(c => c.ShippingAddress == address));
170+
ss => ss.Set<Customer>().Where(c => c.ShippingAddress == address),
171+
ss => ss.Set<Customer>().Where(
172+
c =>
173+
c.ShippingAddress.AddressLine1 == "804 S. Lakeshore Road"
174+
&& c.ShippingAddress.ZipCode == 38654
175+
&& c.ShippingAddress.Country == new Country { FullName = "United States", Code = "US" }
176+
&& c.ShippingAddress.Tags.SequenceEqual(new List<string> { "foo", "bar" })));
163177
}
164178

165179
[ConditionalTheory]
@@ -194,13 +208,21 @@ public virtual Task Contains_over_complex_type(bool async)
194208
{
195209
AddressLine1 = "804 S. Lakeshore Road",
196210
ZipCode = 38654,
197-
Country = new Country { FullName = "United States", Code = "US" }
211+
Country = new Country { FullName = "United States", Code = "US" },
212+
Tags = new List<string> { "foo", "bar" }
198213
};
199214

200215
return AssertQuery(
201216
async,
202217
ss => ss.Set<Customer>().Where(
203-
c => ss.Set<Customer>().Select(c => c.ShippingAddress).Contains(address)));
218+
c => ss.Set<Customer>().Select(c => c.ShippingAddress).Contains(address)),
219+
ss => ss.Set<Customer>().Where(
220+
c => ss.Set<Customer>().Select(c => c.ShippingAddress).Any(
221+
a =>
222+
a.AddressLine1 == "804 S. Lakeshore Road"
223+
&& a.ZipCode == 38654
224+
&& a.Country == new Country { FullName = "United States", Code = "US" }
225+
&& a.Tags.SequenceEqual(new List<string> { "foo", "bar" }))));
204226
}
205227

206228
[ConditionalTheory]

test/EFCore.Specification.Tests/TestModels/ComplexTypeModel/ComplexTypeData.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ private static IReadOnlyList<Customer> CreateCustomers()
5252
{
5353
AddressLine1 = "804 S. Lakeshore Road",
5454
ZipCode = 38654,
55-
Country = new Country { FullName = "United States", Code = "US" }
55+
Country = new Country { FullName = "United States", Code = "US" },
56+
Tags = new List<string> { "foo", "bar" }
5657
};
5758

5859
var customer1 = new Customer
@@ -71,21 +72,24 @@ private static IReadOnlyList<Customer> CreateCustomers()
7172
{
7273
AddressLine1 = "72 Hickory Rd.",
7374
ZipCode = 07728,
74-
Country = new Country { FullName = "Germany", Code = "DE" }
75+
Country = new Country { FullName = "Germany", Code = "DE" },
76+
Tags = new List<string> { "baz" }
7577
},
7678
BillingAddress = new Address
7779
{
7880
AddressLine1 = "79 Main St.",
7981
ZipCode = 29293,
80-
Country = new Country { FullName = "Germany", Code = "DE" }
82+
Country = new Country { FullName = "Germany", Code = "DE" },
83+
Tags = new List<string> { "a1", "a2", "a3" }
8184
}
8285
};
8386

8487
var address3 = new Address
8588
{
8689
AddressLine1 = "79 Main St.",
8790
ZipCode = 29293,
88-
Country = new Country { FullName = "Germany", Code = "DE" }
91+
Country = new Country { FullName = "Germany", Code = "DE" },
92+
Tags = new List<string> { "foo", "moo" }
8993
};
9094

9195
var customer3 = new Customer

test/EFCore.Specification.Tests/TestModels/ComplexTypeModel/Model.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ public record Address
1919
public required string AddressLine1 { get; set; }
2020
public string? AddressLine2 { get; set; }
2121
public int ZipCode { get; set; }
22+
public List<string> Tags { get; set; } = new();
2223

2324
public required Country Country { get; set; }
2425
}

test/EFCore.SqlServer.FunctionalTests/BulkUpdates/ComplexTypeBulkUpdatesSqlServerTest.cs

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,134 @@ public override async Task Update_projected_complex_type_via_OrderBy_Skip_throws
103103
AssertExecuteUpdateSql();
104104
}
105105

106+
public override async Task Update_complex_type_to_parameter(bool async)
107+
{
108+
await base.Update_complex_type_to_parameter(async);
109+
110+
AssertExecuteUpdateSql(
111+
"""
112+
@__complex_type_newAddress_0_AddressLine1='New AddressLine1' (Size = 4000)
113+
@__complex_type_newAddress_0_AddressLine2='New AddressLine2' (Size = 4000)
114+
@__complex_type_newAddress_0_Tags='["new_tag1","new_tag2"]' (Size = 4000)
115+
@__complex_type_newAddress_0_ZipCode='99999' (Nullable = true)
116+
@__complex_type_newAddress_0_Code='FR' (Size = 4000)
117+
@__complex_type_newAddress_0_FullName='France' (Size = 4000)
118+
119+
UPDATE [c]
120+
SET [c].[ShippingAddress_AddressLine1] = @__complex_type_newAddress_0_AddressLine1,
121+
[c].[ShippingAddress_AddressLine2] = @__complex_type_newAddress_0_AddressLine2,
122+
[c].[ShippingAddress_Tags] = @__complex_type_newAddress_0_Tags,
123+
[c].[ShippingAddress_ZipCode] = @__complex_type_newAddress_0_ZipCode,
124+
[c].[ShippingAddress_Country_Code] = @__complex_type_newAddress_0_Code,
125+
[c].[ShippingAddress_Country_FullName] = @__complex_type_newAddress_0_FullName
126+
FROM [Customer] AS [c]
127+
""");
128+
}
129+
130+
public override async Task Update_nested_complex_type_to_parameter(bool async)
131+
{
132+
await base.Update_nested_complex_type_to_parameter(async);
133+
134+
AssertExecuteUpdateSql(
135+
"""
136+
@__complex_type_newCountry_0_Code='FR' (Size = 4000)
137+
@__complex_type_newCountry_0_FullName='France' (Size = 4000)
138+
139+
UPDATE [c]
140+
SET [c].[ShippingAddress_Country_Code] = @__complex_type_newCountry_0_Code,
141+
[c].[ShippingAddress_Country_FullName] = @__complex_type_newCountry_0_FullName
142+
FROM [Customer] AS [c]
143+
""");
144+
}
145+
146+
public override async Task Update_complex_type_to_another_database_complex_type(bool async)
147+
{
148+
await base.Update_complex_type_to_another_database_complex_type(async);
149+
150+
AssertExecuteUpdateSql(
151+
"""
152+
UPDATE [c]
153+
SET [c].[ShippingAddress_AddressLine1] = [c].[BillingAddress_AddressLine1],
154+
[c].[ShippingAddress_AddressLine2] = [c].[BillingAddress_AddressLine2],
155+
[c].[ShippingAddress_Tags] = [c].[BillingAddress_Tags],
156+
[c].[ShippingAddress_ZipCode] = [c].[BillingAddress_ZipCode],
157+
[c].[ShippingAddress_Country_Code] = [c].[ShippingAddress_Country_Code],
158+
[c].[ShippingAddress_Country_FullName] = [c].[ShippingAddress_Country_FullName]
159+
FROM [Customer] AS [c]
160+
""");
161+
}
162+
163+
public override async Task Update_complex_type_to_inline_without_lambda(bool async)
164+
{
165+
await base.Update_complex_type_to_inline_without_lambda(async);
166+
167+
AssertExecuteUpdateSql(
168+
"""
169+
UPDATE [c]
170+
SET [c].[ShippingAddress_AddressLine1] = N'New AddressLine1',
171+
[c].[ShippingAddress_AddressLine2] = N'New AddressLine2',
172+
[c].[ShippingAddress_Tags] = N'["new_tag1","new_tag2"]',
173+
[c].[ShippingAddress_ZipCode] = 99999,
174+
[c].[ShippingAddress_Country_Code] = N'FR',
175+
[c].[ShippingAddress_Country_FullName] = N'France'
176+
FROM [Customer] AS [c]
177+
""");
178+
}
179+
180+
public override async Task Update_complex_type_to_inline_with_lambda(bool async)
181+
{
182+
await base.Update_complex_type_to_inline_with_lambda(async);
183+
184+
AssertExecuteUpdateSql(
185+
"""
186+
UPDATE [c]
187+
SET [c].[ShippingAddress_AddressLine1] = N'New AddressLine1',
188+
[c].[ShippingAddress_AddressLine2] = N'New AddressLine2',
189+
[c].[ShippingAddress_Tags] = N'["new_tag1","new_tag2"]',
190+
[c].[ShippingAddress_ZipCode] = 99999,
191+
[c].[ShippingAddress_Country_Code] = N'FR',
192+
[c].[ShippingAddress_Country_FullName] = N'France'
193+
FROM [Customer] AS [c]
194+
""");
195+
}
196+
197+
public override async Task Update_complex_type_to_another_database_complex_type_with_subquery(bool async)
198+
{
199+
await base.Update_complex_type_to_another_database_complex_type_with_subquery(async);
200+
201+
AssertExecuteUpdateSql(
202+
"""
203+
@__p_0='1'
204+
205+
UPDATE [c]
206+
SET [c].[ShippingAddress_AddressLine1] = [t].[BillingAddress_AddressLine1],
207+
[c].[ShippingAddress_AddressLine2] = [t].[BillingAddress_AddressLine2],
208+
[c].[ShippingAddress_Tags] = [t].[BillingAddress_Tags],
209+
[c].[ShippingAddress_ZipCode] = [t].[BillingAddress_ZipCode],
210+
[c].[ShippingAddress_Country_Code] = [t].[ShippingAddress_Country_Code],
211+
[c].[ShippingAddress_Country_FullName] = [t].[ShippingAddress_Country_FullName]
212+
FROM [Customer] AS [c]
213+
INNER JOIN (
214+
SELECT [c0].[Id], [c0].[Name], [c0].[BillingAddress_AddressLine1], [c0].[BillingAddress_AddressLine2], [c0].[BillingAddress_Tags], [c0].[BillingAddress_ZipCode], [c0].[BillingAddress_Country_Code], [c0].[BillingAddress_Country_FullName], [c0].[ShippingAddress_AddressLine1], [c0].[ShippingAddress_AddressLine2], [c0].[ShippingAddress_Tags], [c0].[ShippingAddress_ZipCode], [c0].[ShippingAddress_Country_Code], [c0].[ShippingAddress_Country_FullName]
215+
FROM [Customer] AS [c0]
216+
ORDER BY [c0].[Id]
217+
OFFSET @__p_0 ROWS
218+
) AS [t] ON [c].[Id] = [t].[Id]
219+
""");
220+
}
221+
222+
public override async Task Update_collection_inside_complex_type(bool async)
223+
{
224+
await base.Update_collection_inside_complex_type(async);
225+
226+
AssertExecuteUpdateSql(
227+
"""
228+
UPDATE [c]
229+
SET [c].[ShippingAddress_Tags] = N'["new_tag1","new_tag2"]'
230+
FROM [Customer] AS [c]
231+
""");
232+
}
233+
106234
[ConditionalFact]
107235
public virtual void Check_all_tests_overridden()
108236
=> TestHelpers.AssertAllMethodsOverridden(GetType());

0 commit comments

Comments
 (0)