【问题标题】:Error on add-migration - Object reference not set to an instance of an object添加迁移时出错 - 对象引用未设置为对象的实例
【发布时间】:2019-07-03 20:09:17
【问题描述】:

我们尝试这篇文章来实现基于数据库的租户提供程序 - 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();
        }
    }
}

【问题讨论】:

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


    【解决方案1】:

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

    那是因为当您通过 add-migration init 运行 dotnet-tool 时,根本就没有 HttpContext。换句话说,此时accessor.HttpContext 为空,然后在您访问nullRequest 属性时失败。

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

        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).
    

    我们应该改变TenantsDbContext类的AddSampleData()方法:

    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 类会在 MultiTenantDbContext 的 add-migration init 中执行?!?!

    那是因为DatabaseTenantProvider被注册为ITenantProvider服务的实现,

    services.AddTransient<ITenantProvider, DatabaseTenantProvider>()
    

    这是MultitenantDbContext 类所要求的:

    public class MultitenantDbContext : DbContext
    {
        //...
    
        // note we inject the `ITenantProvider` service here!
        public MultitenantDbContext(DbContextOptions<MultitenantDbContext> options,
                                    ITenantProvider tenantProvider) : base(options)
        {
            _tenantProvider = tenantProvider;
        }
    
    }
    

    当 EF Core 调用 MultitenantDbContext.OnModelCreating() 方法时,它将调用 ITenantProvider 服务(即 DatabaseTenantProvider 类)来获取 ID。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-06-04
      • 1970-01-01
      • 2011-07-09
      • 2012-08-31
      • 2011-11-21
      • 2012-10-01
      相关资源
      最近更新 更多