Conversation
Task.Run 내부에 예외 처리 추가로 관찰되지 않은 태스크 예외 방지 ConfigureAwait(false) 적용으로 컨텍스트 스위칭 최적화
중복된 예외 처리 제거 및 코드 정리 LogChatRequestCommand, LogChatProcessContext 메서드 삭제 메트릭스 서비스에서 이미 로깅 제공으로 중복 방지
|
Caution Review failedThe pull request is closed. Walkthrough여러 서비스와 인프라 파일에서 내부 로깅 호출 제거 및 비동기 await 관련 ConfigureAwait 제거(동기화 컨텍스트 캡처 변경), WebSocket 미들웨어의 멀티프레임 메시지 조립·핑/퐁 핸들링·바이너리 프레임 처리 추가, 토큰 저장 실패 시 예외 처리와 채팅 요청 토큰 잔액 검증이 도입되었습니다. 공개 API 서명은 변경되지 않았습니다. Changes
Sequence Diagram(s)sequenceDiagram
%% WebSocket: 멀티프레임 수집 + ping/pong 처리 (간략)
autonumber
actor Client
participant WS as WebSocketMiddleware
participant App as ApplicationHandler
Client->>WS: send frame(chunk1)
WS->>WS: append to MemoryStream (not EndOfMessage)
Client->>WS: send frame(chunkN, EndOfMessage)
WS->>WS: assemble full message
alt message == "ping" or contains {"type":"ping"}
WS->>Client: send {"type":"pong"}
else message is text
WS->>App: Invoke handler with full text message
App-->>WS: response / ack
else message is binary
WS->>WS: log and skip processing
end
Client->>WS: Close frame
WS->>Client: CloseAsync (cleanup)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (7)
ProjectVG.Application/Services/Chat/ChatService.cs (7)
74-75: Task.Run 백그라운드 작업: 예외 관찰 및 취소 불가fire-and-forget로 반환 Task를 버리면 초기 예외(예: 스코프 생성 전) 로깅 누락 가능, 취소도 불가. 최소한 Task를 캡처해 Faulted 시 로깅하세요. 장기적으로는 IBackgroundTaskQueue/HostedService 패턴 전환 권장.
적용 예시:
- _ = Task.Run(async () => { - await ProcessChatRequestInternalAsync(preprocessContext).ConfigureAwait(false); - }); + var task = Task.Run(() => ProcessChatRequestInternalAsync(preprocessContext)); + _ = task.ContinueWith( + t => _logger.LogError(t.Exception, "비동기 채팅 처리 실패: 요청 {RequestId}", command.Id), + TaskContinuationOptions.OnlyOnFaulted);
110-112: ASP.NET Core에서는 ConfigureAwait(false) 실익 제한적 — 스타일 일관성 필요ASP.NET Core는 SynchronizationContext가 없어 체감 이득이 거의 없습니다. 유지할지 제거할지 팀 규칙을 정하고 일관 적용하세요(가독성 vs. 규칙).
가독성 선호 시:
- await _llmProcessor.ProcessAsync(context).ConfigureAwait(false); - await _ttsProcessor.ProcessAsync(context).ConfigureAwait(false); + await _llmProcessor.ProcessAsync(context); + await _ttsProcessor.ProcessAsync(context);
116-118: 전송(Handle) → 영속화(Persist) 순서 검토저장 실패 시 이미 사용자에게 결과가 송신되어 불일치가 발생할 수 있습니다. 비즈니스 의도라면 유지, 아니라면 저장 선행 또는 저장 실패시 보정/리트라이 정책을 고려하세요.
121-121: Failure 경로의 ConfigureAwait(false) 및 예외 처리 의도 확인FailureHandler 내부에서 예외를 로깅만 하고 흡수합니다. 장애 추적/알림 관점에서 충분한지 확인 바랍니다. ConfigureAwait(false) 역시 스타일 일관성 원칙에 맞춰 정리 권장.
29-31: 미사용 필드/생성자 인자 정리: _resultProcessor, _chatFailureHandler메서드 내에서 스코프를 통해 핸들러/프로세서를 해석하므로, 이 필드/인자들이 사용되지 않습니다. 혼선을 줄이기 위해 제거를 권장합니다.
예시:
- private readonly ChatResultProcessor _resultProcessor; - private readonly ChatFailureHandler _chatFailureHandler; ... - ICostTrackingDecorator<ChatTTSProcessor> ttsProcessor, - ChatResultProcessor resultProcessor, + ICostTrackingDecorator<ChatTTSProcessor> ttsProcessor, ... - ChatFailureHandler chatFailureHandler + /* removed: ChatFailureHandler chatFailureHandler */ ) { ... - _resultProcessor = resultProcessor; - _chatFailureHandler = chatFailureHandler; + /* removed unused assignments */Also applies to: 45-46, 61-63
108-115: 스코프 혼용 확인성공/결과/실패 핸들러는 새 scope에서, LLM/TTS 프로세서는 기존 주입 인스턴스를 사용합니다. 서비스 라이프타임(Scoped/Singleton) 정책과 일치하는지 확인해 주세요. 필요 시 전부 동일 scope에서 해석하도록 정렬 권장.
67-72: 검증/전처리 단계 예외 시 메트릭 종료 누락 가능Start는 Enqueue에서, End/Log는 내부 처리 finally에서 호출됩니다. Validate/Prepare에서 예외가 나면 End/Log가 실행되지 않을 수 있습니다. 가드 로직을 추가하거나, 메트릭 수명 관리를 한 곳으로 모으는 것을 권장합니다.
Also applies to: 124-126
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
ProjectVG.Application/Services/Chat/ChatService.cs(2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
ProjectVG.Application/Services/Chat/ChatService.cs (3)
ProjectVG.Application/Services/Chat/Handlers/ChatSuccessHandler.cs (2)
ChatSuccessHandler(9-122)ChatSuccessHandler(15-23)ProjectVG.Application/Services/Chat/Processors/ChatResultProcessor.cs (2)
ChatResultProcessor(10-100)ChatResultProcessor(17-27)ProjectVG.Application/Services/Chat/Handlers/ChatFailureHandler.cs (2)
ChatFailureHandler(7-30)ChatFailureHandler(12-18)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Build & Test
불필요한 ConfigureAwait(false) 호출 제거로 코드 가독성 개선 - WebSocketMiddleware.cs - AuthService.cs - ChatService.cs - ChatRequestValidator.cs - TokenService.cs - SqlServerUserRepository.cs ASP.NET Core에서는 SynchronizationContext가 없어 ConfigureAwait(false)의 이점이 제한적 코드 일관성 및 가독성 향상을 위해 제거
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
ProjectVG.Application/Services/Chat/Validators/ChatRequestValidator.cs (1)
79-97: 세션 스토리지 오류 시 ‘검증 통과(fail-open)’는 보안 리스크스토리지 장애가 곧바로 인증/세션 우회를 허용할 수 있습니다. 기본은 실패-폐쇄(fail-closed)로 전환하고, 필요 시 플래그/설정으로만 fail-open 허용을 권장합니다.
대안 A(권장, 기본 fail-closed):
- catch (Exception ex) { - _logger.LogError(ex, "세션 검증 중 예상치 못한 오류: {UserId}", userId); - // 세션 스토리지 오류 시에는 검증을 통과시키되 로그는 남김 (서비스 가용성 우선) - _logger.LogWarning("세션 스토리지 오류로 인해 세션 검증을 건너뜁니다: {UserId}", userId); - } + catch (Exception ex) { + _logger.LogError(ex, "세션 검증 중 오류: {UserId}", userId); + throw new ValidationException(ErrorCode.SESSION_EXPIRED, "세션 검증 실패. 다시 로그인해 주세요."); + }대안 B(플래그로 제어):
- IConfiguration/IOptions에
AllowFailOpenOnSessionError추가(기본 false).- catch 블록에서 플래그가 true일 때만 현재 동작 유지.
ProjectVG.Api/Middleware/WebSocketMiddleware.cs (1)
130-151: 대용량 메시지 무제한 수신으로 인한 메모리 DoS 가능성프레임 결합 시 상한이 없어 MemoryStream이 무제한 성장할 수 있습니다. 합리적 상한(예: 1–4MB)으로 차단 권장.
+ // 메시지 최대 크기(바이트) - 필요시 설정화 + private const int MaxMessageBytes = 1 * 1024 * 1024; ... while (socket.State == WebSocketState.Open && !cancellationTokenSource.Token.IsCancellationRequested) { WebSocketReceiveResult result; using var ms = new MemoryStream(); do { - result = await socket.ReceiveAsync(new ArraySegment<byte>(buffer), cancellationTokenSource.Token); + result = await socket.ReceiveAsync(new ArraySegment<byte>(buffer), cancellationTokenSource.Token); if (result.MessageType == WebSocketMessageType.Close) { _logger.LogInformation("연결 종료 요청: {UserId}", userId); break; } - ms.Write(buffer, 0, result.Count); + ms.Write(buffer, 0, result.Count); + if (ms.Length > MaxMessageBytes) { + _logger.LogWarning("메시지 크기 초과로 연결 종료: {UserId}, Size={Size}", userId, ms.Length); + await socket.CloseAsync(WebSocketCloseStatus.MessageTooBig, "Message too large", cancellationTokenSource.Token); + return; + } } while (!result.EndOfMessage);
🧹 Nitpick comments (5)
ProjectVG.Application/Services/Chat/Validators/ChatRequestValidator.cs (1)
52-67: 잔액 체크 메시지/분기 단순화 제안중복 예외 메시지 생성을 줄일 수 있습니다. 동작 동일.
- if (currentBalance <= 0) { - _logger.LogWarning("토큰 잔액 부족 (0 토큰): UserId={UserId}", command.UserId); - throw new ValidationException(ErrorCode.INSUFFICIENT_CREDIT_BALANCE, $"토큰이 부족합니다. 현재 잔액: {currentBalance} 토큰, 필요 토큰: {ESTIMATED_CHAT_COST} 토큰"); - } - - var hasSufficientTokens = currentBalance >= ESTIMATED_CHAT_COST; - if (!hasSufficientTokens) { + var hasSufficientTokens = currentBalance >= ESTIMATED_CHAT_COST; + if (!hasSufficientTokens) { _logger.LogWarning("토큰 부족: UserId={UserId}, 현재잔액={CurrentBalance}, 필요토큰={RequiredTokens}", command.UserId, currentBalance, ESTIMATED_CHAT_COST); throw new ValidationException(ErrorCode.INSUFFICIENT_CREDIT_BALANCE, $"토큰이 부족합니다. 현재 잔액: {currentBalance} 토큰, 필요 토큰: {ESTIMATED_CHAT_COST} 토큰"); }ProjectVG.Api/Middleware/WebSocketMiddleware.cs (2)
111-116: RequestAborted와 링크된 취소 토큰 사용 권장연결이 끊긴 HTTP 요청 조기 취소를 반영하려면 RequestAborted와 링크하세요.
- var cancellationTokenSource = new CancellationTokenSource(); + using var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource( + // HttpContext=RequestAborted 전달 필요: 메서드 시그니처 변경 또는 필드 보관 + CancellationToken.None + ); // Set a reasonable timeout for WebSocket operations cancellationTokenSource.CancelAfter(TimeSpan.FromMinutes(30));메서드 시그니처를
RunSessionLoop(WebSocket socket, string userId, CancellationToken requestAborted)로 변경해context.RequestAborted를 전달하는 것을 권장합니다.
151-163: pong 메시지 인코딩 재사용으로 GC 압력 감소(마이크로 최적화)상수 바이트 배열을 캐시하면 할당을 줄일 수 있습니다.
- var pongMessage = System.Text.Encoding.UTF8.GetBytes("{\"type\":\"pong\"}"); + // 클래스 정적 필드로 캐시: private static readonly byte[] PongPayload = Encoding.UTF8.GetBytes("{\"type\":\"pong\"}"); + var pongMessage = PongPayload;ProjectVG.Application/Services/Auth/AuthService.cs (1)
94-99: LogoutAsync에서 사용되지 않는 userId 조회 제거의미 없는 호출입니다. 제거로 불필요 I/O 감소.
var revoked = await _tokenService.RevokeRefreshTokenAsync(refreshToken); - if (revoked) { - var userId = await _tokenService.GetUserIdFromTokenAsync(refreshToken); - } return revoked;ProjectVG.Infrastructure/Persistence/Repositories/User/SqlServerUserRepository.cs (1)
24-28: CancellationToken 전파 도입 권장 — 비동기 DB 호출에 토큰 전달(선택 매개변수, 기본값 포함)EF Core의 ToListAsync / FirstOrDefaultAsync / SaveChangesAsync 등 비동기 DB 호출은 취소 가능하므로, 레포 전반의 리포지토리 인터페이스에 선택적 CancellationToken 매개변수를 추가하고 내부 호출에 전달하세요(다음 PR에서 적용 권장). 검증 결과 ProjectVG.Infrastructure/Persistence/Repositories의 Credit, Conversation, Character, User 리포지토리에서 관련 호출이 확인되었습니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
ProjectVG.Api/Middleware/WebSocketMiddleware.cs(3 hunks)ProjectVG.Application/Services/Auth/AuthService.cs(4 hunks)ProjectVG.Application/Services/Chat/ChatService.cs(1 hunks)ProjectVG.Application/Services/Chat/Validators/ChatRequestValidator.cs(2 hunks)ProjectVG.Infrastructure/Auth/TokenService.cs(5 hunks)ProjectVG.Infrastructure/Persistence/Repositories/User/SqlServerUserRepository.cs(3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- ProjectVG.Application/Services/Chat/ChatService.cs
🧰 Additional context used
🧬 Code graph analysis (3)
ProjectVG.Application/Services/Auth/AuthService.cs (2)
ProjectVG.Infrastructure/Auth/TokenService.cs (6)
Task(19-41)Task(43-92)Task(94-97)Task(99-114)Task(116-126)Task(128-136)ProjectVG.Application/Services/Auth/IUserAuthService.cs (1)
AuthResult(39-46)
ProjectVG.Infrastructure/Persistence/Repositories/User/SqlServerUserRepository.cs (3)
ProjectVG.Application/Services/User/UserService.cs (6)
Task(19-43)Task(45-56)Task(58-62)Task(64-68)Task(70-74)Task(76-80)ProjectVG.Common/Exceptions/NotFoundException.cs (5)
NotFoundException(3-24)NotFoundException(5-8)NotFoundException(10-13)NotFoundException(15-18)NotFoundException(20-23)ProjectVG.Domain/Common/BaseEntity.cs (1)
Update(16-19)
ProjectVG.Api/Middleware/WebSocketMiddleware.cs (2)
ProjectVG.Application/Services/Session/ConnectionRegistry.cs (1)
Unregister(28-38)ProjectVG.Application/Services/Session/IConnectionRegistry.cs (1)
Unregister(16-16)
🔇 Additional comments (5)
ProjectVG.Infrastructure/Persistence/Repositories/User/SqlServerUserRepository.cs (1)
27-27: ASP.NET Core에서 ConfigureAwait 제거는 합리적입니다동기화 컨텍스트가 없으므로 오버헤드 제거 측면에서 👍
Also applies to: 32-32, 37-37, 42-42, 47-47, 52-52, 57-57, 68-68, 75-75, 88-88, 95-95, 103-103
ProjectVG.Application/Services/Chat/Validators/ChatRequestValidator.cs (1)
38-39: ConfigureAwait 제거 변경은 무해하며 일관성 좋습니다검증 흐름에 영향 없고 가독성 향상입니다.
Also applies to: 40-45, 46-51, 53-59, 61-67, 79-81
ProjectVG.Application/Services/Auth/AuthService.cs (2)
33-49: ConfigureAwait 제거 일관성 👍컨트롤 플로우 변화 없음. 유지보수성 향상.
Also applies to: 55-61, 74-81, 94-97
59-61: 예외 매핑 확인 필요 — GenerateTokensAsync가 InvalidOperationException을 던집니다
- 요약: TokenService.GenerateTokensAsync가 저장 실패 시 InvalidOperationException을 던지는 것으로 확인됨(증거: ProjectVG.Tests/Auth/TokenServiceTests.cs, 구현: ProjectVG.Infrastructure/Auth/TokenService.cs).
- 영향: 호출 지점 — ProjectVG.Application/Services/Auth/AuthService.cs (약 line 60), ProjectVG.Application/Services/Auth/OAuth2AuthService.cs (약 line 73).
- 현 상태: 현재 검색 결과로는 앱 수준 예외 처리(UseExceptionHandler / ExceptionFilter 등)를 찾지 못함. 전역 예외 미들웨어(Program.cs / Startup.cs 또는 커스텀 미들웨어)에서 InvalidOperationException이 5xx/도메인 에러(ProblemDetails 포함)로 매핑되는지 확인 필요.
- 검증용 명령(로컬 실행): rg -nP -C3 'app.UseExceptionHandler|UseExceptionHandler(|ExceptionHandlerMiddleware|ExceptionFilter|AddProblemDetails|UseProblemDetails|Program.cs|Startup.cs' --type=cs
ProjectVG.Infrastructure/Auth/TokenService.cs (1)
27-33: 로그인/회원가입 흐름에서 GenerateTokensAsync 예외 전파 영향 재확인 필요GenerateTokensAsync가 리프레시 토큰 저장 실패 시 InvalidOperationException을 던지므로 상위 계층에서 이 예외를 포착해 적절한 API 응답(오류 코드/메시지 변환 또는 롤백)을 반환하는지 확인하세요.
- 확인 지점: ProjectVG.Application/Services/Auth/AuthService.cs (라인 60).
- 확인 지점: ProjectVG.Application/Services/Auth/OAuth2AuthService.cs (라인 73).
- 참고: ProjectVG.Tests/Auth/TokenServiceTests.cs (라인 72-74)는 이미 InvalidOperationException을 기대함.
- 참고: ProjectVG.Tests/Auth/AuthServiceTests.cs (라인 66, 112 등)는 GenerateTokensAsync를 성공으로 모킹함 — 예외 시나리오 테스트 추가 권장.
| if (socket.State == WebSocketState.Open || socket.State == WebSocketState.CloseReceived) { | ||
| await socket.CloseAsync( | ||
| WebSocketCloseStatus.NormalClosure, | ||
| "Connection closed", | ||
| CancellationToken.None).ConfigureAwait(false); | ||
| CancellationToken.None); | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
CloseAsync에 무제한 CancellationToken.None 사용 지양
네트워크 이슈 시 무기한 블로킹될 수 있습니다. 짧은 타임아웃을 가진 CTS를 사용하세요.
- await socket.CloseAsync(
+ using var closeCts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
+ await socket.CloseAsync(
WebSocketCloseStatus.NormalClosure,
"Connection closed",
- CancellationToken.None);
+ closeCts.Token);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (socket.State == WebSocketState.Open || socket.State == WebSocketState.CloseReceived) { | |
| await socket.CloseAsync( | |
| WebSocketCloseStatus.NormalClosure, | |
| "Connection closed", | |
| CancellationToken.None).ConfigureAwait(false); | |
| CancellationToken.None); | |
| } | |
| if (socket.State == WebSocketState.Open || socket.State == WebSocketState.CloseReceived) { | |
| using var closeCts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); | |
| await socket.CloseAsync( | |
| WebSocketCloseStatus.NormalClosure, | |
| "Connection closed", | |
| closeCts.Token); | |
| } |
🤖 Prompt for AI Agents
In ProjectVG.Api/Middleware/WebSocketMiddleware.cs around lines 183 to 188, the
call to socket.CloseAsync uses CancellationToken.None which can block
indefinitely on network issues; replace it with a CancellationToken from a
short-lived CancellationTokenSource (e.g., a few seconds) to bound the wait.
Create a CTS with a sensible timeout, pass cts.Token into CloseAsync, and ensure
the CTS is disposed (use a using or try/finally) so the token cancels after the
timeout to avoid hanging on network failures.
| var accessTokenExpiresAt = DateTime.UtcNow.AddMinutes(15); | ||
| var refreshTokenExpiresAt = DateTime.UtcNow.AddMinutes(1440); | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion
토큰 만료 시간 하드코딩 → 설정화 권장
운영 정책 변경에 유연하게 대응할 수 있도록 IOptions 패턴으로 구성값화하세요.
예: TokenOptions { AccessTokenMinutes, RefreshTokenMinutes }를 DI로 주입.
🤖 Prompt for AI Agents
In ProjectVG.Infrastructure/Auth/TokenService.cs around lines 24 to 26, the
access and refresh token lifetimes are hardcoded; change this to use
configurable options injected via IOptions<TokenOptions>. Add a TokenOptions
class with AccessTokenMinutes and RefreshTokenMinutes, inject
IOptions<TokenOptions> into the TokenService constructor, and replace
DateTime.UtcNow.AddMinutes(15) / AddMinutes(1440) with values from
options.Value.AccessTokenMinutes and options.Value.RefreshTokenMinutes; also
ensure TokenOptions is bound in DI (e.g.,
services.Configure<TokenOptions>(configuration.GetSection("TokenOptions"))) and
consider validating the values before use.
| return await _context.Users | ||
| .Where(u => u.Status == AccountStatus.Active) | ||
| .OrderBy(u => u.Username) | ||
| .ToListAsync().ConfigureAwait(false); | ||
| .ToListAsync(); | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
읽기 전용 쿼리에 AsNoTracking 적용으로 트래킹 오버헤드 제거 제안
레포 메서드 다수가 조회 전용입니다. EF Core 트래킹 비용 절감을 위해 AsNoTracking 추가를 권장합니다. Update/Delete는 내부에서 별도 조회로 트래킹 엔티티를 확보하므로 안전합니다.
return await _context.Users
- .Where(u => u.Status == AccountStatus.Active)
+ .AsNoTracking()
+ .Where(u => u.Status == AccountStatus.Active)
.OrderBy(u => u.Username)
.ToListAsync();
-return await _context.Users.FirstOrDefaultAsync(u => u.Id == id && u.Status != AccountStatus.Deleted);
+return await _context.Users
+ .AsNoTracking()
+ .FirstOrDefaultAsync(u => u.Id == id && u.Status != AccountStatus.Deleted);
-return await _context.Users.FirstOrDefaultAsync(u => u.Username == username && u.Status != AccountStatus.Deleted);
+return await _context.Users
+ .AsNoTracking()
+ .FirstOrDefaultAsync(u => u.Username == username && u.Status != AccountStatus.Deleted);
-return await _context.Users.FirstOrDefaultAsync(u => u.Email == email && u.Status != AccountStatus.Deleted);
+return await _context.Users
+ .AsNoTracking()
+ .FirstOrDefaultAsync(u => u.Email == email && u.Status != AccountStatus.Deleted);
-return await _context.Users.FirstOrDefaultAsync(u => u.ProviderId == providerId && u.Status != AccountStatus.Deleted);
+return await _context.Users
+ .AsNoTracking()
+ .FirstOrDefaultAsync(u => u.ProviderId == providerId && u.Status != AccountStatus.Deleted);
-return await _context.Users.FirstOrDefaultAsync(u => u.Provider == provider && u.ProviderId == providerId && u.Status != AccountStatus.Deleted);
+return await _context.Users
+ .AsNoTracking()
+ .FirstOrDefaultAsync(u => u.Provider == provider && u.ProviderId == providerId && u.Status != AccountStatus.Deleted);
-return await _context.Users.FirstOrDefaultAsync(u => u.UID == uid && u.Status != AccountStatus.Deleted);
+return await _context.Users
+ .AsNoTracking()
+ .FirstOrDefaultAsync(u => u.UID == uid && u.Status != AccountStatus.Deleted);Also applies to: 32-33, 37-38, 42-43, 47-48, 52-53, 57-58
Summary by CodeRabbit
리팩터링
개선사항
버그 수정
작업(Chores)