领域对象中字典替代列表的EF Core兼容设计方案咨询
兼容EF Core的领域设计替代方案
针对你遇到的「领域层需要Dictionary提升访问性能,但EF Core不支持Dictionary映射」的问题,有以下几种实用的替代方案:
方案一:用私有List做EF映射桥梁,领域层仅维护Dictionary
核心思路是:EF只负责映射私有List,领域逻辑完全通过Dictionary操作,在数据变更时同步两者,EF加载实体后自动把List转成Dictionary。
修改后的Session领域类
using System.Collections.Immutable; namespace SessionTracker.Domain { public class Session { private readonly Dictionary<int, SessionScore> _sessionScores = new(); // 仅给EF映射用的私有List,EF通过反射访问 private List<SessionScore> _sessionScoresList = new(); protected Session() { } public Session(int playerId, Dictionary<int, long> startScores, DateTime timestamp) { PlayerId = playerId; StartDateTime = timestamp; foreach (var item in startScores) { var score = new SessionScore(item.Key, item.Value, timestamp); _sessionScores.Add(item.Key, score); _sessionScoresList.Add(score); } } // EF加载实体后调用,初始化Dictionary public void InitializeFromList() { _sessionScores.Clear(); foreach (var score in _sessionScoresList) { _sessionScores[score.Id] = score; // 这里的Id是你用来做Dictionary键的属性 } } public void Update(Dictionary<int, long> updatedScores, DateTime timestamp) { foreach (var item in updatedScores) { if (_sessionScores.TryGetValue(item.Key, out var existingScore)) { existingScore.Update(item.Value, timestamp); } else { var newScore = new SessionScore(item.Key, item.Value, timestamp); _sessionScores.Add(item.Key, newScore); _sessionScoresList.Add(newScore); } } } public void End(Dictionary<int, long> finalScores, DateTime timestamp) { Update(finalScores, timestamp); EndDateTime = timestamp; } public int PlayerId { get; } public DateTime StartDateTime { get; } public DateTime? EndDateTime { get; private set; } public ImmutableDictionary<int, SessionScore> SessionScores => _sessionScores.ToImmutableDictionary(); // 给EF配置用的内部属性 internal List<SessionScore> SessionScoresList => _sessionScoresList; }
EF Core配置类修改
public class SessionConfiguration : IEntityTypeConfiguration<Session> { public void Configure(EntityTypeBuilder<Session> builder) { builder.HasKey(ps => ps.Id); builder.Property(ps => ps.PlayerId).IsRequired(); // 映射内部List属性 builder.HasMany(ps => ps.SessionScoresList) .WithOne() .HasForeignKey(sm => sm.SessionId) .IsRequired() .OnDelete(DeleteBehavior.Cascade); // 忽略对外暴露的ImmutableDictionary,EF无需映射 builder.Ignore(ps => ps.SessionScores); } }
EF加载时初始化Dictionary
在DbContext中监听实体加载事件,自动初始化Dictionary:
public class YourDbContext : DbContext { public DbSet<Session> Sessions { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { // 配置你的数据库连接 } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.ApplyConfiguration(new SessionConfiguration()); } protected override void OnEntityLoaded(EntityLoadedEventArgs eventArgs) { base.OnEntityLoaded(eventArgs); if (eventArgs.Entity is Session session) { session.InitializeFromList(); } } }
方案二:List+内存Dictionary缓存,配合数据库索引优化
如果不想维护两个集合,可以保留List作为EF映射对象,同时在领域层维护一个Dictionary缓存,仅在首次访问或集合变更时更新缓存,再给数据库字段加索引提升查询性能。
修改后的Session领域类
using System.Collections.Immutable; namespace SessionTracker.Domain { public class Session { private readonly List<SessionScore> _sessionScores = new(); private Dictionary<int, SessionScore>? _sessionScoresCache; protected Session() { } public Session(int playerId, Dictionary<int, long> startScores, DateTime timestamp) { PlayerId = playerId; StartDateTime = timestamp; foreach (var item in startScores) { _sessionScores.Add(new SessionScore(item.Key, item.Value, timestamp)); } _sessionScoresCache = _sessionScores.ToDictionary(s => s.Id); } // 获取或初始化缓存 private Dictionary<int, SessionScore> GetScoresCache() { return _sessionScoresCache ??= _sessionScores.ToDictionary(s => s.Id); } public void Update(Dictionary<int, long> updatedScores, DateTime timestamp) { var cache = GetScoresCache(); foreach (var item in updatedScores) { if (cache.TryGetValue(item.Key, out var existingScore)) { existingScore.Update(item.Value, timestamp); } else { var newScore = new SessionScore(item.Key, item.Value, timestamp); _sessionScores.Add(newScore); cache.Add(item.Key, newScore); } } } public void End(Dictionary<int, long> finalScores, DateTime timestamp) { Update(finalScores, timestamp); EndDateTime = timestamp; } public int PlayerId { get; } public DateTime StartDateTime { get; } public DateTime? EndDateTime { get; private set; } public ImmutableDictionary<int, SessionScore> SessionScores => GetScoresCache().ToImmutableDictionary(); // 给EF映射用的List属性 public List<SessionScore> SessionScoresList => _sessionScores; }
EF配置加索引
给SessionScore的键字段加联合索引,提升数据库层面的查询效率:
public class SessionConfiguration : IEntityTypeConfiguration<Session> { public void Configure(EntityTypeBuilder<Session> builder) { builder.HasKey(ps => ps.Id); builder.Property(ps => ps.PlayerId).IsRequired(); builder.HasMany(ps => ps.SessionScoresList) .WithOne() .HasForeignKey(sm => sm.SessionId) .IsRequired(); // 给SessionScore的键字段和SessionId加联合索引 builder.Navigation(ps => ps.SessionScoresList).Metadata .PrincipalToDependent?.TargetEntityType .HasIndex(s => new { s.SessionId, s.Id }); } }
方案三:将SessionScore设为复合主键
如果SessionScore的键(即你用来做Dictionary Key的int值)和SessionId组合是唯一的,可以把它们设为复合主键,数据库层面保证唯一性,领域层再用Dictionary缓存提升访问速度。
SessionScore类修改
public class SessionScore { protected SessionScore() { } public SessionScore(int id, long value, DateTime timestamp) { Id = id; Value = value; LastUpdated = timestamp; } public int SessionId { get; set; } // 外键,作为复合主键一部分 public int Id { get; } // 原Dictionary键,作为复合主键一部分 public long Value { get; private set; } public DateTime LastUpdated { get; private set; } public void Update(long value, DateTime timestamp) { Value = value; LastUpdated = timestamp; } }
EF配置SessionScore复合主键
public class SessionScoreConfiguration : IEntityTypeConfiguration<SessionScore> { public void Configure(EntityTypeBuilder<SessionScore> builder) { builder.HasKey(s => new { s.SessionId, s.Id }); builder.Property(s => s.Value).IsRequired(); builder.Property(s => s.LastUpdated).IsRequired(); } }
之后Session类的实现可以参考方案二,用List加Dictionary缓存即可。
内容的提问来源于stack exchange,提问作者user1890098
相关产品推荐
相关产品推荐

