实体类型"列表<string>"需要在我的 mstest 中定义主键



在使用mstest进行一些单元测试时,当我设置UseInMemoryDatabase时出现此错误。重要的是,当我运行应用程序时,错误不会出现。只有在运行测试时才会出现。它似乎来自这里:

public List<string> WordProgress { get; set; } = new List<string>();

当我在上面添加[NotMapped]时,错误消失了,但是这会使列消失。

背景:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<FinalWord>()
.HasMany(c => c.Games)
.WithOne(e => e.FinalWord);
modelBuilder.Entity<Game>()
.HasMany(c => c.GameWords)
.WithOne(e => e.Game);
modelBuilder.Entity<Game>()
.HasOne(c => c.FinalWord)
.WithMany(e => e.Games);

modelBuilder.Entity<Word>()
.HasMany(c => c.GameWords)
.WithOne(e => e.Word);
modelBuilder.Entity<GameWord>()
.HasOne(c => c.Game)
.WithMany(e => e.GameWords);

modelBuilder.Entity<GameWord>()
.HasOne(c => c.Word)
.WithMany(e => e.GameWords);
}

GameWord.cs

public class GameWord
{
[Key]
public int Id { get; set; }
public List<string> WordProgress { get; set; } = new List<string>();

[Required]
public Word Word { get; set; }
[Required]
public Game Game { get; set; }
public bool Finished { get; set; } = false;
}

和我的测试设置。

public UnitTest1()
{
DbContextOptionsBuilder<LingoContext> dbOptions = new DbContextOptionsBuilder<LingoContext>()
.UseInMemoryDatabase(
Guid.NewGuid().ToString()
);

_context = new LingoContext(dbOptions.Options);
}
[TestMethod]
public void GetAllGames()
{
var repo = new SqlGameRepo(_context);
Game game1 = new Game();
Game game2 = new Game();
_context.Game.Add(game1);
_context.Game.Add(game2);
_context.SaveChanges();

IEnumerable<Game> result = repo.GetAllGames();

Assert.AreEqual(result.Count(), 2);
}

有人知道原因吗?

实体框架将List<String>视为导航属性,例如,它期望有一个string表,它可以连接到GameWord表。

关系数据库不支持列是"列表"——你必须创建一个单独的表,使用外键和连接,或者将列映射为不同的类型,例如将值转换为逗号分隔的字符串或json字符串

作为单独的表:

public class GameWord
{
[Key]
public int Id { get; set; }
public List<Attempt> WordProgress { get; set; } = new List<Attempt>();
[Required]
public Word Word { get; set; }
[Required]
public Game Game { get; set; }
public bool Finished { get; set; } = false;
}
public class Attempt
{
[Key]
public int Id { get; set; }
public int GameWordId { get; set; }
public GameWord GameWord { get; set; }
[Required]
public string Value { get; set; }
}

作为逗号分隔的字符串:

// The Entity that you have defined doesn't need to be changed
public class GameWord
{
[Key]
public int Id { get; set; }
public List<Attempt> WordProgress { get; set; } = new List<Attempt>();
[Required]
public Word Word { get; set; }
[Required]
public Game Game { get; set; }
public bool Finished { get; set; } = false;
}
// Just specify how to convert the C# property to the SQL value
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<GameWord>()
.Property(c => c.WordProgress)
.HasConversion(
attempts => string.Join(",", attempts),
csv => csv.Split(',')
);
}

为json:

// Specify a different conversion
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<GameWord>()
.Property(c => c.WordProgress)
.HasConversion(
attempts => JsonSerializer.Serialize(attempts, (JsonSerializerOptions)null),
json => JsonSerializer.Deserialize<List<string>>(json, (JsonSerializerOptions)null)
);
}

当您看到使用实体框架向表中添加json或csv列是多么容易时,您可能会认为添加整个新表的第一种方法是多余的。我仍然建议从这种方法开始,只有在性能受到连接操作的影响时才切换到自定义转换(我认为在这里不太可能)。原因是:

  • 带有ORM的导航属性是常见的,并且易于开发人员理解。自定义转换则不是。它们是实体框架核心的一个特定功能,并且在转换如何影响EF
  • 所做的更改跟踪方面存在一些微妙之处。
  • 如果将来您决定要存储的不仅仅是单个"word"值,可能是"word"和进行尝试的时间,则必须编写一个时髦的迁移脚本来解压缩列中的值并将其转换为不同的格式。使用专用的实体类型和子表,您可以只添加一个新列。
  • 实体框架通常不能在已转换的列上转换SQL筛选操作。如果你想搜索所有玩家尝试过单词"Fruit"的游戏,你就必须自己编写SQL。如果你使用子表和导航属性,这很容易写:
context.GameWords.Where(
gameWord => gameWord.WordProgress.Any(
attempt => attempt.Value == "Fruit"
)
)

相关内容

  • 没有找到相关文章