C# EF CORE 관련 질문있습니다!

안녕하세요.
현재 EF CORE 공부하고 있습니다.
하나 이해가 되지 않는 부분이 있어 이렇게 글을 작성하게 되었습니다.
현재 제가 원하는 동작은 A 와 B 테이블의 Union입니다.
Union 을 하기 위해 칼럼 개수와 칼럼 타입을 맞추는 작업을 하다가 생긴 일입니다.

간단하게 설명하자면
A Column A001, A002, A003, A004
B Column B001, B002, B003, B004
A는 A001, A002 만 필요하고 B는 B003, B004 만 필요하여 아래 코드처럼 작성하여 작성했습니다.
var aTable = A.Select(s => new {U001 = s.A001, U002 = s.A002 , U003 = 0 , U004 = 0});
var bTable = B.Select(s => new {U001 = 0, U002 = 0 , U003 = s.B003 , U004 = s.B004});
var uTable = aTable.Concat(bTable);

컴파일 상 오류는 없지만 실행을 하게 되면
‘Unable to translate set operation when matching columns on both sides have different store types’
라는 오류가 발생합니다.

그래서 ef core에서 생성되는 쿼리를 살펴보면
SELECT [v].[A001] AS [U001], [v].[A002] AS [U002], 0 AS [U003]
FROM [A] AS [v]

SELECT 0 AS [U001], [v].[B003] AS [U003], [v].[B004] AS [U004]
FROM [B] AS [v]

처럼 쿼리가 생성됩니다.
제가 지정해둔 필드가 쿼리로 생성되지 않는 것을 발견했습니다.
ToList를 이용해서 불러온 뒤 Concat를 하면 잘 작동하지만 서버에서 쿼리로 Union까지 하여 데이터를 받고 싶은데방법이 없을까요?
열심히 찾아보았지 제 검색 능력이 부족한 건지 비슷한 케이스에 질문도 찾지 못해서 도움을 받고자 질문드립니다.
글 읽어 주셔서 감사합니다.

좋아요 2

익명 타입을 사용하지 않고 원하시는 속성을 가진 클래스를 정의한 후 그 클래스로 대체하면 됩니다.

var aTable = A.Select(s => new Info {U001 = s.A001, U002 = s.A002 , U003 = 0 , U004 = 0});
var bTable = B.Select(s => new Info {U001 = 0, U002 = 0 , U003 = s.B003 , U004 = s.B004});
var uTable = aTable.Concat(bTable);


public class Info
{
   public int U001 { get; set; }
   public int U002 { get; set; }
   public int U003 { get; set; }
   public int U004 { get; set; }
}
좋아요 2
public class info
{
  public int U001 { get; set; }
  public int U002 { get; set; }
  public int U003 { get; set; }
  public int U004 { get; set; }
}

B.Select(s => new info{ U001 = 0, U002 = 0 , U003 = s.B003 , U004 = s.B004}).ToQueryString();

SELECT 0 AS [U001], [v].[C003] AS [U003], [v].[B004] AS [B004]
FROM [B] AS [v]

말씀해 주신 방법처럼 클래스를 만들어 실행해 보았지만 (ToQueryString) 처음과 같은 쿼리 결과가 나옵니다.

늦은 시간까지 답변 주셔서 감사합니다.

좋아요 2

저는 아래처럼 테스트를 한 후 답변을 드렸습니다.

var a = c.Users.Where(x => x.UserId == "test").Select(x => new Temp { A = x.UserId, B = x.UserName, C = "A" });
var b = c.Todos.Select(x => new Temp { A = x.Memo, B = x.UserId, C = "B" });
var ab = a.Concat(b);

class Temp
{
    public string A { get; set; }
    public string B { get; set; }
    public string C { get; set; }
}

| 생성 쿼리

SELECT "u"."UserId" AS "A", "u"."UserName" AS "B", 'A' AS "C"
FROM "UserInfo" AS "u"
WHERE "u"."UserId" = 'test'
UNION ALL
SELECT "t0"."Memo" AS "A", "t0"."UserId" AS "B", 'B' AS "C"
FROM "TodoInfo" AS "t0"

원하시는 쿼리와 다를까요?

좋아요 2

어쩌면 버젼의 차이일수도 있겠네요. 저는 EF Core 6.0 으로 테스트 하였습니다.

좋아요 2

A.Select(s => new Info {U001 = s.A001, U002 = s.A002 , U003 = 0 , U004 = 0}).ToQueryString();
SELECT를 요청했을 때
[v].[A001] AS [U001], [v].[A002] AS [U002], 0 AS [U003] , 0 AS [U004] 이 아닌
[v].[A001] AS [U001], [v].[A002] AS [U002], 0 AS [U003] 쿼리가 만들어집니다.
U004 Column이 생성되지 않아 질문드렸습니다!

현재 쿼리:
SELECT [v].[A001] AS [U001], [v].[A002] AS [U002], 0 AS [U003]
FROM [A] AS [v]

원하는 쿼리:
SELECT [v].[A001] AS [U001], [v].[A002] AS [U002], 0 AS [U003] , 0 AS [U004]
FROM [A] AS [v]

현재 저도 EF Core 6.0 사용 중입니다!

좋아요 2

네 그렇네요. 특이한 점은 두번째 쿼리도 U002 가 누락되었네요…

누락된 첫번째 쿼리와 두번째 쿼리의 컬럼 타입이 어떻게 되지요?

좋아요 2

현재 두 필드다 INT형 쓰고 있습니다.
String 필드로 테스트해봤을 때도 똑같이 누락되어서 나왔습니다!

좋아요 2

혹시 재현 가능한 코드를 공유 줄 수 있을까요?

좋아요 2

서버에 연결해서 공부하는 중이라 재현 할 수 있는 코드가 없습니다. 죄송합니다. 제 생각으로 아무 쿼리에 select { ~~~ . A001 = 0 , A002 = 0 } 뒤쪽이나 앞쪽에 고정으로 겹치는 값으로 필드를 생성해 주시면 똑같이 쿼리가 누락되어 나 올 것 같습니다!

좋아요 2

키가 없는 테이블 맞나요?

좋아요 2

제가 컴퓨터가 아니어서 잘 생각나지 않지만 둘 다 A001 B001에 키가 잡혀있을 것 같습니다.

정말 죄송하지만 제가 잠이 너무 와서 다음 답글은 일어난 후 최대한 빨리 드리겠습니다.
늦은 시간 댓글 달아 주시고 제 질문에 같이 고민해 주셔서 감사합니다.

좋아요 3

재현이 되었습니다. 흠… 이거 정상적인 동작이 아닌 것 같은데요, DB는 sqlite고요

패턴은,

값이 0 또는 1등 모두 같으면, 심지어 U001 ~ U004 가 모두 0일 때 U001 만 0 이고 U002 ~ U004는 누락되네요…

그래서 U001 = 0, U002 = 1 이런 식으로 하면 누락되지 않습니다.

좋아요 3

중복되지 않는 값을 넣음 누락되지 않는다는 점은 여러 테스트를 해봐 알고 있었습니다. 하지만 목표 쿼리가 Union => Group By => Sum을 하는 게 목적이기 때문에 0 이 외의 값을 넣기가 어려워 질문 글을 올리게 되었습니다.
또한 개인적인 궁금증으로 왜 중복 값을 넣으면 값이 없어지는지 이유가 궁금하기도 했습니다.

좋아요 3

버그인지 EF Core의 일종의 최적화? 인지 저도 좀 알아볼께요. 만약 버그라면 DB 구현체에 따라 다른 결과가 나올것 같고요 만약 최적화 관련된 것이라면 관련 옵션이 있을 것이라 생각합니다.

좋아요 3

밤늦게부터 지금까지 답변해 주셔서 감사드립니다.
저도 열심히 찾아보겠습니다.
좋은 하루 보내세요.

좋아요 2

확인한 내용 (진행중…)

EF 6 및 EF 7 프리뷰에서 확인했고 sqlite 및 progresql에서 확인했는데 모두 결과가 동일합니다. ㅡ.,ㅡ


최종 결과 값에는 적용한 리터럴 값이 제대로 있는 것으로 보아 버그는 아닌 것 같고 일종의 EF Core의 최적화가 도작한 것 같네요. 아마 관련 옵션을 찾을 수 있으면 될 듯 합니다.

좋아요 2

결론 - 버그아님.

Select에 의해 지정된 리터럴 값의 속성이 쿼리에서 탈락 되는 것은 버그가 아니라 EF Core의 최적화인 것으로 보입니다.

생성된 쿼리와 상관없이 결과에는 제대로 저장됩니다.

그런데 문제는 union all 연산을 할 수 없다는 점인데요. 그래서 다시 돌아와서 @외톨이 님의 관련 질의를 했는데, 제대로 질의되는 것으로 확인됩니다.

var a = c.ATable.Select(x => new Info { U001 = 0, U002 = 0, U003 = x.A003, U004 = x.A004 });
var b = c.BTable.Select(x => new Info { U001 = x.B001, U002 = x.B002, U003 = 0, U004 = 0 });
var ac = a.Concat(b).ToArray();

class Info
{
    public int U001 { get; set; }
    public int U002 { get; set; }
    public int U003 { get; set; }
    public int U004 { get; set; }
}

| 생성 쿼리

SELECT 0 AS "U001", 0 AS "U002", "a"."A003" AS "U003", "a"."A004" AS "U004"
      FROM "ATable" AS "a"
      UNION ALL
      SELECT "b"."B001" AS "U001", "b"."B002" AS "U002", 0 AS "U003", 0 AS "U004"
      FROM "BTable" AS "b"
좋아요 3

Int, Double, Float 등은 문제없이 Union 되는 걸 확인했습니다!

제 코드에서 ‘Unable to translate set operation when matching columns on both sides have different store types’ 이 오류가 나오는 이유가 decimal 타입이 있어서 발생한 오류였습니다.
Ex) A008 (decimal) A.Select(s => s.A008 ).Union(A.Select(s =>0.0M)).ToQueryString();
(decimal 타입하고 0.0M (Convert.ToDecimal, decimal.Parse (decimal) 포함) 고정 값이 Union 이 되지 않습니다. Double, Float으로 캐스팅 후 Union 하면 잘 되지만 decimal 타입을 유지하면서 Union 은 되지 않습니다.)

decimal 이 Union 안되는 이유는 제가 한번 혼자서 알아보도록 하겠습니다.
지금까지 답변 감사합니다.

좋아요 4