ExecuteUpdate over projection uses derived-table copy of updated column, causing possible lost updates
Los mantenedores suelen responder en 2 días
Nadie ha tomado este issue todavía.
Evaluación
- Dificultad
- 4/5
- Tiempo estimado
- 3-5 días
- Aptitud para principiantes
- 52/100
Línea de trabajo
Comience reproduciendo la consulta LINQ ExecuteUpdateAsync proporcionada e inspeccionando el SQL de PostgreSQL generado. Siga la ruta de traducción de ExecuteUpdate para determinar por qué la tabla derivada proporciona la columna actualizada; se considera completado cuando la expresión SET hace referencia al alias del destino de actualización y los incrementos concurrentes conservan el total combinado.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
Bug description
ExecuteUpdate over a projection can generate SQL where an increment expression uses a value read into a derived table instead of referencing the actual update target alias.
This can cause lost updates under concurrent executions.
The LINQ expression represents an atomic increment:
target.TotalQuantity = target.TotalQuantity + delta
But the generated SQL uses:
SET "TotalQuantity" = s2."TotalQuantity" + s2."QuantityDelta"
where s2."TotalQuantity" is read inside a derived table before the update.
For concurrent counter updates, the generated SQL should reference the update target alias:
SET "TotalQuantity" = s1."TotalQuantity" + s2."QuantityDelta"
because s1 is the actual row being updated.
Example lost-update scenario:
Initial TotalQuantity = 100
Transaction 1 delta = 10
Transaction 2 delta = 20
Both transactions can read s2.TotalQuantity = 100.
Transaction 1 updates row to 110.
Transaction 2 waits for the same row, then updates row to 100 + 20 = 120.
Expected final value: 130.
Possible final value: 120.
Your code
await context.ShortageOrderLines
.Where(line => line.ShortageOrderId == orderId)
.GroupBy(line => line.ShortageArticleTask!)
.Select(g => new
{
Task = g.Key,
QuantityDelta = g.Sum(l => l.Quantity)
})
.ExecuteUpdateAsync(setters => setters.SetProperty(
t => t.Task.TotalQuantity,
t => t.Task.TotalQuantity + t.QuantityDelta));
Generated SQL:
UPDATE shortage."ShortageArticleTasks" AS s1
SET "TotalQuantity" = s2."TotalQuantity" + s2."QuantityDelta"
FROM (
SELECT s0."Id", s0."TotalQuantity", COALESCE(sum(s."Quantity"), 0.0) AS "QuantityDelta"
FROM shortage."ShortageOrderLines" AS s
LEFT JOIN shortage."ShortageArticleTasks" AS s0 ON s."ShortageArticleTaskId" = s0."Id"
WHERE s."ShortageOrderId" = @orderId
GROUP BY s0."Id", s0."ArticleId", s0."LogisticVariantId", s0."TotalQuantity"
) AS s2
WHERE s1."Id" = s2."Id"
Expected SQL shape:
UPDATE shortage."ShortageArticleTasks" AS s1
SET "TotalQuantity" = s1."TotalQuantity" + s2."QuantityDelta"
FROM (
SELECT
s."ShortageArticleTaskId",
COALESCE(sum(s."Quantity"), 0.0) AS "QuantityDelta"
FROM shortage."ShortageOrderLines" AS s
WHERE s."ShortageOrderId" = @orderId
GROUP BY s."ShortageArticleTaskId"
) AS s2
WHERE s1."Id" = s2."ShortageArticleTaskId"
Stack traces
No exception is thrown. The generated SQL executes successfully, but it may have incorrect concurrency semantics for counter increments.
EF Core version
10.0.8
Database provider
Npgsql.EntityFrameworkCore.PostgreSQL
Target framework
net10.0
Operating system
Amazon Linux
IDE
VS 2026
- Lenguaje dominante
- C#
- Estrellas
- 14.8k
- Forks
- 3.4k
- Merge medio
- 3 d 18 h
- PR fusionados (30 d)
- 159
Preparar el entorno
Inicia el contenedor de desarrollo del proyecto en tu navegador, con tu propia cuenta de GitHub.
- Sin Dockerfile ni archivo de Docker Compose
- Tiene una plantilla de pull request
- Leer la guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de dotnet/efcore
-
customer-reported
Dificultad 4/5 3-5 días Aptitud para principiantes 54/100
Los mantenedores suelen responder en 2 días
-
Dificultad 4/5 3-5 días Aptitud para principiantes 48/100
Los mantenedores suelen responder en 2 días
-
Dificultad 4/5 3-5 días Aptitud para principiantes 48/100
Los mantenedores suelen responder en 2 días
-
Regression in 11.0: replacing an owned instance or re-adding an entity with the same key sets unchanged OnAdd properties to nullPosiblemente ocupada @seragAhmed la tomó hoy. Abierto
Dificultad 4/5 3-5 días Aptitud para principiantes 48/100
dotnet/efcore#39617 · 1 comentario ·
Los mantenedores suelen responder en 2 días
-
customer-reported
Dificultad 4/5 3-5 días Aptitud para principiantes 48/100
Los mantenedores suelen responder en 2 días
Todos los issues de dotnet/efcore
Issues similares
-
bug emitter:client:csharp
Dificultad 2/5 1-3 horas Aptitud para principiantes 84/100
microsoft/typespec#12572 · 1 comentario · 1 reacción ·
Los mantenedores suelen responder en 1 día
-
needs-area-label
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
Los mantenedores suelen responder en 2 días
-
AWS Lambda: KeyNotFoundException drops HTTP-triggered transactions when allowAllHeaders is disabledAbiertobug
Dificultad 2/5 1-3 horas Aptitud para principiantes 70/100
newrelic/newrelic-dotnet-agent#3949 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
area-networking
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
dotnet/aspnetcore#70545 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 87/100
Los mantenedores suelen responder en 1 día