zoukankan      html  css  js  c++  java
  • Many-to-many relationships in EF Core 2.0 – Part 3: Hiding as ICollection

    In the previous post we ended up with entities that hide the join entity from the public surface. However, it was not possible to add or removed entities through this public surface. To enable this we need an ICollection implementation that acts as a true facade over the real join entity collection and delegates all responsibilities to that collection.

    The collection implementation

    Here’s one possible implementation of such a collection:

    public class JoinCollectionFacade<T, TJoin> : ICollection<T>
    {
        private readonly ICollection<TJoin> _collection;
        private readonly Func<TJoin, T> _selector;
        private readonly Func<T, TJoin> _creator;
    
        public JoinCollectionFacade(
            ICollection<TJoin> collection,
            Func<TJoin, T> selector,
            Func<T, TJoin> creator)
        {
            _collection = collection;
            _selector = selector;
            _creator = creator;
        }
    
        public IEnumerator<T> GetEnumerator()
            => _collection.Select(e => _selector(e)).GetEnumerator();
    
        IEnumerator IEnumerable.GetEnumerator()
            => GetEnumerator();
    
        public void Add(T item)
            => _collection.Add(_creator(item));
    
        public void Clear()
            => _collection.Clear();
    
        public bool Contains(T item)
            => _collection.Any(e => Equals(_selector(e), item));
    
        public void CopyTo(T[] array, int arrayIndex)
            => this.ToList().CopyTo(array, arrayIndex);
    
        public bool Remove(T item)
            => _collection.Remove(
                _collection.FirstOrDefault(e => Equals(_selector(e), item)));
    
        public int Count
            => _collection.Count;
    
        public bool IsReadOnly
            => _collection.IsReadOnly;
    }

    The idea is pretty simple–operations on the facade are translated into operations on the underlying collection. Where needed, a “selector” delegate is used to extract the desired target entity from the join entity. Likewise, a “creator” delegate creates a new join entity instance from the target entity when a new relationship is added.

    实际上我觉得改成下面这样会更好,另外Remove和Contains方法我觉得没什么用,所以暂时就先放的抛出NotSupportedException异常:

    public class ActionCollection<T, TJoin> : ICollection<T>
    {
        protected readonly Func<T, TJoin> creator;
        protected readonly Func<TJoin, T> selector;
        protected readonly Func<ICollection<TJoin>> collectionSelector;
    
        public ActionCollection(Func<ICollection<TJoin>> collectionSelector, Func<T, TJoin> creator, Func<TJoin, T> selector)
        {
            this.collectionSelector = collectionSelector;
            this.creator = creator;
            this.selector = selector;
        }
    
        public int Count => collectionSelector().Count;
    
        public bool IsReadOnly => collectionSelector().IsReadOnly;
    
        public void Add(T item)
        {
            collectionSelector().Add(creator(item));
        }
    
        public void Clear()
        {
            collectionSelector().Clear();
        }
    
        public bool Contains(T item)
        {
            throw new NotSupportedException("Contains is not supported");
        }
    
        public void CopyTo(T[] array, int arrayIndex)
        {
            List<T> list = new List<T>();
    
            foreach (var tJoin in collectionSelector())
            {
                list.Add(selector(tJoin));
            }
    
            list.CopyTo(array, arrayIndex);
        }
    
        public IEnumerator<T> GetEnumerator()
        {
            return this.collectionSelector().Select(tj => this.selector(tj)).GetEnumerator();
        }
    
        public bool Remove(T item)
        {
            throw new NotSupportedException("Remove is not supported");
        }
    
        IEnumerator IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }
    }

    Updating the model

    We need to initialize instances of this collection in our entities:

    public class Post
    {
        public Post()
            => Tags = new JoinCollectionFacade<Tag, PostTag>(
                PostTags,
                pt => pt.Tag,
                t => new PostTag { Post = this, Tag = t });
    
        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, PostTag>(
                PostTags,
                pt => pt.Post,
                p => new PostTag { Post = p, Tag = this });
    
        public int TagId { get; set; }
        public string Text { get; set; }
    
        private ICollection<PostTag> PostTags { get; } = new List<PostTag>();
    
        [NotMapped]
        public ICollection<Post> Posts { get; }
    }

    Using the ICollection navigations

    Notice how Tags and Posts are now ICollection properties instead of IEnumerable properties. This means we can add and remove entities from the many-to-many collections without using the join entity directly. Here’s the test application updated to show this:

    public class Program
    {
        public static void Main()
        {
            using (var context = new MyContext())
            {
                context.Database.EnsureDeleted();
                context.Database.EnsureCreated();
    
                var tags = new[]
                {
                    new Tag { Text = "Golden" },
                    new Tag { Text = "Pineapple" },
                    new Tag { Text = "Girlscout" },
                    new Tag { Text = "Cookies" }
                };
    
                var posts = new[]
                {
                    new Post { Title = "Best Boutiques on the Eastside" },
                    new Post { Title = "Avoiding over-priced Hipster joints" },
                    new Post { Title = "Where to buy Mars Bars" }
                };
    
                posts[0].Tags.Add(tags[0]);
                posts[0].Tags.Add(tags[1]);
                posts[1].Tags.Add(tags[2]);
                posts[1].Tags.Add(tags[3]);
                posts[2].Tags.Add(tags[0]);
                posts[2].Tags.Add(tags[1]);
                posts[2].Tags.Add(tags[2]);
                posts[2].Tags.Add(tags[3]);
    
                context.AddRange(tags);
                context.AddRange(posts);
    
                context.SaveChanges();
            }
    
            using (var context = new MyContext())
            {
                var posts = LoadAndDisplayPosts(context, "as added");
    
                posts.Add(context.Add(new Post { Title = "Going to Red Robin" }).Entity);
    
                var newTag1 = new Tag { Text = "Sweet" };
                var newTag2 = new Tag { Text = "Buzz" };
    
                foreach (var post in posts)
                {
                    var oldTag = post.Tags.FirstOrDefault(e => e.Text == "Pineapple");
                    if (oldTag != null)
                    {
                        post.Tags.Remove(oldTag);
                        post.Tags.Add(newTag1);
                    }
                    post.Tags.Add(newTag2);
                }
    
                context.SaveChanges();
            }
    
            using (var context = new MyContext())
            {
                LoadAndDisplayPosts(context, "after manipulation");
            }
        }
    
        private static List<Post> LoadAndDisplayPosts(MyContext context, string message)
        {
            Console.WriteLine($"Dumping posts {message}:");
    
            var posts = context.Posts
                .Include("PostTags.Tag")
                .ToList();
    
            foreach (var post in posts)
            {
                Console.WriteLine($"  Post {post.Title}");
                foreach (var tag in post.Tags)
                {
                    Console.WriteLine($"    Tag {tag.Text}");
                }
            }
    
            Console.WriteLine();
    
            return posts;
        }
    }

    Notice that:

    • When seeding the database, we add Tags directly to the Tags collection on Post.
    • When finding and removing existing tags, we can search directly for the Tag and remove it from the Post.Tags collection without needing to use the join entity.

    It’s worth calling out again that, just like in the previous post, we still can’t use Tags directly in any query. For example, using it for Include won’t work:

    var posts = context.Posts
        .Include(e => e.Tags) // Won't work
        .ToList();

    EF has no knowledge of “Tags”–it is not mapped. EF only knows about the private PostTags navigation property.

    Functionally, this is about as far as we can go without starting to mess with the internals of EF. However, in one last post I’ll show how to abstract out the collection and join entity a bit more so that it is easier reuse for different types.

    原文链接

  • 相关阅读:
    HTML直接引用vue.min.js,bootstrap-vue.min.js,axios.min.js等开发一个页面(2)
    HTML直接引用vue.min.js,bootstrap-vue.min.js,axios.min.js等开发一个页面
    [Vue+Element UI]不知道总页码数时如何实现翻页
    [Vue] 报错: Uncaught (in promise)
    [Vue + Element UI] 单选框
    [Lombok] Lombok的使用和常用注解使用示例
    Eclipse的Web项目开发:Maven插件jetty服务器的关闭
    [Python] 电脑同时安装python2和python3, 如何实现切换使用
    [Yaml] YAML 入门教程
    k8s ha的安装
  • 原文地址:https://www.cnblogs.com/OpenCoder/p/9769698.html
Copyright © 2011-2022 走看看