简体   繁体   English

如何使用具有多对多关系的 Entity Framework Core 5 添加和更新项目?

[英]How do I add and update items using Entity Framework Core 5 that have many to many relationships?

I've been struggling with this all evening and still don't fully understand how Entity Framework Core works with many to many relationships.我整个晚上都在为此苦苦挣扎,但仍然不完全了解 Entity Framework Core 如何处理多对多关系。

I have a TransportProvider class and a Tag class.我有一个 TransportProvider class 和一个标签 class。 It's a many to many relationship.这是多对多的关系。 When adding a new TransportProvider you can assign tags.添加新的 TransportProvider 时,您可以分配标签。 If the tag already exists in the database I'd like to add that existing tag, otherwise I'd like to insert a new tag.如果数据库中已经存在该标签,我想添加该现有标签,否则我想插入一个新标签。 This is what I have for my TransportProvider class:这就是我的 TransportProvider class 所拥有的:

public class TransportProvider
{      
    public int ID { get; set; }

    [Display(Name = "Company name")]
    [Required]
    [StringLength(200)]
    public string CompanyName { get; set; }

    ... standard properties

    public bool Disabled { get; set; }

    [NotMapped]
    public string SelectedTags { get; set; }

    public ICollection<Tag> Tags { get; set; }
}

My tag class:我的标签 class:

public class Tag
{
    public int ID { get; set; }

    [Required]
    [StringLength(100)]
    public string Name { get; set; }

    public ICollection<TransportProvider> TransportProviders { get; set; }
}

And this is my controller function that creates a new transport provider:这是我的 controller function 创建了一个新的传输提供程序:

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("ID,CompanyName,ContactName,ContactTelephone1,ContactTelephone2,ContactEmail,CompanyWebsite,AddressLine1,AddressLine2,Suburb,Province,PostCode,Country,Lat,Lng,SelectedTags,Notes,Disabled")] TransportProvider transportProvider)
{
    if (ModelState.IsValid)
    {
        var selectedTags = !string.IsNullOrEmpty(transportProvider.SelectedTags) ? transportProvider.SelectedTags.Split(',') : new string[0];

        _context.TransportProviders.Add(transportProvider); 

        foreach (var selectedTag in selectedTags)
        {
            var tag = _context.Tags.SingleOrDefault(t => t.Name.ToLower() == selectedTag);
            if (tag == null)
            {
                tag = new Tag();
                tag.Name = selectedTag;     
                
            }

            transportProvider.Tags.Add(tag);                    
        }

        await _context.SaveChangesAsync();

        return RedirectToAction(nameof(Index));
    }

    return View(transportProvider);
}

and finally my context class:最后是我的上下文 class:

public class AntelopeContext : DbContext
{
    public AntelopeContext(DbContextOptions<AntelopeContext> options) : base(options)
    {
    }

    public DbSet<TransportProvider> TransportProviders { get; set; }
    public DbSet<Tag> Tags { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<TransportProvider>().ToTable("TransportProvider");
        modelBuilder.Entity<Tag>().ToTable("Tag");
    }
}

If I try and execute this code I get a NullReferenceException for the line:如果我尝试执行此代码,我会得到该行的 NullReferenceException:

transportProvider.Tags.Add(tag); 

I don't know why this is so difficult to do.我不知道为什么这很难做到。 All I want to do is add tags to a transport provider.我要做的就是向传输提供商添加标签。 If the tag is new it needs to insert a new tag record.如果标签是新的,则需要插入新的标签记录。 If not then it just has to link the existing tag.如果没有,那么它只需要链接现有的标签。

How do I do this?我该怎么做呢?

Thanks谢谢

Many to many relationships require a collection navigation property on both sides.多对多关系需要双方都有一个集合导航属性。 They will be discovered by convention like other types of relationships.它们将像其他类型的关系一样按照惯例被发现。

public class TransportProvider
{
    public int TransportProviderId { get; set; }
    public string CompanyName { get; set; }
    public bool Disabled { get; set; }

    public ICollection<Tag> Tags { get; set; }
}

public class Tag
{
    public int TagId { get; set; }
    
    public string Name { get; set;}

    public ICollection<TransportProvider> TransportProviders { get; set; }
}


The way this relationship is implemented in the database is by a join table that contains foreign keys to both TransferProvider and Tag.在数据库中实现这种关系的方式是通过一个包含 TransferProvider 和 Tag 的外键的连接表。 For example this is what EF will create in a relational database for the above model.例如,这是 EF 将为上述 model 在关系数据库中创建的内容。

CREATE TABLE [TransportProvider] (
    [TransportProviderId] int NOT NULL IDENTITY,
    [CompanyName] nvarchar(max) NULL,
    [Disable]    bit NULL,
    CONSTRAINT [PK_TransportProvider] PRIMARY KEY ([TransportProviderId])
);

CREATE TABLE [Tag] (
    [TagId] int NOT NULL IDENTITY,
    [Name]  nvarchar(max) NULL,
    CONSTRAINT [PK_Tag] PRIMARY KEY ([TagId])
);

CREATE TABLE [TransportProviderTag] (
    [TransportProviderId] int NOT NULL,
    [TagId]  int NOT NULL,
    CONSTRAINT [PK_TransportProviderTag] PRIMARY KEY ([TransportProviderId], [TagId]),
    CONSTRAINT [FK_TransportProviderTag_TransportProviders_TransportProviderId] FOREIGN KEY ([TransportProviderId]) REFERENCES [TransferProviders] ([TransferProviderId]) ON DELETE CASCADE,
    CONSTRAINT [FK_TransportProviderTag_Tags_TagId] FOREIGN KEY ([TagId]) REFERENCES [Tags] ([TagId]) ON DELETE CASCADE
);

Internally, EF creates an entity type to represent the join table that will be referred to as the join entity type.在内部,EF 创建一个实体类型来表示将被称为连接实体类型的连接表。

This is a code first approach.这是代码优先的方法。 You have first to create TransferProvider and Tag, and then add what row with them in TransferProviderTag table您必须首先创建 TransferProvider 和 Tag,然后在 TransferProviderTag 表中添加它们的行

Since you didn't bind the Tags property, it will default be null , you need to initialize the Tags in TransportProvider firstly.由于您没有绑定Tags属性,因此默认为null ,您需要先在TransportProvider中初始化Tags

public async Task<IActionResult> Create([Bind("ID,CompanyName,ContactName,ContactTelephone1,ContactTelephone2,ContactEmail,CompanyWebsite,AddressLine1,AddressLine2,Suburb,Province,PostCode,Country,Lat,Lng,SelectedTags,Notes,Disabled")] TransportProvider transportProvider)
{

    transportProvider.Tags = new List<Tag>();

    //...
}

Finally.最后。 I got it working, I'm not sure this is the 'correct' way.我得到了它的工作,我不确定这是“正确”的方式。 but it seems to work.但它似乎工作。

I was under the impression that EF Core 5 didn't require joining tables in many-to-many relationships.我的印象是 EF Core 5 不需要在多对多关系中加入表。 However when I tried to execute without a joining table I was getting an error about a joining table not being present.但是,当我尝试在没有连接表的情况下执行时,我收到一个关于连接表不存在的错误。 I therefore added one as suggested.因此,我按照建议添加了一个。

I then manually created the TransportProvider, manually checked for a Tag and created if it didn't exist, then manually entered the joining table record.然后我手动创建了 TransportProvider,手动检查了一个 Tag,如果它不存在就创建,然后手动输入连接表记录。 I still feel this probably isn't the most efficient way of doing things, but it works.我仍然觉得这可能不是最有效的做事方式,但它确实有效。 Code in case anyone is interested:万一有人感兴趣的代码:

public async Task<IActionResult> Create([Bind("ID,CompanyName,ContactName,ContactTelephone1,ContactTelephone2,ContactEmail,CompanyWebsite,AddressLine1,AddressLine2,Suburb,Province,PostCode,Country,Lat,Lng,SelectedTags,Notes,Disabled")] TransportProvider transportProvider)
{
    if (ModelState.IsValid)
    {
        var selectedTags = !string.IsNullOrEmpty(transportProvider.SelectedTags) ? transportProvider.SelectedTags.Split(',') : new string[0];

        transportProvider.TransportProviderTags = new List<TransportProviderTag>();

        _context.TransportProviders.Add(transportProvider);

        await _context.SaveChangesAsync();

        foreach (var selectedTag in selectedTags)
        {
            var tag = _context.Tags.SingleOrDefault(t => t.Name.ToLower() == selectedTag);
            if (tag == null)
            {
                tag = new Tag();
                tag.Name = selectedTag;
                _context.Tags.Add(tag);

                await _context.SaveChangesAsync();
            }

            var tpt = new TransportProviderTag();
            tpt.TransportProviderID = transportProvider.ID;
            tpt.TagID = tag.ID;
            transportProvider.TransportProviderTags.Add(tpt);

            await _context.SaveChangesAsync();
        }

        return RedirectToAction(nameof(Index));
    }

    return View(transportProvider);
}

Updated context class:更新了上下文 class:

public class AntelopeContext : DbContext
{
    public AntelopeContext(DbContextOptions<AntelopeContext> options) : base(options)
    {
    }

    public DbSet<TransportProvider> TransportProviders { get; set; }
    public DbSet<Tag> Tags { get; set; }
    public DbSet<TransportProviderTag> TransportProviderTags { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<TransportProvider>().ToTable("TransportProvider");
        modelBuilder.Entity<Tag>().ToTable("Tag");
        modelBuilder.Entity<TransportProviderTag>().ToTable("TransportProviderTag");

        modelBuilder.Entity<TransportProviderTag>()
            .HasKey(tpt => new { tpt.TransportProviderID, tpt.TagID });
        modelBuilder.Entity<TransportProviderTag>()
            .HasOne(tpt => tpt.TransportProvider)
            .WithMany(tp => tp.TransportProviderTags)
            .HasForeignKey(tpt => tpt.TransportProviderID);
        modelBuilder.Entity<TransportProviderTag>()
            .HasOne(tpt => tpt.Tag)
            .WithMany(t => t.TransportProviderTags)
            .HasForeignKey(tpt => tpt.TagID);
    }
}

And thanks @MilutinSpaic and @mj1313 for steering me in the right direction.感谢@MilutinSpaic 和@mj1313 引导我朝着正确的方向前进。 Hopefully this will help someone else希望这会帮助别人

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM