在实体框架中为多对多关系添加标签

时间:2017-06-08 05:44:35

标签: asp.net entity-framework asp.net-core entity-framework-core

我正在创建一个API端点,该端点使用应与谈话关联的标记创建新的Talk。我在我的域中的标签和会话之间建立了多对多的关系,请参阅下面的关系。

Tag.cs​​

using System;
using System.Collections.Generic;

namespace Conferency.Domain
{
    public class Tag : IAuditable
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public List<TalkTag> TalkTags { get; set; }
        public DateTime ModifiedAt { get; set; }
        public DateTime CreatedAt { get; set; }
    }
}

Talk.cs

using System;
using System.Collections.Generic;

namespace Conferency.Domain
{
    public class Talk : IAuditable
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Url { get; set; }
        public List<TalkTag> TalkTags { get; set; }
        public DateTime Presented { get; set; }
        public DateTime ModifiedAt { get; set; }
        public DateTime CreatedAt { get; set; }
    }
}

TalkTag.cs​​

using System;
using System.Collections.Generic;
using System.Text;

namespace Conferency.Domain
{
    public class TalkTag
    {
        public int TalkId { get; set; }
        public Talk Talk { get; set; }
        public int TagId { get; set; }
        public Tag Tag { get; set; }
    }
}

ConferencyContext.cs(删除不相关的代码)

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using System;
using Conferency.Domain;

namespace Conferency.Data
{
    public class ConferencyContext: DbContext
    {
        public DbSet<Talk> Talks { get; set; }
        public DbSet<Tag> Tags { get; set; }
        public DbSet<TalkTag> TagTalks { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<TalkTag>()
                .HasKey(s => new { s.TalkId, s.TagId });

            modelBuilder.Entity<TalkTag>()
                .HasOne(pt => pt.Talk)
                .WithMany(p => p.TalkTags)
                .HasForeignKey(pt => pt.TalkId);

            modelBuilder.Entity<TalkTag>()
                .HasOne(pt => pt.Tag)
                .WithMany(t => t.TalkTags)
                .HasForeignKey(pt => pt.TagId);

            base.OnModelCreating(modelBuilder);
        }
    }
}

TalkViewModel.cs

using System;
using System.Collections.Generic;

namespace Conferency.Application.Models
{
    public class TalkViewModel
    {
        public string Name { get; set; }
        public string Url { get; set; }
        public List<String> Tags { get; set; }
    }
}

问题是我无法弄清楚如何创建一个谈话及其标签(如果它们存在则附加,如果它们不存在则创建)。我不确定以什么顺序完成这项工作。我是否必须查询每个标记以检查它们是否存在或者是否存在我可以使用的findOrCreate方法?如果尚未创建Talk,如何创建TalkTag记录?是否有一种优雅的方式来实现这一点,我不理解?

TalkRepository.cs

using System.Collections.Generic;
using Conferency.Domain;
using Microsoft.EntityFrameworkCore;
using System;
using System.Linq;
using System.Threading.Tasks;

namespace Conferency.Data
{
    public class TalkRepository : ITalkRepository
    {
        private ConferencyContext _context;

        public TalkRepository(ConferencyContext context)
        {
            _context = context;
        }

        public void Add(Talk entity)
        {
            _context.Add(entity);
        }

        public void AddWithTags(Talk entity, List<String> tags)
        {
            // Create Talk
            // Query for each tag
            // Create if they don't exist
            // Attach to talk
            // ??
        }

        public IEnumerable<Talk> GetAllTalks()
        {
            return _context.Talks
                .Include(c => c.TalkTags)
                .OrderBy(c => c.Presented)
                .ToList();
        }

        public Talk GetTalk(int id)
        {
            return _context.Talks
                .Include(c => c.TalkTags)
                .Where(c => c.Id == id)
                .FirstOrDefault();
        }

        public async Task<bool> SaveAllAsync()
        {
            return (await _context.SaveChangesAsync()) > 0;
        }
    }
}

我是c#的新手,我正在尝试学习最佳实践并熟悉EF和ASP.NET Core,所以希望有人可以帮助指导我走正确的道路。如果你想看看https://github.com/bliitzkrieg/Conferency

,那么完整的解决方案就在这里

我自己尝试解决它,但我得到了NullPointerException,这是我尝试解决方案:

TalksController.cs

  [HttpPost()]
        public async Task<IActionResult> Post([FromBody]TalkViewModel model)
        {
            try
            {
                _logger.LogInformation("Creating a new Talk");

                List<Tag> tags = _tagRepo.FindOrCreateTags(model.Tags);

                Talk talk = new Talk { Name = model.Name, Url = model.Url };

                List<TalkTag> talkTags = new List<TalkTag>();
                tags.ForEach(tag =>
                {
                    var talkTag = new TalkTag { TagId = tag.Id, Talk = talk };
                    talkTags.Add(talkTag);
                });

                talk.TalkTags.AddRange(talkTags); // Exception being thrown here
                _repo.Add(talk);

                if (await _repo.SaveAllAsync())
                {
                    string newUri = Url.Link("TalkGet", new { id = talk.Id });
                    return Created(newUri, talk);
                }
                else
                {
                    _logger.LogWarning("Could not save Talk");
                }
            }
            catch (Exception ex)
            {
                _logger.LogError($"Threw exception while saving Talk: {ex}");
            }

            return BadRequest();
        }
    }

TagRepository.cs

using System;
using System.Collections.Generic;
using Conferency.Domain;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using System.Linq;

namespace Conferency.Data
{
    public class TagRepository: ITagRepository
    {
        private ConferencyContext _context;

        public TagRepository(ConferencyContext context)
        {
            _context = context;
        }

        public void Add(Tag entity)
        {
            _context.Add(entity);
        }

        public List<Tag> FindOrCreateTags(List<string> tags)
        {
            List<Tag> _tags = new List<Tag>();
            tags.ForEach(t =>
            {
                try
                {
                    var tag = _context.Tags
                       .Where(c => c.Name == t)
                       .FirstOrDefault();

                    if (tag != null)
                    {
                        _tags.Add(tag);
                    }
                    else
                    {
                        Tag created = new Tag { Name = t };
                        this.Add(created);
                        _tags.Add(created);
                    }
                }
                catch (Exception ex)
                {

                }

            });

            return _tags;
        }

        public async Task<bool> SaveAllAsync()
        {
            return (await _context.SaveChangesAsync()) > 0;
        }
    }
}

2 个答案:

答案 0 :(得分:2)

TalkViewModel添加List<TagViewModel>属性,其中包含以下属性:

public int TagId { get; set; }
public string TagName { get; set; }
public bool Selected { get; set; }

当您将TalkViewModel传递到您的仓库时,过滤掉所选的TagViewModel并为每个仓库添加TalkTag TagId到您的TalkTags属性在Talk上。 EF应该注意在TalkId上添加适当的_context.SaveChanges()

如果Tag不存在,请创建一个TalkTag,其中包含新的Tag和新的Talk作为其属性,然后将其添加到{{1} }}。 EF应该照顾其余部分。

答案 1 :(得分:0)

您尚未初始化导致nullpointer的TalkTags集合。初始化Talk对象时尝试此操作:

Talk talk = new Talk { Name = model.Name, Url = model.Url, TalkTags = new List<TalkTag>() };

您是否需要更多TalkTag对象的属性?否则,您可以在Tag类中使用List<Talk>,在Talk类中使用List<Tag>,并且映射将由EF完成(将在数据库中创建TalkTag表)。

Michael Tranchida已经描述了将对象添加到上下文中的方法。