Many-to-many relationships in EF Core 2.0 – Part 4: A more general abstraction
In the last few posts we saw how to hide use of the join entity from two entities with a many-to-many relationship. This post doesn’t add any additional functionality, it just abstracts some of what we saw so it can be re-used more easily.
To start with we define an interface for join entities:
public interface IJoinEntity<TEntity> { TEntity Navigation { get; set; } }
Any join entity will implement this interface twice; once for each side:
public class PostTag : IJoinEntity<Post>, IJoinEntity<Tag> { public int PostId { get; set; } public Post Post { get; set; } Post IJoinEntity<Post>.Navigation { get => Post; set => Post = value; } public int TagId { get; set; } public Tag Tag { get; set; } Tag IJoinEntity<Tag>.Navigation { get => Tag; set => Tag = value; } }
We can now re-write our facade colection to use any types that implement this interface:
public class JoinCollectionFacade<TEntity, TOtherEntity, TJoinEntity> : ICollection<TEntity> where TJoinEntity : IJoinEntity<TEntity>, IJoinEntity<TOtherEntity>, new() { private readonly TOtherEntity _ownerEntity; private readonly ICollection<TJoinEntity> _collection; public JoinCollectionFacade( TOtherEntity ownerEntity, ICollection<TJoinEntity> collection) { _ownerEntity = ownerEntity; _collection = collection; } public IEnumerator<TEntity> GetEnumerator() => _collection.Select(e => ((IJoinEntity<TEntity>)e).Navigation).GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); public void Add(TEntity item) { var entity = new TJoinEntity(); ((IJoinEntity<TEntity>)entity).Navigation = item; ((IJoinEntity<TOtherEntity>)entity).Navigation = _ownerEntity; _collection.Add(entity); } public void Clear() => _collection.Clear(); public bool Contains(TEntity item) => _collection.Any(e => Equals(item, e)); public void CopyTo(TEntity[] array, int arrayIndex) => this.ToList().CopyTo(array, arrayIndex); public bool Remove(TEntity item) => _collection.Remove( _collection.FirstOrDefault(e => Equals(item, e))); public int Count => _collection.Count; public bool IsReadOnly => _collection.IsReadOnly; private static bool Equals(TEntity item, TJoinEntity e) => Equals(((IJoinEntity<TEntity>)e).Navigation, item); }
The main advantage of this new abstraction is that specific delegates to select target entities and create join entities are not needed anymore. So now in our entities we can create collections like so:
public class Post { public Post() => Tags = new JoinCollectionFacade<Tag, Post, PostTag>(this, PostTags); public int PostId { get; set; } public string Title { get; set; } private ICollection<PostTag> PostTags { get; } = new List<PostTag>(); [NotMapped] public ICollection<Tag> Tags { get; } } public class Tag { public Tag() => Posts = new JoinCollectionFacade<Post, Tag, PostTag>(this, PostTags); public int TagId { get; set; } public string Text { get; set; } private ICollection<PostTag> PostTags { get; } = new List<PostTag>(); [NotMapped] public IEnumerable<Post> Posts { get; } }
Everything else, including the little test application, is unchanged from the previous post.
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架