添加迁移时出错-未将对象引用设置为对象的实例

时间:2019-02-09 23:18:26

标签: c# asp.net-core .net-core

我们尝试了本文来实现基于数据库的租户提供程序-https://www.codingame.com/playgrounds/5440/multi-tenant-asp-net-core-2---implementing-database-based-tenant-provider ...,它有2个数据库上下文ApplicationDbContext和MultiTenantDbContext。

我们设法为ApplicationDbContext做add-migration init and update-database,但是我们不能不能为MultiTenantDbContext做第二个……并且它一直说对象引用的问题未设置为与`var host = accessor.HttpContext.Request.Host.Value;相关的对象;在下面的/Models/Tenant.cs上。

我的第二个问题,我不明白为什么这个DatabaseTenantProvider类在MultiTenantDbContext的add-migration init中执行?!?!

有什么想法吗?

代码如下:

/Models/Tenant.cs:

using AthlosifyCore.Data;
using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;

namespace AthlosifyCore.Models
{
    public class Tenant
    {
        [Key]
        public Guid Id { get; set; }
        public string Name { get; set; }
        public string HostName { get; set; }
    }

    public interface ITenantProvider
    {
        Guid GetTenantId();
    }

    public class DatabaseTenantProvider : ITenantProvider
    {
        private Guid _tenantId;

        public DatabaseTenantProvider(ApplicationDbContext context, IHttpContextAccessor accessor) 
        {
            var host = accessor.HttpContext.Request.Host.Value;

            context.AddSampleData();

            // This is for real life cases
            //_tenantId = context.Tenants.First(t => t.HostName == host).Id;

            _tenantId = context.Tenants.First(t => t.HostName == "imaginary.example.com").Id;
        }

        public Guid GetTenantId()
        {
            return _tenantId;
        }
    }
}

/Data/ApplicationDbContext.cs:

using System;
using System.Collections.Generic;
using System.Text;
using AthlosifyCore.Models;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;

namespace AthlosifyCore.Data
{
    public class ApplicationDbContext : IdentityDbContext
    {
        private readonly IHttpContextAccessor _httpContextAccessor;

        public DbSet<Tenant> Tenants { get; set; }
        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options,
                                    IHttpContextAccessor httpContextAccessor)
            : base(options)
        {
            _httpContextAccessor = httpContextAccessor;
        }
        public void AddSampleData()
        {
            Tenants.Add(new Tenant
            {
                Id = MultitenantDbContext.Tenant1Id,
                Name = "Imaginary corp.",
                HostName = "imaginary.example.com"
            });

            Tenants.Add(new Tenant
            {
                Id = MultitenantDbContext.Tenant2Id,
                Name = "The Very Big corp.",
                HostName = "big.example.com"
            });

            SaveChanges();
        }
    }
}

/Data/MultitenantDbContext.cs:

using AthlosifyCore.Models;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace AthlosifyCore.Data
{
    public class MultitenantDbContext : DbContext
    {
        public static Guid Tenant1Id = Guid.Parse("51aab199-1482-4f0d-8ff1-5ca0e7bc525a");
        public static Guid Tenant2Id = Guid.Parse("ae4e21fa-57cb-4733-b971-fdd14c4c667e");

        public DbSet<Person> People { get; set; }

        private ITenantProvider _tenantProvider;

        public MultitenantDbContext(DbContextOptions<MultitenantDbContext> options,
                                ITenantProvider tenantProvider) : base(options)
        {
            _tenantProvider = tenantProvider;
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);

            modelBuilder.Entity<Person>().HasQueryFilter(p => p.TenantId == _tenantProvider.GetTenantId());
        }

        public void AddSampleData()
        {
            People.Add(new Person
            {
                Id = Guid.Parse("79865406-e01b-422f-bd09-92e116a0664a"),
                TenantId = Tenant1Id,
                FirstName = "Gunnar",
                LastName = "Peipman"
            });

            People.Add(new Person
            {
                Id = Guid.Parse("d5674750-7f6b-43b9-b91b-d27b7ac13572"),
                TenantId = Tenant2Id,
                FirstName = "John",
                LastName = "Doe"
            });

            People.Add(new Person
            {
                Id = Guid.Parse("e41446f9-c779-4ff6-b3e5-752a3dad97bb"),
                TenantId = Tenant1Id,
                FirstName = "Mary",
                LastName = "Jones"
            });

            SaveChanges();
        }
    }
}

1 个答案:

答案 0 :(得分:0)

  

我们不能为MultiTenantDbContext做第二个...并且它一直说对象引用的问题没有设置为与`var host = accessor.HttpContext.Request.Host.Value;相关的对象的实例;在下面的/Models/Tenant.cs上。

这是因为当您通过add-migration init运行dotnet-tool时,根本没有HttpContext。换句话说,accessor.HttpContext目前为空,然后由于您正在访问Request的{​​{1}}属性而失败。

要修复该错误,请更改以下代码:

null

此外,为避免多次添加样本并导致错误:

    public DatabaseTenantProvider(TenantsDbContext context, IHttpContextAccessor accessor)
    {
        var host = accessor.HttpContext?.Request.Host.Value;

        // ....
    }

我们应该更改 Cannot insert duplicate key in object 'dbo.Tenants'. The duplicate key value is (51aab199-1482-4f0d-8ff1-5ca0e7bc525a). 类的AddSampleData()方法:

TenantsDbContext
  

我的第二个问题,我不明白为什么这个DatabaseTenantProvider类在MultiTenantDbContext的add-migration init中执行?!?!

这是因为public class TenantsDbContext : DbContext { // .... public void AddSampleData() { // add samples only if there's no record. if(Tenants.Count()==0){ Tenants.Add(new Tenant { Id = MultitenantDbContext.Tenant1Id, Name = "Imaginary corp.", HostName = "imaginary.example.com" }); Tenants.Add(new Tenant { Id = MultitenantDbContext.Tenant2Id, Name = "The Very Big corp.", HostName = "big.example.com" }); SaveChanges(); } } } 已注册为DatabaseTenantProvider服务的实现

ITenantProvider

services.AddTransient<ITenantProvider, DatabaseTenantProvider>() 类所必需的:

MultitenantDbContext

当EF Core调用public class MultitenantDbContext : DbContext { //... // note we inject the `ITenantProvider` service here! public MultitenantDbContext(DbContextOptions<MultitenantDbContext> options, ITenantProvider tenantProvider) : base(options) { _tenantProvider = tenantProvider; } } 方法时,它将随后调用MultitenantDbContext.OnModelCreating()服务(即ITenantProvider类)以获取ID。