以下のteratailで質問されている内容が私の環境でも発生した。
不具合は質問内容と同様、ある特定のテーブルのデータを取得したときレコードが重複されて取得されてしまうことがあるというもの。
teratailではOS再起動で直ったとあるが私の環境では直らなかった。
さっさと結論だけ読みたい場合はこちら
バージョン情報
.NET 5.0
Microsoft.EntityFrameworkCore v5.0.12
Npgsql.EntityFrameworkCore.PostgreSQL v5.0.10
現象
以下のPersonTableを取得する場合を例にする。
using System;
using SystemComponentModel.DataAnnotations.Schemes;
namespace SampleProject
{
[Table("persontbl")]
[Serializable]
public class PersonTable
{
[Column("id")]
public string ID{ get; set; }
[Column("name")]
public string Name{ get; set; }
[Column("age")]
public decimal Age{ get; set; }
}
DBには以下の値が入っているものとする。
id | name | age |
---|---|---|
{0001 | taro | 21 |
0002 | shigeru | 12 |
0003 | satoshi | 8 |
0004 | hikaru | 14 |
0005 | shigezo | 87 |
0006 | takeshi | 10 |
そして、以下のコードでDBのレコードを取得する。
using System;
using System.Collections.Generic;
namespace SampleProject
{
public class DBAccess
{
public DatabaseAccess(DatabaseContext context)
{
this.context = context;
}
public List<PersonTable> GetShota()
{
int[] targetAges = new[]{ 6, 7, 8, 9, 10, 11, 12 };
var results = context.PersonTable
.Where(x => (targetAges.Contains(x.Age) == true))
.ToList();
return results;
}
}
}
GetShotaメソッドの取得結果として期待されるのはこのようになる。
id | name | age |
---|---|---|
0002 | shigeru | 12 |
0003 | satoshi | 8 |
0006 | takeshi | 10 |
しかし、不具合発生時にはこのようになってしまう。
id | name | age |
---|---|---|
0002 | shigeru | 12 |
0003 | satoshi | 8 |
0003 | satoshi | 8 |
あるいはこう。
id | name | age |
---|---|---|
0002 | shigeru | 12 |
0002 | shigeru | 12 |
0002 | shigeru | 12 |
原因
不明。
別のテーブルだと起きない。
解決策
EntityFramewok + LINQ で取得する方法としての解決策は
ありません。
(情報お待ちしております。)
結論
どうにもならないのでSQLを直打ちすることにした。
using System;
using System.Collections.Generic;
namespace SampleProject
{
public class DBAccess
{
public DatabaseAccess(DatabaseContext context)
{
this.context = context;
}
public List<PersonTable> GetShota()
{
int[] targetAges = new[]{ 6, 7, 8, 9, 10, 11, 12 };
List<PersonTable> results = new();
using (var command = context.Database.GetDbConnection().CreateCommand())
{
string inCondition = "";
for (int i = 0; i < targetAges.Count; i++)
{
inCondition += $"{targetAges[i]}";
if (i != targetAges.Count - 1)
{
inCondition += ",";
}
}
if (command.Connection.State != ConnectionState.Open)
{
command.Connection.Open();
}
command.CommandText = $"SELECT * FROM persontbl WHERE age IN ({inCondition});";
using (var dbReader = command.ExecuteReader())
{
bool read = dbReader.Read();
while (read)
{
string id = dbReader["id"] as string;
string name = dbReader["name"] as string;
decimal age = (decimal)dbReader["age"];
results.Add(new(){ ID = id, Age = (int)age, Name = name });
read = dbReader.Read();
}
}
command.Connection.Close();
}
return results;
}
}
}
不本意ながらまあ一番確実だよね・・・。
留意事項
私はショタコンではない。
DBAccess_AFTER.csはベタで処理を書いているけど、もちろんラッパーとか作ってちゃんと構造化してね?
同コードは「とりあえず動いた!」という段階で投稿したものなので、部分的に間違っている可能性がある。
あと、エラー処理もしていない。
勝手に質疑応答
Q:なんで x.Age >= 6 || x.Age <= 12 の条件でLINQ作ってないの?
A:元のソースだとその部分が string型だったんよ。
そのまま掲載できないから改変してたらdecimalになってた。すまん。
Q:ほかに試したことは?
A:ContainsメソッドでSQLのIN句に相当する条件を作っているのがいけないのかと思って、Whereの条件取っ払って全件取得にしても駄目だった。
どうやっても重複するレコードが発生してしまった。