osharp多租户方案
租户信息
C#
复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace OSharp.MultiTenancy
{
/// <summary>
/// 租户信息
/// </summary>
public class TenantInfo
{
/// <summary>
/// 获取或设置 租户ID
/// </summary>
public string TenantId { get; set; }
/// <summary>
/// 获取或设置 租户名称
/// </summary>
public string Name { get; set; }
/// <summary>
/// 获取或设置 租户主机
/// </summary>
public string Host { get; set; }
/// <summary>
/// 获取或设置 连接字符串
/// </summary>
public string ConnectionString { get; set; }
/// <summary>
/// 获取或设置 是否启用
/// </summary>
public bool IsEnabled { get; set; } = true;
}
}
定义租户访问器实现
C#
复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace OSharp.MultiTenancy
{
// 租户访问器实现
public class TenantAccessor : ITenantAccessor
{
public TenantInfo CurrentTenant { get; set; }
}
/// <summary>
/// 使用 AsyncLocal<T> 实现的租户信息访问器
/// </summary>
public class AsyncLocalTenantAccessor : ITenantAccessor
{
// 使用 AsyncLocal<T> 存储租户信息
private static readonly AsyncLocal<TenantInfo> _currentTenant = new AsyncLocal<TenantInfo>();
/// <summary>
/// 获取或设置当前租户
/// </summary>
public TenantInfo CurrentTenant
{
get => _currentTenant.Value;
set => _currentTenant.Value = value;
}
}
}
修改MultiTenantConnectionStringProvider使用选定租户的数据库连接
C#
复制代码
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using OSharp.Entity;
using OSharp.MultiTenancy;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace OSharp.Entity
{
/// <summary>
/// 租户数据库选择器,实现IConnectionStringProvider接口
/// </summary>
public class MultiTenantConnectionStringProvider : IConnectionStringProvider
{
private readonly IConfiguration _configuration;
private readonly ITenantAccessor _tenantAccessor;
private readonly ILogger<MultiTenantConnectionStringProvider> _logger;
private readonly ConcurrentDictionary<string, string> _connectionStringCache = new ConcurrentDictionary<string, string>();
public MultiTenantConnectionStringProvider(
IConfiguration configuration,
ITenantAccessor tenantAccessor,
ILogger<MultiTenantConnectionStringProvider> logger)
{
_configuration = configuration;
_tenantAccessor = tenantAccessor;
_logger = logger;
}
/// <summary>
/// 获取数据库连接字符串
/// </summary>
public string GetConnectionString(Type dbContextType)
{
if(dbContextType.Name == "TenantDbContext")
{
return _configuration.GetConnectionString("Tenant");
}
// 获取当前租户
TenantInfo tenant = _tenantAccessor.CurrentTenant;
// 获取DbContext的连接字符串名称
string connectionStringName = dbContextType.Name.Replace("DbContext", "");
// 构建缓存键
string cacheKey = tenant?.TenantId + "_" + connectionStringName;
// 尝试从缓存获取连接字符串
if (!string.IsNullOrEmpty(cacheKey) && _connectionStringCache.TryGetValue(cacheKey, out string cachedConnectionString))
{
return cachedConnectionString;
}
string connectionString = null;
// 如果有租户信息
if (tenant != null)
{
// 1. 首先尝试使用租户自己的连接字符串
if (!string.IsNullOrEmpty(tenant.ConnectionString))
{
connectionString = tenant.ConnectionString;
_logger.LogDebug("使用租户 {TenantId} 的连接字符串", tenant.TenantId);
}
else
{
// 2. 尝试从配置中获取特定租户的特定DbContext连接字符串
string tenantConnectionStringKey = $"ConnectionStrings:{tenant.TenantId}:{connectionStringName}";
connectionString = _configuration[tenantConnectionStringKey];
// 3. 尝试从配置中获取特定租户的默认连接字符串
if (string.IsNullOrEmpty(connectionString))
{
string tenantDefaultConnectionStringKey = $"ConnectionStrings:{tenant.TenantId}:Default";
connectionString = _configuration[tenantDefaultConnectionStringKey];
if (!string.IsNullOrEmpty(connectionString))
{
_logger.LogDebug("使用租户 {TenantId} 的默认连接字符串", tenant.TenantId);
}
}
else
{
_logger.LogDebug("使用租户 {TenantId} 的 {DbContext} 连接字符串", tenant.TenantId, connectionStringName);
}
}
}
// 4. 如果仍未找到连接字符串,则使用应用程序的默认连接字符串
if (string.IsNullOrEmpty(connectionString))
{
// 尝试获取特定DbContext的连接字符串
connectionString = _configuration.GetConnectionString(connectionStringName);
// 如果没有特定DbContext的连接字符串,则使用默认连接字符串
if (string.IsNullOrEmpty(connectionString))
{
connectionString = _configuration.GetConnectionString("Default");
_logger.LogDebug("使用应用程序默认连接字符串");
}
else
{
_logger.LogDebug("使用应用程序 {DbContext} 连接字符串", connectionStringName);
}
}
// 缓存连接字符串
if (!string.IsNullOrEmpty(cacheKey) && !string.IsNullOrEmpty(connectionString))
{
_connectionStringCache[cacheKey] = connectionString;
}
return connectionString;
}
}
}
修改Program.cs
C#
复制代码
// -----------------------------------------------------------------------
// <copyright file="Program.cs" company="OSharp开源团队">
// Copyright (c) 2014-2018 OSharp. All rights reserved.
// </copyright>
// <site>http://www.osharp.org</site>
// <last-editor>郭明锋</last-editor>
// <last-date>2018-06-27 4:50</last-date>
// -----------------------------------------------------------------------
using AspectCore.Extensions.Hosting;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using OSharp.Core;
using Liuliu.Demo.Authorization;
using Liuliu.Demo.Identity;
using Liuliu.Demo.Infos;
using Liuliu.Demo.Systems;
using Liuliu.Demo.Web.Startups;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using OSharp.AspNetCore;
using OSharp.AspNetCore.Routing;
using OSharp.AutoMapper;
using OSharp.Log4Net;
using OSharp.MiniProfiler;
using OSharp.Swagger;
using Microsoft.AspNetCore.Http;
using OSharp.Entity;
using OSharp.MultiTenancy;
using OSharp.Entity.SqlServer;
using OSharp.Caching;
var builder = WebApplication.CreateBuilder(args);
builder.Services.Configure<FileTenantStoreOptions>(options =>
{
options.FilePath = "App_Data/tenants.json";
});
// 添加服务到容器
// 注册租户访问器
builder.Services.AddScoped<ITenantAccessor, AsyncLocalTenantAccessor>();
builder.Services.AddScoped<ITenantProvider, HttpTenantProvider>();
// 注册租户存储
//builder.Services.AddSingleton<ITenantStore, ConfigurationTenantStore>();
//builder.Services.AddSingleton<ITenantStore, FileTenantStore>();
builder.Services.AddSingleton<ITenantStore, DatabaseTenantStore>();
builder.Services.AddHttpContextAccessor(); // 注册 IHttpContextAccessor
// 注册租户数据库迁移器
builder.Services.AddSingleton<TenantDatabaseMigrator>();
// 替换默认的连接字符串提供者
//builder.Services.AddScoped<IConnectionStringProvider, MultiTenantConnectionStringProvider>();
builder.Services.Replace<IConnectionStringProvider, MultiTenantConnectionStringProvider>(ServiceLifetime.Scoped);
builder.Services.AddSingleton<ICacheKeyGenerator, StringCacheKeyGenerator>();
builder.Services.AddSingleton<IGlobalCacheKeyGenerator>(provider =>
provider.GetRequiredService<ICacheKeyGenerator>() as IGlobalCacheKeyGenerator);
//builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddOSharp()
.AddPack<Log4NetPack>()
.AddPack<AutoMapperPack>()
.AddPack<EndpointsPack>()
.AddPack<MiniProfilerPack>()
.AddPack<SwaggerPack>()
//.AddPack<RedisPack>()
.AddPack<AuthenticationPack>()
.AddPack<FunctionAuthorizationPack>()
.AddPack<DataAuthorizationPack>()
.AddPack<SqlServerDefaultDbContextMigrationPack>()
.AddPack<TenantDbContextMigrationPack>()
.AddPack<AuditPack>()
.AddPack<InfosPack>();
var app = builder.Build();
// 在请求管道中注册 TenantMiddleware
// 注意:应该在 UseRouting 之后,但在 UseAuthentication 和 UseAuthorization 之前注册
app.UseRouting();
// 添加多租户中间件
app.UseMiddleware<TenantMiddleware>();
// 使用 OSharp
app.UseOSharp();
using (var scope = app.Services.CreateScope())
{
var migrator = scope.ServiceProvider.GetRequiredService<TenantDatabaseMigrator>();
await migrator.MigrateAllTenantsAsync();
}
app.Run();
将所有的数据库配置文件中的HasName改成HasDatabaseName
将项目设置为.net8.0
租户存储管理
C#
复制代码
//ITenantStore
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace OSharp.MultiTenancy
{
/// <summary>
/// 定义租户存储
/// </summary>
public interface ITenantStore
{
/// <summary>
/// 获取所有租户
/// </summary>
Task<IEnumerable<TenantInfo>> GetAllTenantsAsync();
/// <summary>
/// 根据租户ID获取租户
/// </summary>
Task<TenantInfo> GetTenantAsync(string tenantId);
/// <summary>
/// 根据主机名获取租户
/// </summary>
Task<TenantInfo> GetTenantByHostAsync(string host);
/// <summary>
/// 保存租户信息
/// </summary>
Task<bool> SaveTenantAsync(TenantInfo tenant);
/// <summary>
/// 删除租户
/// </summary>
Task<bool> DeleteTenantAsync(string tenantId);
}
}
C#
复制代码
//FileTenantStore
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
namespace OSharp.MultiTenancy
{
/// <summary>
/// 基于文件的租户存储实现,将租户信息保存在JSON文件中
/// </summary>
public class FileTenantStore : ITenantStore
{
private readonly FileTenantStoreOptions _options;
private readonly ILogger<FileTenantStore> _logger;
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
private Dictionary<string, TenantInfo> _tenants = new Dictionary<string, TenantInfo>();
private bool _isInitialized = false;
public FileTenantStore(
IOptions<FileTenantStoreOptions> options,
ILogger<FileTenantStore> logger)
{
_options = options.Value;
_logger = logger;
}
/// <summary>
/// 获取所有启用的租户
/// </summary>
public async Task<IEnumerable<TenantInfo>> GetAllTenantsAsync()
{
await EnsureInitializedAsync();
return _tenants.Values.Where(t => t.IsEnabled).ToList();
}
/// <summary>
/// 根据租户ID获取租户信息
/// </summary>
public async Task<TenantInfo> GetTenantAsync(string tenantId)
{
if (string.IsNullOrEmpty(tenantId))
{
return null;
}
await EnsureInitializedAsync();
if (_tenants.TryGetValue(tenantId, out var tenant) && tenant.IsEnabled)
{
return tenant;
}
return null;
}
/// <summary>
/// 根据主机名获取租户信息
/// </summary>
public async Task<TenantInfo> GetTenantByHostAsync(string host)
{
if (string.IsNullOrEmpty(host))
{
return null;
}
await EnsureInitializedAsync();
return _tenants.Values
.FirstOrDefault(t => t.IsEnabled &&
(t.Host.Equals(host, StringComparison.OrdinalIgnoreCase) ||
host.EndsWith("." + t.Host, StringComparison.OrdinalIgnoreCase)));
}
/// <summary>
/// 保存租户信息
/// </summary>
public async Task<bool> SaveTenantAsync(TenantInfo tenant)
{
if (tenant == null || string.IsNullOrEmpty(tenant.TenantId))
{
return false;
}
await EnsureInitializedAsync();
await _semaphore.WaitAsync();
try
{
// 更新内存中的租户信息
_tenants[tenant.TenantId] = tenant;
// 保存到文件
await SaveToFileAsync();
_logger.LogInformation("已保存租户信息: {TenantId}, {Name}, {Host}",
tenant.TenantId, tenant.Name, tenant.Host);
return true;
}
catch (Exception ex)
{
_logger.LogError(ex, "保存租户信息失败: {TenantId}", tenant.TenantId);
return false;
}
finally
{
_semaphore.Release();
}
}
/// <summary>
/// 删除租户信息
/// </summary>
public async Task<bool> DeleteTenantAsync(string tenantId)
{
if (string.IsNullOrEmpty(tenantId))
{
return false;
}
await EnsureInitializedAsync();
await _semaphore.WaitAsync();
try
{
if (!_tenants.ContainsKey(tenantId))
{
return false;
}
// 从内存中移除租户
_tenants.Remove(tenantId);
// 保存到文件
await SaveToFileAsync();
_logger.LogInformation("已删除租户: {TenantId}", tenantId);
return true;
}
catch (Exception ex)
{
_logger.LogError(ex, "删除租户失败: {TenantId}", tenantId);
return false;
}
finally
{
_semaphore.Release();
}
}
/// <summary>
/// 确保已初始化
/// </summary>
private async Task EnsureInitializedAsync()
{
if (_isInitialized)
{
return;
}
await _semaphore.WaitAsync();
try
{
if (_isInitialized)
{
return;
}
await LoadFromFileAsync();
_isInitialized = true;
}
finally
{
_semaphore.Release();
}
}
/// <summary>
/// 从文件加载租户信息
/// </summary>
private async Task LoadFromFileAsync()
{
string filePath = GetFilePath();
if (!File.Exists(filePath))
{
_logger.LogInformation("租户配置文件不存在,将创建新文件: {FilePath}", filePath);
_tenants = new Dictionary<string, TenantInfo>();
await SaveToFileAsync(); // 创建空文件
return;
}
try
{
string json = await File.ReadAllTextAsync(filePath);
var tenants = JsonSerializer.Deserialize<List<TenantInfo>>(json, new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
});
_tenants = tenants.ToDictionary(t => t.TenantId);
_logger.LogInformation("已从文件加载 {Count} 个租户", _tenants.Count);
}
catch (Exception ex)
{
_logger.LogError(ex, "从文件加载租户信息失败: {FilePath}", filePath);
_tenants = new Dictionary<string, TenantInfo>();
}
}
/// <summary>
/// 保存租户信息到文件
/// </summary>
private async Task SaveToFileAsync()
{
string filePath = GetFilePath();
string directoryPath = Path.GetDirectoryName(filePath);
if (!Directory.Exists(directoryPath))
{
Directory.CreateDirectory(directoryPath);
}
try
{
var tenants = _tenants.Values.ToList();
string json = JsonSerializer.Serialize(tenants, new JsonSerializerOptions
{
WriteIndented = true
});
await File.WriteAllTextAsync(filePath, json);
_logger.LogDebug("已将 {Count} 个租户信息保存到文件: {FilePath}", tenants.Count, filePath);
}
catch (Exception ex)
{
_logger.LogError(ex, "保存租户信息到文件失败: {FilePath}", filePath);
throw;
}
}
/// <summary>
/// 获取文件路径
/// </summary>
private string GetFilePath()
{
return Path.GetFullPath(_options.FilePath);
}
}
/// <summary>
/// 文件租户存储选项
/// </summary>
public class FileTenantStoreOptions
{
/// <summary>
/// 租户配置文件路径,默认为 "App_Data/tenants.json"
/// </summary>
public string FilePath { get; set; } = "App_Data/tenants.json";
}
}
C#
复制代码
//ConfigurationTenantStore
using Microsoft.Extensions.Configuration;
using OSharp.MultiTenancy;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace OSharp.MultiTenancy
{
/// <summary>
/// 基于配置文件的租户存储实现
/// </summary>
public class ConfigurationTenantStore : ITenantStore
{
private readonly IConfiguration _configuration;
private readonly ILogger<ConfigurationTenantStore> _logger;
private readonly Dictionary<string, TenantInfo> _tenants = new Dictionary<string, TenantInfo>();
public ConfigurationTenantStore(
IConfiguration configuration,
ILogger<ConfigurationTenantStore> logger)
{
_configuration = configuration;
_logger = logger;
// 从配置中加载租户信息
LoadTenantsFromConfiguration();
}
public Task<bool> DeleteTenantAsync(string tenantId)
{
throw new NotImplementedException();
}
public Task<IEnumerable<TenantInfo>> GetAllTenantsAsync()
{
return Task.FromResult(_tenants.Values.Where(t => t.IsEnabled).AsEnumerable());
}
public Task<TenantInfo> GetTenantAsync(string tenantId)
{
if (string.IsNullOrEmpty(tenantId) || !_tenants.TryGetValue(tenantId, out var tenant) || !tenant.IsEnabled)
{
return Task.FromResult<TenantInfo>(null);
}
return Task.FromResult(tenant);
}
public Task<TenantInfo> GetTenantByHostAsync(string host)
{
if (string.IsNullOrEmpty(host))
{
return Task.FromResult<TenantInfo>(null);
}
var tenant = _tenants.Values
.FirstOrDefault(t => t.IsEnabled &&
(t.Host.Equals(host, StringComparison.OrdinalIgnoreCase) ||
host.EndsWith("." + t.Host, StringComparison.OrdinalIgnoreCase)));
return Task.FromResult(tenant);
}
public Task<bool> SaveTenantAsync(TenantInfo tenant)
{
// 配置文件实现通常是只读的,不支持保存
_logger.LogWarning("ConfigurationTenantStore 不支持保存租户信息");
return Task.FromResult(false);
}
private void LoadTenantsFromConfiguration()
{
var tenantsSection = _configuration.GetSection("Tenants");
if (!tenantsSection.Exists())
{
_logger.LogWarning("在配置中未找到 'Tenants' 节点");
return;
}
foreach (var tenantSection in tenantsSection.GetChildren())
{
var tenant = new TenantInfo
{
TenantId = tenantSection.Key,
Name = tenantSection["Name"],
Host = tenantSection["Host"],
ConnectionString = tenantSection["ConnectionString"],
IsEnabled = tenantSection.GetValue<bool>("IsEnabled", true)
};
if (!string.IsNullOrEmpty(tenant.TenantId) && !string.IsNullOrEmpty(tenant.Host))
{
_tenants[tenant.TenantId] = tenant;
_logger.LogInformation("已加载租户: {TenantId}, {Name}, {Host}", tenant.TenantId, tenant.Name, tenant.Host);
}
else
{
_logger.LogWarning("租户配置不完整,已跳过: {TenantId}", tenant.TenantId);
}
}
}
}
}
基于数据库的租户存储实现
C#
复制代码
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using OSharp.MultiTenancy;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Threading.Tasks;
namespace OSharp.Entity
{
/// <summary>
/// 基于数据库的租户存储实现
/// </summary>
public class DatabaseTenantStore : ITenantStore
{
private readonly IServiceProvider _serviceProvider;
private readonly IMemoryCache _cache;
private readonly ILogger<DatabaseTenantStore> _logger;
private readonly TimeSpan _cacheExpiration = TimeSpan.FromMinutes(10);
private const string CacheKeyPrefix = "Tenant_";
private const string AllTenantsCacheKey = "AllTenants";
private readonly IConfiguration _configuration;
public DatabaseTenantStore(
IServiceProvider serviceProvider,
IMemoryCache cache,
IConfiguration configuration,
ILogger<DatabaseTenantStore> logger)
{
_serviceProvider = serviceProvider;
_cache = cache;
_logger = logger;
_configuration = configuration;
}
/// <summary>
/// 获取所有启用的租户
/// </summary>
public async Task<IEnumerable<TenantInfo>> GetAllTenantsAsync()
{
// 尝试从缓存获取
if (_cache.TryGetValue(AllTenantsCacheKey, out IEnumerable<TenantInfo> cachedTenants))
{
return cachedTenants;
}
// 从数据库获取
using var scope = _serviceProvider.CreateScope();
var TenantRepository = scope.ServiceProvider.GetRequiredService<IRepository<TenantEntity,Guid>>();
var tenantEntities = await TenantRepository.QueryAsNoTracking().Where(t => t.IsEnabled).ToListAsync();
if(tenantEntities.Count == 0)
{
await ImportFromConfigurationAsync(_configuration);
}
tenantEntities = await TenantRepository.QueryAsNoTracking().Where(t => t.IsEnabled).ToListAsync();
var tenants = tenantEntities.Select(MapToTenantInfo).ToList();
// 缓存结果
_cache.Set(AllTenantsCacheKey, tenants, _cacheExpiration);
return tenants;
}
/// <summary>
/// 根据租户ID获取租户
/// </summary>
public async Task<TenantInfo> GetTenantAsync(string tenantId)
{
if (string.IsNullOrEmpty(tenantId))
{
return null;
}
// 尝试从缓存获取
string cacheKey = $"{CacheKeyPrefix}{tenantId}";
if (_cache.TryGetValue(cacheKey, out TenantInfo cachedTenant))
{
return cachedTenant;
}
// 从数据库获取
using var scope = _serviceProvider.CreateScope();
var TenantRepository = scope.ServiceProvider.GetRequiredService<IRepository<TenantEntity, Guid>>();
var tenantEntity = await TenantRepository.QueryAsNoTracking().Where(t => t.TenantId == tenantId).FirstOrDefaultAsync();
if (tenantEntity == null)
{
return null;
}
var tenant = MapToTenantInfo(tenantEntity);
// 缓存结果
_cache.Set(cacheKey, tenant, _cacheExpiration);
return tenant;
}
/// <summary>
/// 根据主机名获取租户
/// </summary>
public async Task<TenantInfo> GetTenantByHostAsync(string host)
{
if (string.IsNullOrEmpty(host))
{
return null;
}
// 获取所有租户
var allTenants = await GetAllTenantsAsync();
// 查找匹配的租户
return allTenants.FirstOrDefault(t =>
t.Host.Equals(host, StringComparison.OrdinalIgnoreCase) ||
host.EndsWith("." + t.Host, StringComparison.OrdinalIgnoreCase));
}
/// <summary>
/// 保存租户信息
/// </summary>
public async Task<bool> SaveTenantAsync(TenantInfo tenant)
{
if (tenant == null || string.IsNullOrEmpty(tenant.TenantId))
{
return false;
}
using var scope = _serviceProvider.CreateScope();
var TenantRepository = scope.ServiceProvider.GetRequiredService<IRepository<TenantEntity, Guid>>();
var existingEntity = await TenantRepository.Query().Where(t => t.TenantId == tenant.TenantId).FirstOrDefaultAsync();
try
{
if (existingEntity == null)
{
// 创建新租户
var newEntity = new TenantEntity
{
TenantId = tenant.TenantId,
Name = tenant.Name,
Host = tenant.Host,
ConnectionString = tenant.ConnectionString,
IsEnabled = tenant.IsEnabled,
CreatedTime = DateTime.Now
};
await TenantRepository.InsertAsync(newEntity);
_logger.LogInformation("创建新租户: {TenantId}, {Name}", tenant.TenantId, tenant.Name);
}
else
{
// 更新现有租户
existingEntity.Name = tenant.Name;
existingEntity.Host = tenant.Host;
existingEntity.ConnectionString = tenant.ConnectionString;
existingEntity.IsEnabled = tenant.IsEnabled;
existingEntity.UpdatedTime = DateTime.Now;
await TenantRepository.UpdateAsync(existingEntity);
_logger.LogInformation("更新租户: {TenantId}, {Name}", tenant.TenantId, tenant.Name);
}
// 清除缓存
InvalidateCache(tenant.TenantId);
return true;
}
catch (Exception ex)
{
_logger.LogError(ex, "保存租户信息失败: {TenantId}", tenant.TenantId);
return false;
}
}
/// <summary>
/// 删除租户
/// </summary>
public async Task<bool> DeleteTenantAsync(string tenantId)
{
if (string.IsNullOrEmpty(tenantId))
{
return false;
}
using var scope = _serviceProvider.CreateScope();
var TenantRepository = scope.ServiceProvider.GetRequiredService<IRepository<TenantEntity, Guid>>();
var tenantEntity = await TenantRepository.Query().Where(t => t.TenantId == tenantId).FirstOrDefaultAsync();
if (tenantEntity == null)
{
return false;
}
try
{
await TenantRepository.DeleteAsync(tenantEntity);
// 清除缓存
InvalidateCache(tenantId);
_logger.LogInformation("删除租户: {TenantId}", tenantId);
return true;
}
catch (Exception ex)
{
_logger.LogError(ex, "删除租户失败: {TenantId}", tenantId);
return false;
}
}
/// <summary>
/// 从配置导入租户信息到数据库
/// </summary>
public async Task<int> ImportFromConfigurationAsync(IConfiguration configuration)
{
var tenantsSection = configuration.GetSection("Tenants");
if (!tenantsSection.Exists())
{
_logger.LogWarning("在配置中未找到 'Tenants' 节点");
return 0;
}
int importCount = 0;
foreach (var tenantSection in tenantsSection.GetChildren())
{
var tenant = new TenantInfo
{
TenantId = tenantSection.Key,
Name = tenantSection["Name"],
Host = tenantSection["Host"],
ConnectionString = tenantSection["ConnectionString"],
IsEnabled = tenantSection.GetValue<bool>("IsEnabled", true)
};
// 如果ConnectionString为空,尝试从ConnectionStrings节点获取
if (string.IsNullOrEmpty(tenant.ConnectionString))
{
string connectionStringKey = $"ConnectionStrings:{tenant.TenantId}:Default";
tenant.ConnectionString = configuration[connectionStringKey];
// 如果仍然为空,尝试直接获取租户ID对应的连接字符串
if (string.IsNullOrEmpty(tenant.ConnectionString))
{
tenant.ConnectionString = configuration.GetConnectionString(tenant.TenantId);
}
}
if (!string.IsNullOrEmpty(tenant.TenantId) && !string.IsNullOrEmpty(tenant.Host))
{
bool success = await SaveTenantAsync(tenant);
if (success)
{
importCount++;
_logger.LogInformation("已导入租户: {TenantId}, {Name}, {Host}",
tenant.TenantId, tenant.Name, tenant.Host);
}
}
else
{
_logger.LogWarning("租户配置不完整,已跳过: {TenantId}", tenant.TenantId);
}
}
// 清除所有缓存
_cache.Remove(AllTenantsCacheKey);
return importCount;
}
/// <summary>
/// 将实体映射为租户信息
/// </summary>
private TenantInfo MapToTenantInfo(TenantEntity entity)
{
return new TenantInfo
{
TenantId = entity.TenantId,
Name = entity.Name,
Host = entity.Host,
ConnectionString = entity.ConnectionString,
IsEnabled = entity.IsEnabled
};
}
/// <summary>
/// 使缓存失效
/// </summary>
private void InvalidateCache(string tenantId)
{
_cache.Remove($"{CacheKeyPrefix}{tenantId}");
_cache.Remove(AllTenantsCacheKey);
}
}
}
TenantEntity
c#
复制代码
using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using OSharp.Entity;
namespace OSharp.MultiTenancy
{
/// <summary>
/// 租户数据库实体
/// </summary>
[Description("租户信息")]
public class TenantEntity : EntityBase<Guid>
{
/// <summary>
/// 获取或设置 租户ID
/// </summary>
[Required, StringLength(50)]
[Description("租户ID")]
public string TenantId { get; set; }
/// <summary>
/// 获取或设置 租户名称
/// </summary>
[Required, StringLength(100)]
[Description("租户名称")]
public string Name { get; set; }
/// <summary>
/// 获取或设置 租户主机
/// </summary>
[Required, StringLength(100)]
[Description("租户主机")]
public string Host { get; set; }
/// <summary>
/// 获取或设置 连接字符串
/// </summary>
[StringLength(1000)]
[Description("连接字符串")]
public string ConnectionString { get; set; }
/// <summary>
/// 获取或设置 是否启用
/// </summary>
[Description("是否启用")]
public bool IsEnabled { get; set; } = true;
/// <summary>
/// 获取或设置 创建时间
/// </summary>
[Description("创建时间")]
public DateTime CreatedTime { get; set; } = DateTime.Now;
/// <summary>
/// 获取或设置 更新时间
/// </summary>
[Description("更新时间")]
public DateTime? UpdatedTime { get; set; }
}
}
TenantDbContext
C#
复制代码
using Microsoft.EntityFrameworkCore;
using OSharp.MultiTenancy;
namespace OSharp.Entity
{
/// <summary>
/// 租户数据库上下文
/// </summary>
public class TenantDbContext : DbContextBase
{
/// <summary>
/// 初始化一个<see cref="TenantDbContext"/>类型的新实例
/// </summary>
public TenantDbContext(DbContextOptions<TenantDbContext> options, IServiceProvider serviceProvider)
: base(options, serviceProvider)
{
}
}
}
C#
复制代码
//TenantEntityConfiguration
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using OSharp.Entity;
using OSharp.MultiTenancy;
using System;
namespace Liuliu.Demo.EntityConfiguration
{
/// <summary>
/// 租户实体类型配置
/// </summary>
public partial class TenantEntityConfiguration : EntityTypeConfigurationBase<TenantEntity, Guid>
{
public override Type DbContextType { get; } = typeof(TenantDbContext);
/// <summary>
/// 重写以实现实体类型各个属性的数据库配置
/// </summary>
/// <param name="builder">实体类型构建器</param>
public override void Configure(EntityTypeBuilder<TenantEntity> builder)
{
// 配置表名
builder.ToTable("Tenants");
// 配置属性
builder.Property(m => m.TenantId)
.IsRequired()
.HasMaxLength(50)
.HasComment("租户ID");
builder.Property(m => m.Name)
.IsRequired()
.HasMaxLength(100)
.HasComment("租户名称");
builder.Property(m => m.Host)
.IsRequired()
.HasMaxLength(100)
.HasComment("租户主机");
builder.Property(m => m.ConnectionString)
.HasMaxLength(1000)
.HasComment("连接字符串");
builder.Property(m => m.IsEnabled)
.IsRequired()
.HasDefaultValue(true)
.HasComment("是否启用");
builder.Property(m => m.CreatedTime)
.IsRequired()
.HasComment("创建时间");
builder.Property(m => m.UpdatedTime)
.HasComment("更新时间");
// 配置索引
builder.HasIndex(m => m.TenantId)
.IsUnique()
.HasDatabaseName("IX_Tenants_TenantId");
builder.HasIndex(m => m.Host)
.HasDatabaseName("IX_Tenants_Host");
EntityConfigurationAppend(builder);
}
/// <summary>
/// 额外的数据映射
/// </summary>
partial void EntityConfigurationAppend(EntityTypeBuilder<TenantEntity> builder);
}
}
定义租户提供者
C#
复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace OSharp.MultiTenancy
{
/// <summary>
/// 定义租户提供者
/// </summary>
public interface ITenantProvider
{
/// <summary>
/// 获取当前租户信息
/// </summary>
/// <returns>租户信息</returns>
TenantInfo GetCurrentTenant();
/// <summary>
/// 异步获取当前租户信息
/// </summary>
/// <returns>租户信息</returns>
Task<TenantInfo> GetCurrentTenantAsync();
/// <summary>
/// 根据租户标识获取租户信息
/// </summary>
/// <param name="identifier">租户标识</param>
/// <returns>租户信息</returns>
TenantInfo GetTenant(string identifier);
/// <summary>
/// 异步根据租户标识获取租户信息
/// </summary>
/// <param name="identifier">租户标识</param>
/// <returns>租户信息</returns>
Task<TenantInfo> GetTenantAsync(string identifier);
}
}
C#
复制代码
//HttpTenantProvider
using OSharp.Dependency;
using OSharp.MultiTenancy;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
namespace Liuliu.Demo.Web.Startups
{
/// <summary>
/// 基于HTTP请求的租户提供者实现,支持多种方式识别租户
/// </summary>
public class HttpTenantProvider : ITenantProvider
{
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IConfiguration _configuration;
private readonly ILogger<HttpTenantProvider> _logger;
private readonly ITenantAccessor _tenantAccessor; // 添加租户访问器
private readonly ITenantStore _tenantStore; // 使用 ITenantStore
// 租户识别方式配置
private readonly TenantResolveOptions _resolveOptions;
public HttpTenantProvider(
IHttpContextAccessor httpContextAccessor,
IConfiguration configuration,
ILogger<HttpTenantProvider> logger,
ITenantStore tenantStore,
ITenantAccessor tenantAccessor)
{
_httpContextAccessor = httpContextAccessor;
_configuration = configuration;
_logger = logger;
_tenantAccessor = tenantAccessor;
_tenantStore = tenantStore;
// 初始化租户识别方式配置
_resolveOptions = new TenantResolveOptions();
ConfigureTenantResolveOptions();
}
/// <summary>
/// 获取当前租户信息
/// </summary>
public async Task<TenantInfo> GetCurrentTenantAsync()
{
// 首先检查租户访问器中是否已有租户信息
if (_tenantAccessor.CurrentTenant != null)
{
return _tenantAccessor.CurrentTenant;
}
HttpContext httpContext = _httpContextAccessor.HttpContext;
if (httpContext == null)
{
return null;
}
// 尝试使用多种方式识别租户
TenantInfo tenant = null;
// 按照优先级顺序尝试不同的租户识别方式
foreach (var resolver in _resolveOptions.Resolvers.OrderBy(r => r.Priority))
{
tenant = await resolver.ResolveTenantAsync(httpContext, _tenantStore);
if (tenant != null)
{
// 将解析到的租户设置到租户访问器中
_tenantAccessor.CurrentTenant = tenant;
return tenant;
}
}
return tenant;
}
/// <summary>
/// 根据租户标识获取租户信息
/// </summary>
public TenantInfo GetTenant(string identifier)
{
if (string.IsNullOrEmpty(identifier))
{
return null;
}
return GetTenantAsync(identifier).GetAwaiter().GetResult();
}
/// <summary>
/// 异步根据租户标识获取租户信息
/// </summary>
public async Task<TenantInfo> GetTenantAsync(string identifier)
{
if (string.IsNullOrEmpty(identifier))
{
return null;
}
TenantInfo tenant = await _tenantStore.GetTenantAsync(identifier);
return tenant?.IsEnabled == true ? tenant : null;
}
/// <summary>
/// 配置租户识别方式
/// </summary>
private void ConfigureTenantResolveOptions()
{
// 从配置中读取租户识别方式配置
var resolveSection = _configuration.GetSection("MultiTenancy:TenantResolve");
// 添加域名解析器(默认启用,优先级最高)
bool enableDomain = resolveSection.GetValue<bool>("EnableDomain", true);
if (enableDomain)
{
_resolveOptions.AddResolver(new DomainTenantResolver(), 100);
}
// 添加请求头解析器
bool enableHeader = resolveSection.GetValue<bool>("EnableHeader", false);
string headerName = resolveSection.GetValue<string>("HeaderName", "X-Tenant");
if (enableHeader)
{
_resolveOptions.AddResolver(new HeaderTenantResolver(headerName), 200);
}
// 添加查询参数解析器
bool enableQueryString = resolveSection.GetValue<bool>("EnableQueryString", false);
string queryStringName = resolveSection.GetValue<string>("QueryStringName", "tenant");
if (enableQueryString)
{
_resolveOptions.AddResolver(new QueryStringTenantResolver(queryStringName), 300);
}
// 添加Cookie解析器
bool enableCookie = resolveSection.GetValue<bool>("EnableCookie", false);
string cookieName = resolveSection.GetValue<string>("CookieName", "tenant");
if (enableCookie)
{
_resolveOptions.AddResolver(new CookieTenantResolver(cookieName), 400);
}
// 添加Claims解析器
bool enableClaim = resolveSection.GetValue<bool>("EnableClaim", false);
string claimType = resolveSection.GetValue<string>("ClaimType", "tenant");
if (enableClaim)
{
_resolveOptions.AddResolver(new ClaimTenantResolver(claimType), 500);
}
// 添加路由解析器
bool enableRoute = resolveSection.GetValue<bool>("EnableRoute", false);
string routeParamName = resolveSection.GetValue<string>("RouteParamName", "tenant");
if (enableRoute)
{
_resolveOptions.AddResolver(new RouteTenantResolver(routeParamName), 600);
}
_logger.LogInformation("已配置租户识别方式: Domain={0}, Header={1}, QueryString={2}, Cookie={3}, Claim={4}, Route={5}",
enableDomain, enableHeader, enableQueryString, enableCookie, enableClaim, enableRoute);
}
public TenantInfo GetCurrentTenant()
{
return GetCurrentTenantAsync().Result;
}
}
/// <summary>
/// 租户识别选项
/// </summary>
public class TenantResolveOptions
{
public List<ITenantResolver> Resolvers { get; } = new List<ITenantResolver>();
public void AddResolver(ITenantResolver resolver, int priority)
{
resolver.Priority = priority;
Resolvers.Add(resolver);
}
}
/// <summary>
/// 租户解析器接口
/// </summary>
public interface ITenantResolver
{
/// <summary>
/// 优先级,数值越小优先级越高
/// </summary>
int Priority { get; set; }
/// <summary>
/// 解析租户
/// </summary>
Task<TenantInfo> ResolveTenantAsync(HttpContext context, ITenantStore tenantStore);
}
/// <summary>
/// 基于域名的租户解析器
/// </summary>
public class DomainTenantResolver : ITenantResolver
{
public int Priority { get; set; }
public async Task<TenantInfo> ResolveTenantAsync(HttpContext context, ITenantStore tenantStore)
{
string host = context.Request.Host.Host.ToLower();
if (string.IsNullOrEmpty(host))
{
return null;
}
// 获取所有租户并查找匹配的租户
var tenants = await tenantStore.GetAllTenantsAsync();
return tenants.FirstOrDefault(t =>
t.IsEnabled &&
(t.Host.Equals(host, StringComparison.OrdinalIgnoreCase) ||
host.EndsWith("." + t.Host, StringComparison.OrdinalIgnoreCase)));
}
}
/// <summary>
/// 基于请求头的租户解析器
/// </summary>
public class HeaderTenantResolver : ITenantResolver
{
private readonly string _headerName;
public HeaderTenantResolver(string headerName)
{
_headerName = headerName;
}
public int Priority { get; set; }
public async Task<TenantInfo> ResolveTenantAsync(HttpContext context, ITenantStore tenantStore)
{
if (!context.Request.Headers.TryGetValue(_headerName, out var values) || values.Count == 0)
{
return null;
}
string tenantId = values.First();
if (string.IsNullOrEmpty(tenantId))
{
return null;
}
return await tenantStore.GetTenantAsync(tenantId);
}
}
/// <summary>
/// 基于查询参数的租户解析器
/// </summary>
public class QueryStringTenantResolver : ITenantResolver
{
private readonly string _paramName;
public QueryStringTenantResolver(string paramName)
{
_paramName = paramName;
}
public int Priority { get; set; }
public async Task<TenantInfo> ResolveTenantAsync(HttpContext context, ITenantStore tenantStore)
{
if (!context.Request.Query.TryGetValue(_paramName, out var values) || values.Count == 0)
{
return null;
}
string tenantId = values.First();
if (string.IsNullOrEmpty(tenantId))
{
return null;
}
return await tenantStore.GetTenantAsync(tenantId);
}
}
/// <summary>
/// 基于Cookie的租户解析器
/// </summary>
public class CookieTenantResolver : ITenantResolver
{
private readonly string _cookieName;
public CookieTenantResolver(string cookieName)
{
_cookieName = cookieName;
}
public int Priority { get; set; }
public async Task<TenantInfo> ResolveTenantAsync(HttpContext context, ITenantStore tenantStore)
{
if (!context.Request.Cookies.TryGetValue(_cookieName, out string tenantId) || string.IsNullOrEmpty(tenantId))
{
return null;
}
return await tenantStore.GetTenantAsync(tenantId);
}
}
/// <summary>
/// 基于Claims的租户解析器
/// </summary>
public class ClaimTenantResolver : ITenantResolver
{
private readonly string _claimType;
public ClaimTenantResolver(string claimType)
{
_claimType = claimType;
}
public int Priority { get; set; }
public async Task<TenantInfo> ResolveTenantAsync(HttpContext context, ITenantStore tenantStore)
{
if (!context.User.Identity.IsAuthenticated)
{
return null;
}
var claim = context.User.Claims.FirstOrDefault(c => c.Type == _claimType);
if (claim == null || string.IsNullOrEmpty(claim.Value))
{
return null;
}
var tenantId = claim.Value;
return await tenantStore.GetTenantAsync(tenantId);
}
}
/// <summary>
/// 基于路由参数的租户解析器
/// </summary>
public class RouteTenantResolver : ITenantResolver
{
private readonly string _routeParamName;
public RouteTenantResolver(string routeParamName)
{
_routeParamName = routeParamName;
}
public int Priority { get; set; }
public async Task<TenantInfo> ResolveTenantAsync(HttpContext context, ITenantStore tenantStore)
{
if (!context.Request.RouteValues.TryGetValue(_routeParamName, out var value) || value == null)
{
return null;
}
string tenantId = value.ToString();
if (string.IsNullOrEmpty(tenantId))
{
return null;
}
return await tenantStore.GetTenantAsync(tenantId);
}
}
}
定义租户中间件
C#
复制代码
using Microsoft.AspNetCore.Http;
using OSharp.MultiTenancy;
using System;
using System.Threading.Tasks;
namespace Liuliu.Demo.Web.Startups
{
/// <summary>
/// 多租户中间件,用于在请求处理过程中设置当前租户
/// </summary>
public class TenantMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<TenantMiddleware> _logger;
public TenantMiddleware(
RequestDelegate next,
ILogger<TenantMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context, ITenantProvider tenantProvider, ITenantAccessor tenantAccessor)
{
try
{
// 解析当前租户
TenantInfo tenant = await tenantProvider.GetCurrentTenantAsync();
// 设置当前租户到访问器中
if (tenant != null)
{
tenantAccessor.CurrentTenant = tenant;
_logger.LogDebug("已设置当前租户: {TenantId}, {Name}", tenant.TenantId, tenant.Name);
// 可以在这里添加租户相关的请求头或其他信息
context.Items["CurrentTenant"] = tenant;
}
else
{
_logger.LogDebug("未识别到租户信息");
}
}
catch (Exception ex)
{
_logger.LogError(ex, "处理租户信息时发生错误");
}
// 继续处理请求
await _next(context);
}
}
}
数据库迁移器TenantDatabaseMigrator
C#
复制代码
using OSharp.MultiTenancy;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using OSharp.Entity;
using Microsoft.EntityFrameworkCore;
using OSharp.Authorization.Modules;
using OSharp.Authorization.Functions;
using OSharp.AspNetCore.Mvc;
using OSharp.Authorization;
using OSharp.Authorization.EntityInfos;
namespace Liuliu.Demo.Web.Startups
{
public class TenantDatabaseMigrator
{
private readonly IServiceProvider _serviceProvider;
private readonly ITenantStore _tenantStore;
private readonly ILogger<TenantDatabaseMigrator> _logger;
public TenantDatabaseMigrator(
IServiceProvider serviceProvider,
ITenantStore tenantStore,
ILogger<TenantDatabaseMigrator> logger)
{
_serviceProvider = serviceProvider;
_tenantStore = tenantStore;
_logger = logger;
}
/// <summary>
/// 迁移所有租户数据库
/// </summary>
public async Task MigrateAllTenantsAsync()
{
// 获取所有租户
var tenants = await _tenantStore.GetAllTenantsAsync();
foreach (var tenant in tenants)
{
try
{
await MigrateTenantDatabaseAsync(tenant);
}
catch (Exception ex)
{
_logger.LogError(ex, "迁移租户 {TenantId} 的数据库时出错", tenant.TenantId);
}
}
}
/// <summary>
/// 迁移指定租户的数据库
/// </summary>
public async Task MigrateTenantDatabaseAsync(TenantInfo tenant)
{
if (tenant == null || !tenant.IsEnabled)
{
return;
}
_logger.LogInformation("开始迁移租户 {TenantId} 的数据库", tenant.TenantId);
// 创建一个新的作用域,以便我们可以使用租户特定的服务
using (var scope = _serviceProvider.CreateScope())
{
// 设置当前租户
var tenantAccessor = scope.ServiceProvider.GetRequiredService<ITenantAccessor>();
tenantAccessor.CurrentTenant = tenant;
// 获取 DbContext 并执行迁移
var dbContext = new DesignTimeDefaultDbContextFactory(scope.ServiceProvider).CreateDbContext(new string[0]);//scope.ServiceProvider.GetRequiredService<DefaultDbContext>();
ILogger logger = _serviceProvider.GetLogger(GetType());
dbContext.CheckAndMigration(logger);
}
InitializeSeedDataAsync(tenant);
InitFrameWorkData(tenant);
}
/// <summary>
/// 初始化种子数据
/// </summary>
private void InitializeSeedDataAsync(TenantInfo tenant)
{
using (var scope = _serviceProvider.CreateScope())
{
//初始化种子数据,只初始化当前上下文的种子数据
IEntityManager entityManager = scope.ServiceProvider.GetRequiredService<IEntityManager>();
Type[] entityTypes = entityManager.GetEntityRegisters(typeof(DefaultDbContext)).Select(m => m.EntityType).Distinct().ToArray();
IEnumerable<ISeedDataInitializer> seedDataInitializers = scope.ServiceProvider.GetServices<ISeedDataInitializer>()
.Where(m => entityTypes.Contains(m.EntityType)).OrderBy(m => m.Order);
try
{
ITenantAccessor tenantAccessor = scope.ServiceProvider.GetRequiredService<ITenantAccessor>();
tenantAccessor.CurrentTenant = tenant;
foreach (ISeedDataInitializer initializer in seedDataInitializers)
{
initializer.Initialize();
}
var csp = scope.ServiceProvider.GetRequiredService<IConnectionStringProvider>();
var str = csp.GetConnectionString(typeof(DefaultDbContext));
_logger.LogInformation("IConnectionStringProvider 链接:" + str);
}
catch (Exception ex)
{
_logger.LogError(ex, "初始化种子数据时出错" + ex.Message);
}
}
}
private void InitFrameWorkData(TenantInfo tenant)
{
using (var scope = _serviceProvider.CreateScope())
{
ITenantAccessor tenantAccessor = scope.ServiceProvider.GetRequiredService<ITenantAccessor>();
tenantAccessor.CurrentTenant = tenant;
IFunctionHandler functionHandler = scope.ServiceProvider.GetServices<IFunctionHandler>().FirstOrDefault(m => m.GetType() == typeof(MvcFunctionHandler));
if (functionHandler != null)
{
functionHandler.Initialize();
}
IModuleHandler moduleHandler = scope.ServiceProvider.GetRequiredService<IModuleHandler>();
moduleHandler.Initialize();
//IFunctionAuthCache functionAuthCache = scope.ServiceProvider.GetRequiredService<IFunctionAuthCache>();
//functionAuthCache.BuildRoleCaches();
IEntityInfoHandler entityInfoHandler = scope.ServiceProvider.GetRequiredService<IEntityInfoHandler>();
entityInfoHandler.Initialize();
}
}
}
}
配置文件
json
复制代码
{
"Logging": {
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information",
"OSharp": "Debug",
"Liuliu": "Debug"
}
},
"ConnectionStrings": {
"Tenant": "Server=(localdb)\\mssqllocaldb;Database=MasterDb;Trusted_Connection=True;MultipleActiveResultSets=true",
"Default": "Server=(localdb)\\mssqllocaldb;Database=DefaultDb;Trusted_Connection=True;MultipleActiveResultSets=true",
"tenant0": {
"Default": "Server=(localdb)\\mssqllocaldb;Database=Tenant0Db;Trusted_Connection=True;MultipleActiveResultSets=true"
},
"tenant1": {
"Default": "Server=(localdb)\\mssqllocaldb;Database=Tenant1Db;Trusted_Connection=True;MultipleActiveResultSets=true"
},
"tenant2": {
"Default": "Server=(localdb)\\mssqllocaldb;Database=Tenant2Db;Trusted_Connection=True;MultipleActiveResultSets=true"
}
},
"MultiTenancy": {
"TenantResolve": {
"EnableDomain": true,
"EnableHeader": true,
"HeaderName": "X-Tenant",
"EnableQueryString": true,
"QueryStringName": "tenant",
"EnableCookie": true,
"CookieName": "tenant",
"EnableClaim": true,
"ClaimType": "tenant",
"EnableRoute": true,
"RouteParamName": "tenant"
}
},
"Tenants": {
"tenant0": {
"Name": "租户0",
"Host": "localhost",
"ConnectionString": "Server=(localdb)\\mssqllocaldb;Database=Tenant0Db;Trusted_Connection=True;MultipleActiveResultSets=true",
"IsEnabled": true
},
"tenant1": {
"Name": "租户1",
"Host": "tenant1",
"ConnectionString": "Server=(localdb)\\mssqllocaldb;Database=Tenant1Db;Trusted_Connection=True;MultipleActiveResultSets=true",
"IsEnabled": true
},
"tenant2": {
"Name": "租户2",
"Host": "tenant2",
"ConnectionString": "Server=(localdb)\\mssqllocaldb;Database=Tenant2Db;Trusted_Connection=True;MultipleActiveResultSets=true",
"IsEnabled": true
}
},
"OSharp": {
"DbContexts": {
"SqlServer": {
"DbContextTypeName": "OSharp.Entity.DefaultDbContext,OSharp.EntityFrameworkCore",
"ConnectionString": "Server=(localdb)\\mssqllocaldb;Database=osharpns-dev01;Trusted_Connection=True;MultipleActiveResultSets=true",
"DatabaseType": "SqlServer",
"LazyLoadingProxiesEnabled": true,
"AuditEntityEnabled": true,
"AutoMigrationEnabled": true
},
"Tenant": {
"DbContextTypeName": "OSharp.Entity.TenantDbContext,OSharp.EntityFrameworkCore",
"ConnectionString": "Server=(localdb)\\mssqllocaldb;Database=osharpns-dev02;Trusted_Connection=True;MultipleActiveResultSets=true",
"DatabaseType": "SqlServer",
"LazyLoadingProxiesEnabled": true,
"AuditEntityEnabled": true,
"AutoMigrationEnabled": true
}
//,
//"MySql": {
// "DbContextTypeName": "OSharp.Entity.DefaultDbContext,OSharp.EntityFrameworkCore",
// "ConnectionString": "Server=127.0.0.1;UserId=root;Password=abc123456;Database=osharpns-dev3;charset='utf8';Allow User Variables=True",
// "DatabaseType": "MySql",
// "LazyLoadingProxiesEnabled": true,
// "AuditEntityEnabled": true,
// "AutoMigrationEnabled": true
//}
//,
//"Sqlite": {
// "DbContextTypeName": "OSharp.Entity.DefaultDbContext,OSharp.EntityFrameworkCore",
// "ConnectionString": "data source=osharpns.db",
// "DatabaseType": "Sqlite",
// "LazyLoadingProxiesEnabled": true,
// "AuditEntityEnabled": true,
// "AutoMigrationEnabled": true
//}
//,
//"PostgreSql": {
// "DbContextTypeName": "OSharp.Entity.DefaultDbContext,OSharp.EntityFrameworkCore",
// "ConnectionString": "User ID=postgres;Password=abc123456;Host=localhost;Port=5432;Database=osharpns.demo-dev3",
// "DatabaseType": "PostgreSql",
// "LazyLoadingProxiesEnabled": true,
// "AuditEntityEnabled": true,
// "AutoMigrationEnabled": true
//}
},
"OAuth2": {
//"QQ": {
// "ClientId": "你的QQ互联项目AppId",
// "ClientSecret": "你的QQ互联项目AppKey",
// "Enabled": false
//},
//"Microsoft": {
// "ClientId": "你的微软项目ClientId",
// "ClientSecret": "你的微软项目ClientSecret",
// "Enabled": false
//},
//"GitHub": {
// "ClientId": "你的微软项目ClientId",
// "ClientSecret": "你的微软项目ClientSecret",
// "Enabled": false
//}
},
"HttpEncrypt": {
"HostPrivateKey": "<RSAKeyValue><Modulus>npBAH/wQ+CzWz0cNvt7TRroWzE4dF5TvQjQqoEa+7PutPPPsLYkHlCXlDWqv0gwGDc/vg9mzaxFFWFFKi+hMjwh1dkDIa9GVm3umB8Ris1j2yNqIvOEXDPpwbnCgqwP8HsnOwRG0Klqc3qjZlaaex0cv8XY/9v2l6qYkd9J0imtBLL+22bfPW+a/qtQfvVkNAKNm6gjLLFwkXBnw3WnBNvmpqR70fe4NtIX3gIY5uWQcD5pdBmpG1uwzCatiA2b2Gso+tr/CE5nbZ7BCofIGu4Q1JSpQTzbVnLq0+e3z3ysLNZbbXHxkcTtihKOSHhkHGnSUnJZCKHaoAlNT1tAP2Q==</Modulus><Exponent>AQAB</Exponent><P>0Xm6GIcZy2C6jj0pfhFvQfOFzOGgocXLyOJAlkZ2ULk033Xx/LdDogHzte2sxxSzuTApoJHQuCmuBIKJI74SkjAAAcRNwrhbuzLB5ulAUlTINBUHixn+PFd7m1nug0PfcTbAnapUx5n+5WTR/9zbnnfsyHXRBFumzhzByF85ibc=</P><Q>wcfJ5NkRgGDUAE8sL1Gcf5TQAJ2dK4/coHal38S2v+HzkuGGFDwICfNm/q9IzIDa72MhjBlbE7zBsRmHy2wJL/bVB1xxpfWXPZ2sHzkWeuLBEY5V1k0MaUxjFEhfgLi439IFFEPczFl3ndpNR6DYBc7Jcou0J90w9rMq2Wljcu8=</Q><DP>kHnFaX9cwhHv+YSjpoi91J3yTbHciVcTy3SJGVx15A0pM2p0wVlg808nWPYZcaGMp5BZVZ7cdviARioGDjndMyiaCJ3tB/0Bf6ZtaCa+L0q8XneWoVEHMXUhEq+/OpfId5xM0zGUkapbzLlxwWgBrVWHYWcpBzlzXbslyF4tIBc=</DP><DQ>WQbNxZrIhJ93prC5DwBCkwauTSocVDAi34HDETwR7bQEMH32GIO/+bpenjGvk2y7qPF1LyVTB41Xu2KMVbPLwMJ4+onJGMLs+fzfX/TdVBWrN8KZwvvg8NuMRXw+jCfRn9qgRMAsx6Fu6BGsIXVO6dQoDr0KRqpDXYPQ8tONQfc=</DQ><InverseQ>wo7bSu+OlrXYWC6EpzmFoqHrCCzVQ/Vbf/0HsMFl5CFB8k3LJjngyFDkUi/5NNNJ/BZv0oE3pk6XyHEXGlk/MZV4FCO/YrF4DSZ++ecD0/aVQDGfCNDU0HFLstBEQDhmRTQZEkkLHN8O+zjFKDzujBMY4DYZcXpFbr0srOhiDv4=</InverseQ><D>eOZgHoMhpTj7KNxyfKCF0528GFdPE1X6AC6qeb63gRZ9BsatxCKBZtJmDY7VNID6dLmhVJU/mll22FpTTs324fB/L4VBzAPn4L+s3qs83qbstET8kZfG7Zxe8bZqqzrEl+0j+k43Yzvn9FYmYVbEJgn/YkrZhsfsJDg+AiazuX3OoFfPmgArsBmiii+7nWLmMnpoiebxGS29YDl2YqrntkcVuOpZALHDefHQdJcsNpJgFd6Dbm77ajZhpJppC4mtuUu0agUmJL3Uc6SLYHjIp/tDd2L8noZipYuNfzxxuf5KejWZM4FT6zGbU/QEurvTWMQAqp2ibixl614mUXFcKQ==</D></RSAKeyValue>",
"HostPublicKey": "<RSAKeyValue><Modulus>npBAH/wQ+CzWz0cNvt7TRroWzE4dF5TvQjQqoEa+7PutPPPsLYkHlCXlDWqv0gwGDc/vg9mzaxFFWFFKi+hMjwh1dkDIa9GVm3umB8Ris1j2yNqIvOEXDPpwbnCgqwP8HsnOwRG0Klqc3qjZlaaex0cv8XY/9v2l6qYkd9J0imtBLL+22bfPW+a/qtQfvVkNAKNm6gjLLFwkXBnw3WnBNvmpqR70fe4NtIX3gIY5uWQcD5pdBmpG1uwzCatiA2b2Gso+tr/CE5nbZ7BCofIGu4Q1JSpQTzbVnLq0+e3z3ysLNZbbXHxkcTtihKOSHhkHGnSUnJZCKHaoAlNT1tAP2Q==</Modulus><Exponent>AQAB</Exponent><P></P><Q></Q><DP></DP><DQ></DQ><InverseQ></InverseQ><D></D></RSAKeyValue>",
"Enabled": false
},
"MailSender": {
"Host": "smtp.mxhichina.com",
"Port": 587,
"EnableSsl": true,
"DisplayName": "OSharp邮件发送",
"UserName": "osharpsender@66soft.net",
"Password": "OSharp147963"
},
"Jwt": {
"Issuer": "osharp identity",
"Audience": "osharp angular demo",
"Secret": "{8619F7C3-B53C-4B85-99F0-983D351ECD82}",
"AccessExpireMins": 5,
"RefreshExpireMins": 10080, // 7天
"IsRefreshAbsoluteExpired": false,
"Enabled": true
},
//"Cookie": {
// "Enabled": false
//},
"Cors": {
"PolicyName": "MyCors",
"AllowAnyHeader": true,
"WithMethods": [ "POST", "PUT", "DELETE" ],
"WithOrigins": [ "http://example.com" ],
"Enabled": true
},
"Redis": {
"Configuration": "localhost",
"InstanceName": "OSharpDemo:"
},
"Swagger": {
"Endpoints": [
{
"Title": "框架API",
"Version": "v1",
"Url": "/swagger/v1/swagger.json"
},
{
"Title": "业务API",
"Version": "buss",
"Url": "/swagger/buss/swagger.json"
}
],
"RoutePrefix": "swagger",
"IsHideSchemas": true,
"MiniProfiler": false,
"Enabled": true
},
"Hangfire": {
"WorkerCount": 20,
"StorageConnectionString": "Server=.;Database=osharpns.hangfire-dev;User Id=sa;Password=Abc123456!;MultipleActiveResultSets=true",
"DashboardUrl": "/hangfire",
"Roles": ""
}
}
}
租户信息存文件的配置文件
json
复制代码
//tenants.json
[
{
"tenantId": "tenant0",
"name": "租户0",
"host": "localhost",
"connectionString": "Server=(localdb)\\mssqllocaldb;Database=Tenant0Db;Trusted_Connection=True;MultipleActiveResultSets=true",
"isEnabled": true
},
{
"tenantId": "tenant1",
"name": "租户1",
"host": "tenant1",
"connectionString": "Server=(localdb)\\mssqllocaldb;Database=Tenant1Db;Trusted_Connection=True;MultipleActiveResultSets=true",
"isEnabled": true
},
{
"tenantId": "tenant2",
"name": "租户2",
"host": "tenant2",
"connectionString": "Server=(localdb)\\mssqllocaldb;Database=Tenant2Db;Trusted_Connection=True;MultipleActiveResultSets=true",
"isEnabled": true
}
]
全局缓存接口
C#
复制代码
using System.Threading.Tasks;
namespace OSharp.Caching
{
/// <summary>
/// 全局缓存键生成器接口
/// </summary>
public interface IGlobalCacheKeyGenerator
{
/// <summary>
/// 获取全局缓存键
/// </summary>
/// <param name="args">参数</param>
/// <returns>缓存键</returns>
string GetGlobalKey(params object[] args);
/// <summary>
/// 异步获取全局缓存键
/// </summary>
/// <param name="args">参数</param>
/// <returns>缓存键</returns>
Task<string> GetGlobalKeyAsync(params object[] args);
}
}
定义缓存服务功能接口
C#
复制代码
// -----------------------------------------------------------------------
// <copyright file="ICacheService.cs" company="OSharp开源团队">
// Copyright (c) 2014-2018 OSharp. All rights reserved.
// </copyright>
// <site>http://www.osharp.org</site>
// <last-editor>郭明锋</last-editor>
// <last-date>2018-12-19 18:07</last-date>
// -----------------------------------------------------------------------
namespace OSharp.Caching;
/// <summary>
/// 定义缓存服务功能
/// </summary>
public interface ICacheService
{
/// <summary>
/// 查询分页数据结果,如缓存存在,直接返回,否则从数据源查找分页结果,并存入缓存中再返回
/// </summary>
/// <typeparam name="TSource">数据源的项数据类型</typeparam>
/// <typeparam name="TResult">结果集的项数据类型</typeparam>
/// <param name="source">数据源</param>
/// <param name="predicate">数据筛选表达式</param>
/// <param name="pageCondition">分页条件</param>
/// <param name="selector">数据投影表达式</param>
/// <param name="cacheSeconds">缓存时间</param>
/// <param name="keyParams">缓存键参数</param>
/// <returns></returns>
PageResult<TResult> ToPageCache<TSource, TResult>(IQueryable<TSource> source,
Expression<Func<TSource, bool>> predicate,
PageCondition pageCondition,
Expression<Func<TSource, TResult>> selector,
int cacheSeconds = 60,
params object[] keyParams);
/// <summary>
/// 查询分页数据结果,如缓存存在,直接返回,否则从数据源查找分页结果,并存入缓存中再返回
/// </summary>
/// <typeparam name="TSource">数据源的项数据类型</typeparam>
/// <typeparam name="TResult">结果集的项数据类型</typeparam>
/// <param name="source">数据源</param>
/// <param name="predicate">数据筛选表达式</param>
/// <param name="pageCondition">分页条件</param>
/// <param name="selector">数据投影表达式</param>
/// <param name="function">当前功能信息</param>
/// <param name="keyParams">缓存键参数</param>
/// <returns></returns>
PageResult<TResult> ToPageCache<TSource, TResult>(IQueryable<TSource> source,
Expression<Func<TSource, bool>> predicate,
PageCondition pageCondition,
Expression<Func<TSource, TResult>> selector,
IFunction function,
params object[] keyParams);
/// <summary>
/// 将结果转换为缓存的列表,如缓存存在,直接返回,否则从数据源查询,并存入缓存中再返回
/// </summary>
/// <typeparam name="TSource">数据源的项数据类型</typeparam>
/// <typeparam name="TResult">结果集的项数据类型</typeparam>
/// <param name="source">数据源</param>
/// <param name="predicate">数据查询表达式</param>
/// <param name="selector">数据筛选表达式</param>
/// <param name="cacheSeconds">缓存时间:秒</param>
/// <param name="keyParams">缓存键参数</param>
/// <returns></returns>
List<TResult> ToCacheList<TSource, TResult>(IQueryable<TSource> source,
Expression<Func<TSource, bool>> predicate,
Expression<Func<TSource, TResult>> selector,
int cacheSeconds = 60,
params object[] keyParams);
/// <summary>
/// 将结果转换为缓存的数组,如缓存存在,直接返回,否则从数据源查询,并存入缓存中再返回
/// </summary>
/// <typeparam name="TSource">数据源的项数据类型</typeparam>
/// <typeparam name="TResult">结果集的项数据类型</typeparam>
/// <param name="source">数据源</param>
/// <param name="predicate">数据查询表达式</param>
/// <param name="selector">数据筛选表达式</param>
/// <param name="cacheSeconds">缓存时间:秒</param>
/// <param name="keyParams">缓存键参数</param>
/// <returns></returns>
TResult[] ToCacheArray<TSource, TResult>(IQueryable<TSource> source,
Expression<Func<TSource, bool>> predicate,
Expression<Func<TSource, TResult>> selector,
int cacheSeconds = 60,
params object[] keyParams);
/// <summary>
/// 将结果转换为缓存的列表,如缓存存在,直接返回,否则从数据源查询,并存入缓存中再返回
/// </summary>
/// <typeparam name="TSource">数据源的项数据类型</typeparam>
/// <typeparam name="TResult">结果集的项数据类型</typeparam>
/// <param name="source">数据源</param>
/// <param name="predicate">数据查询表达式</param>
/// <param name="selector">数据筛选表达式</param>
/// <param name="function">缓存策略相关功能</param>
/// <param name="keyParams">缓存键参数</param>
/// <returns></returns>
List<TResult> ToCacheList<TSource, TResult>(IQueryable<TSource> source,
Expression<Func<TSource, bool>> predicate,
Expression<Func<TSource, TResult>> selector,
IFunction function,
params object[] keyParams);
/// <summary>
/// 将结果转换为缓存的数组,如缓存存在,直接返回,否则从数据源查询,并存入缓存中再返回
/// </summary>
/// <typeparam name="TSource">数据源的项数据类型</typeparam>
/// <typeparam name="TResult">结果集的项数据类型</typeparam>
/// <param name="source">数据源</param>
/// <param name="predicate">数据查询表达式</param>
/// <param name="selector">数据筛选表达式</param>
/// <param name="function">缓存策略相关功能</param>
/// <param name="keyParams">缓存键参数</param>
/// <returns></returns>
TResult[] ToCacheArray<TSource, TResult>(IQueryable<TSource> source,
Expression<Func<TSource, bool>> predicate,
Expression<Func<TSource, TResult>> selector,
IFunction function,
params object[] keyParams);
/// <summary>
/// 将结果转换为缓存的列表,如缓存存在,直接返回,否则从数据源查询,并存入缓存中再返回
/// </summary>
/// <typeparam name="TSource">源数据类型</typeparam>
/// <typeparam name="TResult">结果集的项数据类型</typeparam>
/// <param name="source">查询数据源</param>
/// <param name="selector">数据筛选表达式</param>
/// <param name="cacheSeconds">缓存的秒数</param>
/// <param name="keyParams">缓存键参数</param>
/// <returns>查询结果</returns>
List<TResult> ToCacheList<TSource, TResult>(IQueryable<TSource> source,
Expression<Func<TSource, TResult>> selector,
int cacheSeconds = 60,
params object[] keyParams);
/// <summary>
/// 将结果转换为缓存的数组,如缓存存在,直接返回,否则从数据源查询,并存入缓存中再返回
/// </summary>
/// <typeparam name="TSource">源数据类型</typeparam>
/// <typeparam name="TResult">结果集的项数据类型</typeparam>
/// <param name="source">查询数据源</param>
/// <param name="selector">数据筛选表达式</param>
/// <param name="cacheSeconds">缓存的秒数</param>
/// <param name="keyParams">缓存键参数</param>
/// <returns>查询结果</returns>
TResult[] ToCacheArray<TSource, TResult>(IQueryable<TSource> source,
Expression<Func<TSource, TResult>> selector,
int cacheSeconds = 60,
params object[] keyParams);
/// <summary>
/// 将结果转换为缓存的列表,如缓存存在,直接返回,否则从数据源查询,并按指定缓存策略存入缓存中再返回
/// </summary>
/// <typeparam name="TSource">源数据类型</typeparam>
/// <typeparam name="TResult">结果集的项数据类型</typeparam>
/// <param name="source">查询数据源</param>
/// <param name="selector">数据筛选表达式</param>
/// <param name="function">缓存策略相关功能</param>
/// <param name="keyParams">缓存键参数</param>
/// <returns>查询结果</returns>
List<TResult> ToCacheList<TSource, TResult>(IQueryable<TSource> source,
Expression<Func<TSource, TResult>> selector,
IFunction function,
params object[] keyParams);
/// <summary>
/// 将结果转换为缓存的列表,如缓存存在,直接返回,否则从数据源查询,并按指定缓存策略存入缓存中再返回
/// </summary>
/// <typeparam name="TSource">源数据类型</typeparam>
/// <typeparam name="TResult">结果集的项数据类型</typeparam>
/// <param name="source">查询数据源</param>
/// <param name="selector">数据筛选表达式</param>
/// <param name="function">缓存策略相关功能</param>
/// <param name="keyParams">缓存键参数</param>
/// <returns>查询结果</returns>
TResult[] ToCacheArray<TSource, TResult>(IQueryable<TSource> source,
Expression<Func<TSource, TResult>> selector,
IFunction function,
params object[] keyParams);
/// <summary>
/// 将结果转换为缓存的列表,如缓存存在,直接返回,否则从数据源查询,并存入缓存中再返回
/// </summary>
/// <typeparam name="TSource">源数据类型</typeparam>
/// <param name="source">查询数据源</param>
/// <param name="cacheSeconds">缓存的秒数</param>
/// <param name="keyParams">缓存键参数</param>
/// <returns>查询结果</returns>
List<TSource> ToCacheList<TSource>(IQueryable<TSource> source, int cacheSeconds = 60, params object[] keyParams);
/// <summary>
/// 将结果转换为缓存的数组,如缓存存在,直接返回,否则从数据源查询,并存入缓存中再返回
/// </summary>
/// <typeparam name="TSource">源数据类型</typeparam>
/// <param name="source">查询数据源</param>
/// <param name="cacheSeconds">缓存的秒数</param>
/// <param name="keyParams">缓存键参数</param>
/// <returns>查询结果</returns>
TSource[] ToCacheArray<TSource>(IQueryable<TSource> source, int cacheSeconds = 60, params object[] keyParams);
/// <summary>
/// 将结果转换为缓存的列表,如缓存存在,直接返回,否则从数据源查询,并按指定缓存策略存入缓存中再返回
/// </summary>
/// <typeparam name="TSource">源数据类型</typeparam>
/// <param name="source">查询数据源</param>
/// <param name="function">缓存策略相关功能</param>
/// <param name="keyParams">缓存键参数</param>
/// <returns>查询结果</returns>
List<TSource> ToCacheList<TSource>(IQueryable<TSource> source, IFunction function, params object[] keyParams);
/// <summary>
/// 将结果转换为缓存的列表,如缓存存在,直接返回,否则从数据源查询,并按指定缓存策略存入缓存中再返回
/// </summary>
/// <typeparam name="TSource">源数据类型</typeparam>
/// <param name="source">查询数据源</param>
/// <param name="function">缓存策略相关功能</param>
/// <param name="keyParams">缓存键参数</param>
/// <returns>查询结果</returns>
TSource[] ToCacheArray<TSource>(IQueryable<TSource> source, IFunction function, params object[] keyParams);
#region OutputDto
/// <summary>
/// 查询分页数据结果,如缓存存在,直接返回,否则从数据源查找分页结果,并存入缓存中再返回
/// </summary>
/// <typeparam name="TEntity">实体类型</typeparam>
/// <typeparam name="TOutputDto">分页数据类型</typeparam>
/// <param name="source">要查询的数据集</param>
/// <param name="predicate">查询条件谓语表达式</param>
/// <param name="pageCondition">分页查询条件</param>
/// <param name="cacheSeconds">缓存的秒数</param>
/// <param name="keyParams">缓存键参数</param>
/// <returns>查询的分页结果</returns>
PageResult<TOutputDto> ToPageCache<TEntity, TOutputDto>(IQueryable<TEntity> source,
Expression<Func<TEntity, bool>> predicate,
PageCondition pageCondition,
int cacheSeconds = 60,
params object[] keyParams)
where TOutputDto : IOutputDto;
/// <summary>
/// 查询分页数据结果,如缓存存在,直接返回,否则从数据源查找分页结果,并存入缓存中再返回
/// </summary>
/// <typeparam name="TEntity">实体类型</typeparam>
/// <typeparam name="TOutputDto">分页数据类型</typeparam>
/// <param name="source">要查询的数据集</param>
/// <param name="predicate">查询条件谓语表达式</param>
/// <param name="pageCondition">分页查询条件</param>
/// <param name="function">缓存策略相关功能</param>
/// <param name="keyParams">缓存键参数</param>
/// <returns>查询的分页结果</returns>
PageResult<TOutputDto> ToPageCache<TEntity, TOutputDto>(IQueryable<TEntity> source,
Expression<Func<TEntity, bool>> predicate,
PageCondition pageCondition,
IFunction function,
params object[] keyParams)
where TOutputDto : IOutputDto;
/// <summary>
/// 将结果转换为缓存的列表,如缓存存在,直接返回,否则从数据源查询,并存入缓存中再返回
/// </summary>
/// <typeparam name="TSource">数据源的项数据类型</typeparam>
/// <typeparam name="TOutputDto">结果集的项数据类型</typeparam>
/// <param name="source">数据源</param>
/// <param name="predicate">数据查询表达式</param>
/// <param name="cacheSeconds">缓存时间:秒</param>
/// <param name="keyParams">缓存键参数</param>
/// <returns></returns>
List<TOutputDto> ToCacheList<TSource, TOutputDto>(IQueryable<TSource> source,
Expression<Func<TSource, bool>> predicate,
int cacheSeconds = 60,
params object[] keyParams);
/// <summary>
/// 将结果转换为缓存的数组,如缓存存在,直接返回,否则从数据源查询,并存入缓存中再返回
/// </summary>
/// <typeparam name="TSource">数据源的项数据类型</typeparam>
/// <typeparam name="TOutputDto">结果集的项数据类型</typeparam>
/// <param name="source">数据源</param>
/// <param name="predicate">数据查询表达式</param>
/// <param name="cacheSeconds">缓存时间:秒</param>
/// <param name="keyParams">缓存键参数</param>
/// <returns></returns>
TOutputDto[] ToCacheArray<TSource, TOutputDto>(IQueryable<TSource> source,
Expression<Func<TSource, bool>> predicate,
int cacheSeconds = 60,
params object[] keyParams);
/// <summary>
/// 将结果转换为缓存的列表,如缓存存在,直接返回,否则从数据源查询,并存入缓存中再返回
/// </summary>
/// <typeparam name="TSource">数据源的项数据类型</typeparam>
/// <typeparam name="TOutputDto">结果集的项数据类型</typeparam>
/// <param name="source">数据源</param>
/// <param name="predicate">数据查询表达式</param>
/// <param name="function">缓存策略相关功能</param>
/// <param name="keyParams">缓存键参数</param>
/// <returns></returns>
List<TOutputDto> ToCacheList<TSource, TOutputDto>(IQueryable<TSource> source,
Expression<Func<TSource, bool>> predicate,
IFunction function,
params object[] keyParams);
/// <summary>
/// 将结果转换为缓存的数组,如缓存存在,直接返回,否则从数据源查询,并存入缓存中再返回
/// </summary>
/// <typeparam name="TSource">数据源的项数据类型</typeparam>
/// <typeparam name="TOutputDto">结果集的项数据类型</typeparam>
/// <param name="source">数据源</param>
/// <param name="predicate">数据查询表达式</param>
/// <param name="function">缓存策略相关功能</param>
/// <param name="keyParams">缓存键参数</param>
/// <returns></returns>
TOutputDto[] ToCacheArray<TSource, TOutputDto>(IQueryable<TSource> source,
Expression<Func<TSource, bool>> predicate,
IFunction function,
params object[] keyParams);
/// <summary>
/// 将结果转换为缓存的列表,如缓存存在,直接返回,否则从数据源查询,并存入缓存中再返回
/// </summary>
/// <typeparam name="TSource">源数据类型</typeparam>
/// <typeparam name="TOutputDto">结果集的项数据类型</typeparam>
/// <param name="source">查询数据源</param>
/// <param name="cacheSeconds">缓存的秒数</param>
/// <param name="keyParams">缓存键参数</param>
/// <returns>查询结果</returns>
List<TOutputDto> ToCacheList<TSource, TOutputDto>(IQueryable<TSource> source,
int cacheSeconds = 60,
params object[] keyParams);
/// <summary>
/// 将结果转换为缓存的数组,如缓存存在,直接返回,否则从数据源查询,并存入缓存中再返回
/// </summary>
/// <typeparam name="TSource">源数据类型</typeparam>
/// <typeparam name="TOutputDto">结果集的项数据类型</typeparam>
/// <param name="source">查询数据源</param>
/// <param name="cacheSeconds">缓存的秒数</param>
/// <param name="keyParams">缓存键参数</param>
/// <returns>查询结果</returns>
TOutputDto[] ToCacheArray<TSource, TOutputDto>(IQueryable<TSource> source,
int cacheSeconds = 60,
params object[] keyParams);
/// <summary>
/// 将结果转换为缓存的列表,如缓存存在,直接返回,否则从数据源查询,并按指定缓存策略存入缓存中再返回
/// </summary>
/// <typeparam name="TSource">源数据类型</typeparam>
/// <typeparam name="TOutputDto">结果集的项数据类型</typeparam>
/// <param name="source">查询数据源</param>
/// <param name="function">缓存策略相关功能</param>
/// <param name="keyParams">缓存键参数</param>
/// <returns>查询结果</returns>
List<TOutputDto> ToCacheList<TSource, TOutputDto>(IQueryable<TSource> source,
IFunction function,
params object[] keyParams);
/// <summary>
/// 将结果转换为缓存的列表,如缓存存在,直接返回,否则从数据源查询,并按指定缓存策略存入缓存中再返回
/// </summary>
/// <typeparam name="TSource">源数据类型</typeparam>
/// <typeparam name="TOutputDto">结果集的项数据类型</typeparam>
/// <param name="source">查询数据源</param>
/// <param name="function">缓存策略相关功能</param>
/// <param name="keyParams">缓存键参数</param>
/// <returns>查询结果</returns>
TOutputDto[] ToCacheArray<TSource, TOutputDto>(IQueryable<TSource> source,
IFunction function,
params object[] keyParams);
#endregion
/// <summary>
/// 获取或添加全局缓存,不考虑租户
/// </summary>
/// <typeparam name="T">缓存数据类型</typeparam>
/// <param name="key">缓存键</param>
/// <param name="factory">缓存数据获取工厂</param>
/// <param name="expiration">过期时间</param>
/// <returns>缓存数据</returns>
T GetOrAddGlobal<T>(string key, Func<T> factory, TimeSpan? expiration = null);
/// <summary>
/// 异步获取或添加全局缓存,不考虑租户
/// </summary>
/// <typeparam name="T">缓存数据类型</typeparam>
/// <param name="key">缓存键</param>
/// <param name="factory">缓存数据获取工厂</param>
/// <param name="expiration">过期时间</param>
/// <returns>缓存数据</returns>
Task<T> GetOrAddGlobalAsync<T>(string key, Func<Task<T>> factory, TimeSpan? expiration = null);
/// <summary>
/// 移除全局缓存,不考虑租户
/// </summary>
/// <param name="key">缓存键</param>
void RemoveGlobal(string key);
/// <summary>
/// 异步移除全局缓存,不考虑租户
/// </summary>
/// <param name="key">缓存键</param>
Task RemoveGlobalAsync(string key);
// 在 ICacheService 接口中添加以下方法
/// <summary>
/// 设置缓存
/// </summary>
/// <typeparam name="T">缓存数据类型</typeparam>
/// <param name="key">缓存键</param>
/// <param name="value">缓存值</param>
/// <param name="expiration">过期时间</param>
void Set<T>(string key, T value, TimeSpan? expiration = null);
/// <summary>
/// 异步设置缓存
/// </summary>
/// <typeparam name="T">缓存数据类型</typeparam>
/// <param name="key">缓存键</param>
/// <param name="value">缓存值</param>
/// <param name="expiration">过期时间</param>
/// <returns>异步任务</returns>
Task SetAsync<T>(string key, T value, TimeSpan? expiration = null);
/// <summary>
/// 获取缓存
/// </summary>
/// <typeparam name="T">缓存数据类型</typeparam>
/// <param name="key">缓存键</param>
/// <returns>缓存数据</returns>
T Get<T>(string key);
/// <summary>
/// 异步获取缓存
/// </summary>
/// <typeparam name="T">缓存数据类型</typeparam>
/// <param name="key">缓存键</param>
/// <returns>缓存数据</returns>
Task<T> GetAsync<T>(string key);
}
缓存服务实现
C#
复制代码
// -----------------------------------------------------------------------
// <copyright file="CacheService.cs" company="OSharp开源团队">
// Copyright (c) 2014-2018 OSharp. All rights reserved.
// </copyright>
// <site>http://www.osharp.org</site>
// <last-editor>郭明锋</last-editor>
// <last-date>2018-12-19 19:10</last-date>
// -----------------------------------------------------------------------
using System.Text.Json;
namespace OSharp.Caching;
/// <summary>
/// 缓存服务实现
/// </summary>
public class CacheService : ICacheService
{
private readonly IDistributedCache _cache;
private readonly ICacheKeyGenerator _keyGenerator;
private readonly IGlobalCacheKeyGenerator _globalKeyGenerator;
private readonly ILogger<CacheService> _logger;
private readonly IServiceProvider _serviceProvider;
/// <summary>
/// 初始化一个<see cref="CacheService"/>类型的新实例
/// </summary>
public CacheService(
IDistributedCache cache,
ICacheKeyGenerator keyGenerator,
ILogger<CacheService> logger,
IServiceProvider serviceProvider)
{
_cache = cache;
_keyGenerator = keyGenerator;
_globalKeyGenerator = keyGenerator as IGlobalCacheKeyGenerator;
_logger = logger;
_serviceProvider = serviceProvider;
}
#region Implementation of ICacheService
/// <summary>
/// 查询分页数据结果,如缓存存在,直接返回,否则从数据源查找分页结果,并存入缓存中再返回
/// </summary>
/// <typeparam name="TSource">数据源的项数据类型</typeparam>
/// <typeparam name="TResult">结果集的项数据类型</typeparam>
/// <param name="source">数据源</param>
/// <param name="predicate">数据筛选表达式</param>
/// <param name="pageCondition">分页条件</param>
/// <param name="selector">数据投影表达式</param>
/// <param name="cacheSeconds">缓存时间</param>
/// <param name="keyParams">缓存键参数</param>
/// <returns></returns>
public virtual PageResult<TResult> ToPageCache<TSource, TResult>(IQueryable<TSource> source,
Expression<Func<TSource, bool>> predicate,
PageCondition pageCondition,
Expression<Func<TSource, TResult>> selector,
int cacheSeconds = 60,
params object[] keyParams)
{
string key = GetKey(source, predicate, pageCondition, selector, keyParams);
return _cache.Get(key, () => source.ToPage(predicate, pageCondition, selector), cacheSeconds);
}
/// <summary>
/// 查询分页数据结果,如缓存存在,直接返回,否则从数据源查找分页结果,并存入缓存中再返回
/// </summary>
/// <typeparam name="TSource">数据源的项数据类型</typeparam>
/// <typeparam name="TResult">结果集的项数据类型</typeparam>
/// <param name="source">数据源</param>
/// <param name="predicate">数据筛选表达式</param>
/// <param name="pageCondition">分页条件</param>
/// <param name="selector">数据投影表达式</param>
/// <param name="function">当前功能信息</param>
/// <param name="keyParams">缓存键参数</param>
/// <returns></returns>
public virtual PageResult<TResult> ToPageCache<TSource, TResult>(IQueryable<TSource> source,
Expression<Func<TSource, bool>> predicate,
PageCondition pageCondition,
Expression<Func<TSource, TResult>> selector,
IFunction function,
params object[] keyParams)
{
string key = GetKey(source, predicate, pageCondition, selector, keyParams);
return _cache.Get(key, () => source.ToPage(predicate, pageCondition, selector), function);
}
/// <summary>
/// 将结果转换为缓存的列表,如缓存存在,直接返回,否则从数据源查询,并存入缓存中再返回
/// </summary>
/// <typeparam name="TSource">数据源的项数据类型</typeparam>
/// <typeparam name="TResult">结果集的项数据类型</typeparam>
/// <param name="source">数据源</param>
/// <param name="predicate">数据查询表达式</param>
/// <param name="selector">数据筛选表达式</param>
/// <param name="cacheSeconds">缓存时间:秒</param>
/// <param name="keyParams">缓存键参数</param>
/// <returns></returns>
public virtual List<TResult> ToCacheList<TSource, TResult>(IQueryable<TSource> source,
Expression<Func<TSource, bool>> predicate,
Expression<Func<TSource, TResult>> selector,
int cacheSeconds = 60,
params object[] keyParams)
{
return ToCacheList(source.Where(predicate), selector, cacheSeconds, keyParams);
}
/// <summary>
/// 将结果转换为缓存的数组,如缓存存在,直接返回,否则从数据源查询,并存入缓存中再返回
/// </summary>
/// <typeparam name="TSource">数据源的项数据类型</typeparam>
/// <typeparam name="TResult">结果集的项数据类型</typeparam>
/// <param name="source">数据源</param>
/// <param name="predicate">数据查询表达式</param>
/// <param name="selector">数据筛选表达式</param>
/// <param name="cacheSeconds">缓存时间:秒</param>
/// <param name="keyParams">缓存键参数</param>
/// <returns></returns>
public virtual TResult[] ToCacheArray<TSource, TResult>(IQueryable<TSource> source,
Expression<Func<TSource, bool>> predicate,
Expression<Func<TSource, TResult>> selector,
int cacheSeconds = 60,
params object[] keyParams)
{
return ToCacheArray(source.Where(predicate), selector, cacheSeconds, keyParams);
}
/// <summary>
/// 将结果转换为缓存的列表,如缓存存在,直接返回,否则从数据源查询,并存入缓存中再返回
/// </summary>
/// <typeparam name="TSource">数据源的项数据类型</typeparam>
/// <typeparam name="TResult">结果集的项数据类型</typeparam>
/// <param name="source">数据源</param>
/// <param name="predicate">数据查询表达式</param>
/// <param name="selector">数据筛选表达式</param>
/// <param name="function">缓存策略相关功能</param>
/// <param name="keyParams">缓存键参数</param>
/// <returns></returns>
public virtual List<TResult> ToCacheList<TSource, TResult>(IQueryable<TSource> source,
Expression<Func<TSource, bool>> predicate,
Expression<Func<TSource, TResult>> selector,
IFunction function,
params object[] keyParams)
{
return ToCacheList(source.Where(predicate), selector, function, keyParams);
}
/// <summary>
/// 将结果转换为缓存的数组,如缓存存在,直接返回,否则从数据源查询,并存入缓存中再返回
/// </summary>
/// <typeparam name="TSource">数据源的项数据类型</typeparam>
/// <typeparam name="TResult">结果集的项数据类型</typeparam>
/// <param name="source">数据源</param>
/// <param name="predicate">数据查询表达式</param>
/// <param name="selector">数据筛选表达式</param>
/// <param name="function">缓存策略相关功能</param>
/// <param name="keyParams">缓存键参数</param>
/// <returns></returns>
public virtual TResult[] ToCacheArray<TSource, TResult>(IQueryable<TSource> source,
Expression<Func<TSource, bool>> predicate,
Expression<Func<TSource, TResult>> selector,
IFunction function,
params object[] keyParams)
{
return ToCacheArray(source.Where(predicate), selector, function, keyParams);
}
/// <summary>
/// 将结果转换为缓存的列表,如缓存存在,直接返回,否则从数据源查询,并存入缓存中再返回
/// </summary>
/// <typeparam name="TSource">源数据类型</typeparam>
/// <typeparam name="TResult">结果集的项数据类型</typeparam>
/// <param name="source">查询数据源</param>
/// <param name="selector">数据筛选表达式</param>
/// <param name="cacheSeconds">缓存的秒数</param>
/// <param name="keyParams">缓存键参数</param>
/// <returns>查询结果</returns>
public virtual List<TResult> ToCacheList<TSource, TResult>(IQueryable<TSource> source,
Expression<Func<TSource, TResult>> selector,
int cacheSeconds = 60,
params object[] keyParams)
{
string key = GetKey(source, selector, keyParams);
return _cache.Get(key, () => source.Select(selector).ToList(), cacheSeconds);
}
/// <summary>
/// 将结果转换为缓存的数组,如缓存存在,直接返回,否则从数据源查询,并存入缓存中再返回
/// </summary>
/// <typeparam name="TSource">源数据类型</typeparam>
/// <typeparam name="TResult">结果集的项数据类型</typeparam>
/// <param name="source">查询数据源</param>
/// <param name="selector">数据筛选表达式</param>
/// <param name="cacheSeconds">缓存的秒数</param>
/// <param name="keyParams">缓存键参数</param>
/// <returns>查询结果</returns>
public virtual TResult[] ToCacheArray<TSource, TResult>(IQueryable<TSource> source,
Expression<Func<TSource, TResult>> selector,
int cacheSeconds = 60,
params object[] keyParams)
{
string key = GetKey(source, selector, keyParams);
return _cache.Get(key, () => source.Select(selector).ToArray(), cacheSeconds);
}
/// <summary>
/// 将结果转换为缓存的列表,如缓存存在,直接返回,否则从数据源查询,并按指定缓存策略存入缓存中再返回
/// </summary>
/// <typeparam name="TSource">源数据类型</typeparam>
/// <typeparam name="TResult">结果集的项数据类型</typeparam>
/// <param name="source">查询数据源</param>
/// <param name="selector">数据筛选表达式</param>
/// <param name="function">缓存策略相关功能</param>
/// <param name="keyParams">缓存键参数</param>
/// <returns>查询结果</returns>
public virtual List<TResult> ToCacheList<TSource, TResult>(IQueryable<TSource> source,
Expression<Func<TSource, TResult>> selector,
IFunction function,
params object[] keyParams)
{
string key = GetKey(source, selector, keyParams);
return _cache.Get(key, () => source.Select(selector).ToList(), function);
}
/// <summary>
/// 将结果转换为缓存的列表,如缓存存在,直接返回,否则从数据源查询,并按指定缓存策略存入缓存中再返回
/// </summary>
/// <typeparam name="TSource">源数据类型</typeparam>
/// <typeparam name="TResult">结果集的项数据类型</typeparam>
/// <param name="source">查询数据源</param>
/// <param name="selector">数据筛选表达式</param>
/// <param name="function">缓存策略相关功能</param>
/// <param name="keyParams">缓存键参数</param>
/// <returns>查询结果</returns>
public virtual TResult[] ToCacheArray<TSource, TResult>(IQueryable<TSource> source,
Expression<Func<TSource, TResult>> selector,
IFunction function,
params object[] keyParams)
{
string key = GetKey(source, selector, keyParams);
return _cache.Get(key, () => source.Select(selector).ToArray(), function);
}
/// <summary>
/// 将结果转换为缓存的列表,如缓存存在,直接返回,否则从数据源查询,并存入缓存中再返回
/// </summary>
/// <typeparam name="TSource">源数据类型</typeparam>
/// <param name="source">查询数据源</param>
/// <param name="cacheSeconds">缓存的秒数</param>
/// <param name="keyParams">缓存键参数</param>
/// <returns>查询结果</returns>
public virtual List<TSource> ToCacheList<TSource>(IQueryable<TSource> source, int cacheSeconds = 60, params object[] keyParams)
{
string key = GetKey(source.Expression, keyParams);
return _cache.Get(key, source.ToList, cacheSeconds);
}
/// <summary>
/// 将结果转换为缓存的数组,如缓存存在,直接返回,否则从数据源查询,并存入缓存中再返回
/// </summary>
/// <typeparam name="TSource">源数据类型</typeparam>
/// <param name="source">查询数据源</param>
/// <param name="cacheSeconds">缓存的秒数</param>
/// <param name="keyParams">缓存键参数</param>
/// <returns>查询结果</returns>
public virtual TSource[] ToCacheArray<TSource>(IQueryable<TSource> source, int cacheSeconds = 60, params object[] keyParams)
{
string key = GetKey(source.Expression, keyParams);
return _cache.Get(key, source.ToArray, cacheSeconds);
}
/// <summary>
/// 将结果转换为缓存的列表,如缓存存在,直接返回,否则从数据源查询,并按指定缓存策略存入缓存中再返回
/// </summary>
/// <typeparam name="TSource">源数据类型</typeparam>
/// <param name="source">查询数据源</param>
/// <param name="function">缓存策略相关功能</param>
/// <param name="keyParams">缓存键参数</param>
/// <returns>查询结果</returns>
public virtual List<TSource> ToCacheList<TSource>(IQueryable<TSource> source, IFunction function, params object[] keyParams)
{
if (function == null || function.CacheExpirationSeconds <= 0)
{
return source.ToList();
}
string key = GetKey(source.Expression, keyParams);
return _cache.Get(key, source.ToList, function);
}
/// <summary>
/// 将结果转换为缓存的列表,如缓存存在,直接返回,否则从数据源查询,并按指定缓存策略存入缓存中再返回
/// </summary>
/// <typeparam name="TSource">源数据类型</typeparam>
/// <param name="source">查询数据源</param>
/// <param name="function">缓存策略相关功能</param>
/// <param name="keyParams">缓存键参数</param>
/// <returns>查询结果</returns>
public virtual TSource[] ToCacheArray<TSource>(IQueryable<TSource> source, IFunction function, params object[] keyParams)
{
if (function == null || function.CacheExpirationSeconds <= 0)
{
return source.ToArray();
}
string key = GetKey(source.Expression, keyParams);
return _cache.Get(key, source.ToArray, function);
}
/// <summary>
/// 查询分页数据结果,如缓存存在,直接返回,否则从数据源查找分页结果,并存入缓存中再返回
/// </summary>
/// <typeparam name="TEntity">实体类型</typeparam>
/// <typeparam name="TOutputDto">分页数据类型</typeparam>
/// <param name="source">要查询的数据集</param>
/// <param name="predicate">查询条件谓语表达式</param>
/// <param name="pageCondition">分页查询条件</param>
/// <param name="cacheSeconds">缓存的秒数</param>
/// <param name="keyParams">缓存键参数</param>
/// <returns>查询的分页结果</returns>
public virtual PageResult<TOutputDto> ToPageCache<TEntity, TOutputDto>(IQueryable<TEntity> source,
Expression<Func<TEntity, bool>> predicate,
PageCondition pageCondition,
int cacheSeconds = 60,
params object[] keyParams) where TOutputDto : IOutputDto
{
string key = GetKey<TEntity, TOutputDto>(source, predicate, pageCondition, keyParams);
return _cache.Get(key, () => source.ToPage<TEntity, TOutputDto>(predicate, pageCondition), cacheSeconds);
}
/// <summary>
/// 查询分页数据结果,如缓存存在,直接返回,否则从数据源查找分页结果,并存入缓存中再返回
/// </summary>
/// <typeparam name="TEntity">实体类型</typeparam>
/// <typeparam name="TOutputDto">分页数据类型</typeparam>
/// <param name="source">要查询的数据集</param>
/// <param name="predicate">查询条件谓语表达式</param>
/// <param name="pageCondition">分页查询条件</param>
/// <param name="function">缓存策略相关功能</param>
/// <param name="keyParams">缓存键参数</param>
/// <returns>查询的分页结果</returns>
public virtual PageResult<TOutputDto> ToPageCache<TEntity, TOutputDto>(IQueryable<TEntity> source,
Expression<Func<TEntity, bool>> predicate,
PageCondition pageCondition,
IFunction function,
params object[] keyParams) where TOutputDto : IOutputDto
{
string key = GetKey<TEntity, TOutputDto>(source, predicate, pageCondition, keyParams);
return _cache.Get(key, () => source.ToPage<TEntity, TOutputDto>(predicate, pageCondition), function);
}
/// <summary>
/// 将结果转换为缓存的列表,如缓存存在,直接返回,否则从数据源查询,并存入缓存中再返回
/// </summary>
/// <typeparam name="TSource">数据源的项数据类型</typeparam>
/// <typeparam name="TOutputDto">结果集的项数据类型</typeparam>
/// <param name="source">数据源</param>
/// <param name="predicate">数据查询表达式</param>
/// <param name="cacheSeconds">缓存时间:秒</param>
/// <param name="keyParams">缓存键参数</param>
/// <returns></returns>
public virtual List<TOutputDto> ToCacheList<TSource, TOutputDto>(IQueryable<TSource> source,
Expression<Func<TSource, bool>> predicate,
int cacheSeconds = 60,
params object[] keyParams)
{
return ToCacheList<TSource, TOutputDto>(source.Where(predicate), cacheSeconds, keyParams);
}
/// <summary>
/// 将结果转换为缓存的数组,如缓存存在,直接返回,否则从数据源查询,并存入缓存中再返回
/// </summary>
/// <typeparam name="TSource">数据源的项数据类型</typeparam>
/// <typeparam name="TOutputDto">结果集的项数据类型</typeparam>
/// <param name="source">数据源</param>
/// <param name="predicate">数据查询表达式</param>
/// <param name="cacheSeconds">缓存时间:秒</param>
/// <param name="keyParams">缓存键参数</param>
/// <returns></returns>
public virtual TOutputDto[] ToCacheArray<TSource, TOutputDto>(IQueryable<TSource> source,
Expression<Func<TSource, bool>> predicate,
int cacheSeconds = 60,
params object[] keyParams)
{
return ToCacheArray<TSource, TOutputDto>(source.Where(predicate), cacheSeconds, keyParams);
}
/// <summary>
/// 将结果转换为缓存的列表,如缓存存在,直接返回,否则从数据源查询,并存入缓存中再返回
/// </summary>
/// <typeparam name="TSource">数据源的项数据类型</typeparam>
/// <typeparam name="TOutputDto">结果集的项数据类型</typeparam>
/// <param name="source">数据源</param>
/// <param name="predicate">数据查询表达式</param>
/// <param name="function">缓存策略相关功能</param>
/// <param name="keyParams">缓存键参数</param>
/// <returns></returns>
public virtual List<TOutputDto> ToCacheList<TSource, TOutputDto>(IQueryable<TSource> source,
Expression<Func<TSource, bool>> predicate,
IFunction function,
params object[] keyParams)
{
return ToCacheList<TSource, TOutputDto>(source.Where(predicate), function, keyParams);
}
/// <summary>
/// 将结果转换为缓存的数组,如缓存存在,直接返回,否则从数据源查询,并存入缓存中再返回
/// </summary>
/// <typeparam name="TSource">数据源的项数据类型</typeparam>
/// <typeparam name="TOutputDto">结果集的项数据类型</typeparam>
/// <param name="source">数据源</param>
/// <param name="predicate">数据查询表达式</param>
/// <param name="function">缓存策略相关功能</param>
/// <param name="keyParams">缓存键参数</param>
/// <returns></returns>
public virtual TOutputDto[] ToCacheArray<TSource, TOutputDto>(IQueryable<TSource> source,
Expression<Func<TSource, bool>> predicate,
IFunction function,
params object[] keyParams)
{
return ToCacheArray<TSource, TOutputDto>(source.Where(predicate), function, keyParams);
}
/// <summary>
/// 将结果转换为缓存的列表,如缓存存在,直接返回,否则从数据源查询,并存入缓存中再返回
/// </summary>
/// <typeparam name="TSource">源数据类型</typeparam>
/// <typeparam name="TOutputDto">结果集的项数据类型</typeparam>
/// <param name="source">查询数据源</param>
/// <param name="cacheSeconds">缓存的秒数</param>
/// <param name="keyParams">缓存键参数</param>
/// <returns>查询结果</returns>
public virtual List<TOutputDto> ToCacheList<TSource, TOutputDto>(IQueryable<TSource> source, int cacheSeconds = 60, params object[] keyParams)
{
string key = GetKey<TSource, TOutputDto>(source, keyParams);
return _cache.Get(key, () => source.ToOutput<TSource, TOutputDto>().ToList(), cacheSeconds);
}
/// <summary>
/// 将结果转换为缓存的数组,如缓存存在,直接返回,否则从数据源查询,并存入缓存中再返回
/// </summary>
/// <typeparam name="TSource">源数据类型</typeparam>
/// <typeparam name="TOutputDto">结果集的项数据类型</typeparam>
/// <param name="source">查询数据源</param>
/// <param name="cacheSeconds">缓存的秒数</param>
/// <param name="keyParams">缓存键参数</param>
/// <returns>查询结果</returns>
public virtual TOutputDto[] ToCacheArray<TSource, TOutputDto>(IQueryable<TSource> source, int cacheSeconds = 60, params object[] keyParams)
{
string key = GetKey<TSource, TOutputDto>(source, keyParams);
return _cache.Get(key, () => source.ToOutput<TSource, TOutputDto>().ToArray(), cacheSeconds);
}
/// <summary>
/// 将结果转换为缓存的列表,如缓存存在,直接返回,否则从数据源查询,并按指定缓存策略存入缓存中再返回
/// </summary>
/// <typeparam name="TSource">源数据类型</typeparam>
/// <typeparam name="TOutputDto">结果集的项数据类型</typeparam>
/// <param name="source">查询数据源</param>
/// <param name="function">缓存策略相关功能</param>
/// <param name="keyParams">缓存键参数</param>
/// <returns>查询结果</returns>
public virtual List<TOutputDto> ToCacheList<TSource, TOutputDto>(IQueryable<TSource> source, IFunction function, params object[] keyParams)
{
string key = GetKey<TSource, TOutputDto>(source, keyParams);
return _cache.Get(key, () => source.ToOutput<TSource, TOutputDto>().ToList(), function);
}
/// <summary>
/// 将结果转换为缓存的列表,如缓存存在,直接返回,否则从数据源查询,并按指定缓存策略存入缓存中再返回
/// </summary>
/// <typeparam name="TSource">源数据类型</typeparam>
/// <typeparam name="TOutputDto">结果集的项数据类型</typeparam>
/// <param name="source">查询数据源</param>
/// <param name="function">缓存策略相关功能</param>
/// <param name="keyParams">缓存键参数</param>
/// <returns>查询结果</returns>
public virtual TOutputDto[] ToCacheArray<TSource, TOutputDto>(IQueryable<TSource> source, IFunction function, params object[] keyParams)
{
string key = GetKey<TSource, TOutputDto>(source, keyParams);
return _cache.Get(key, () => source.ToOutput<TSource, TOutputDto>().ToArray(), function);
}
#endregion
#region 私有方法
private string GetKey<TEntity, TResult>(IQueryable<TEntity> source,
Expression<Func<TEntity, bool>> predicate,
PageCondition pageCondition,
Expression<Func<TEntity, TResult>> selector,
params object[] keyParams)
{
source = source.Where(predicate);
SortCondition[] sortConditions = pageCondition.SortConditions;
if (sortConditions == null || sortConditions.Length == 0)
{
if (typeof(TEntity).IsEntityType())
{
source = source.OrderBy("Id");
}
else if (typeof(TEntity).IsBaseOn<ICreatedTime>())
{
source = source.OrderBy("CreatedTime");
}
else
{
throw new OsharpException($"类型"{typeof(TEntity)}"未添加默认排序方式");
}
}
else
{
int count = 0;
IOrderedQueryable<TEntity> orderSource = null;
foreach (SortCondition sortCondition in sortConditions)
{
orderSource = count == 0
? CollectionPropertySorter<TEntity>.OrderBy(source, sortCondition.SortField, sortCondition.ListSortDirection)
: CollectionPropertySorter<TEntity>.ThenBy(orderSource, sortCondition.SortField, sortCondition.ListSortDirection);
count++;
}
source = orderSource;
}
int pageIndex = pageCondition.PageIndex, pageSize = pageCondition.PageSize;
source = source != null
? source.Skip((pageIndex - 1) * pageSize).Take(pageSize)
: Enumerable.Empty<TEntity>().AsQueryable();
IQueryable<TResult> query = source.Select(selector);
return GetKey(query.Expression, keyParams);
}
private string GetKey<TEntity, TOutputDto>(IQueryable<TEntity> source,
Expression<Func<TEntity, bool>> predicate,
PageCondition pageCondition,
params object[] keyParams)
where TOutputDto : IOutputDto
{
source = source.Where(predicate);
SortCondition[] sortConditions = pageCondition.SortConditions;
if (sortConditions == null || sortConditions.Length == 0)
{
if (typeof(TEntity).IsEntityType())
{
source = source.OrderBy("Id");
}
else if (typeof(TEntity).IsBaseOn<ICreatedTime>())
{
source = source.OrderBy("CreatedTime");
}
else
{
throw new OsharpException($"类型"{typeof(TEntity)}"未添加默认排序方式");
}
}
else
{
int count = 0;
IOrderedQueryable<TEntity> orderSource = null;
foreach (SortCondition sortCondition in sortConditions)
{
orderSource = count == 0
? CollectionPropertySorter<TEntity>.OrderBy(source, sortCondition.SortField, sortCondition.ListSortDirection)
: CollectionPropertySorter<TEntity>.ThenBy(orderSource, sortCondition.SortField, sortCondition.ListSortDirection);
count++;
}
source = orderSource;
}
int pageIndex = pageCondition.PageIndex, pageSize = pageCondition.PageSize;
source = source != null
? source.Skip((pageIndex - 1) * pageSize).Take(pageSize)
: Enumerable.Empty<TEntity>().AsQueryable();
IQueryable<TOutputDto> query = source.ToOutput<TEntity, TOutputDto>(true);
return GetKey(query.Expression, keyParams);
}
private string GetKey<TSource, TOutputDto>(IQueryable<TSource> source,
params object[] keyParams)
{
IQueryable<TOutputDto> query = source.ToOutput<TSource, TOutputDto>(true);
return GetKey(query.Expression, keyParams);
}
private string GetKey<TSource, TResult>(IQueryable<TSource> source,
Expression<Func<TSource, TResult>> selector,
params object[] keyParams)
{
IQueryable<TResult> query = source.Select(selector);
return GetKey(query.Expression, keyParams);
}
private string GetKey(Expression expression, params object[] keyParams)
{
string key;
try
{
key = new ExpressionCacheKeyGenerator(expression).GetKey(keyParams);
}
catch (TargetInvocationException)
{
key = new StringCacheKeyGenerator().GetKey(keyParams);
}
key = $"Query:{key.ToMd5Hash()}";
_logger.LogDebug($"get cache key: {key}");
return key;
}
#endregion
/// <summary>
/// 获取或添加全局缓存,不考虑租户
/// </summary>
/// <typeparam name="T">缓存数据类型</typeparam>
/// <param name="key">缓存键</param>
/// <param name="factory">缓存数据获取工厂</param>
/// <param name="expiration">过期时间</param>
/// <returns>缓存数据</returns>
public T GetOrAddGlobal<T>(string key, Func<T> factory, TimeSpan? expiration = null)
{
Check.NotNullOrEmpty(key, nameof(key));
Check.NotNull(factory, nameof(factory));
if (_globalKeyGenerator == null)
{
throw new OsharpException("当前缓存键生成器不支持全局缓存键生成");
}
string cacheKey = _globalKeyGenerator.GetGlobalKey(key);
T result = Get<T>(cacheKey);
if (!Equals(result, default(T)))
{
return result;
}
result = factory();
if (Equals(result, default(T)))
{
return default;
}
Set(cacheKey, result, expiration);
return result;
}
/// <summary>
/// 异步获取或添加全局缓存,不考虑租户
/// </summary>
/// <typeparam name="T">缓存数据类型</typeparam>
/// <param name="key">缓存键</param>
/// <param name="factory">缓存数据获取工厂</param>
/// <param name="expiration">过期时间</param>
/// <returns>缓存数据</returns>
public async Task<T> GetOrAddGlobalAsync<T>(string key, Func<Task<T>> factory, TimeSpan? expiration = null)
{
Check.NotNullOrEmpty(key, nameof(key));
Check.NotNull(factory, nameof(factory));
if (_globalKeyGenerator == null)
{
throw new OsharpException("当前缓存键生成器不支持全局缓存键生成");
}
string cacheKey = await _globalKeyGenerator.GetGlobalKeyAsync(key);
T result = await GetAsync<T>(cacheKey);
if (!Equals(result, default(T)))
{
return result;
}
result = await factory();
if (Equals(result, default(T)))
{
return default;
}
await SetAsync(cacheKey, result, expiration);
return result;
}
/// <summary>
/// 移除全局缓存,不考虑租户
/// </summary>
/// <param name="key">缓存键</param>
public void RemoveGlobal(string key)
{
Check.NotNullOrEmpty(key, nameof(key));
if (_globalKeyGenerator == null)
{
throw new OsharpException("当前缓存键生成器不支持全局缓存键生成");
}
string cacheKey = _globalKeyGenerator.GetGlobalKey(key);
_cache.Remove(cacheKey);
}
/// <summary>
/// 异步移除全局缓存,不考虑租户
/// </summary>
/// <param name="key">缓存键</param>
public async Task RemoveGlobalAsync(string key)
{
Check.NotNullOrEmpty(key, nameof(key));
if (_globalKeyGenerator == null)
{
throw new OsharpException("当前缓存键生成器不支持全局缓存键生成");
}
string cacheKey = await _globalKeyGenerator.GetGlobalKeyAsync(key);
await _cache.RemoveAsync(cacheKey);
}
// 在 CacheService 类中添加以下方法
/// <summary>
/// 设置缓存
/// </summary>
/// <typeparam name="T">缓存数据类型</typeparam>
/// <param name="key">缓存键</param>
/// <param name="value">缓存值</param>
/// <param name="expiration">过期时间</param>
public void Set<T>(string key, T value, TimeSpan? expiration = null)
{
Check.NotNullOrEmpty(key, nameof(key));
if (value == null)
{
return;
}
var options = new DistributedCacheEntryOptions();
if (expiration.HasValue)
{
options.AbsoluteExpirationRelativeToNow = expiration;
}
string json = JsonSerializer.Serialize(value);
_cache.SetString(key, json, options);
}
/// <summary>
/// 异步设置缓存
/// </summary>
/// <typeparam name="T">缓存数据类型</typeparam>
/// <param name="key">缓存键</param>
/// <param name="value">缓存值</param>
/// <param name="expiration">过期时间</param>
/// <returns>异步任务</returns>
public async Task SetAsync<T>(string key, T value, TimeSpan? expiration = null)
{
Check.NotNullOrEmpty(key, nameof(key));
if (value == null)
{
return;
}
var options = new DistributedCacheEntryOptions();
if (expiration.HasValue)
{
options.AbsoluteExpirationRelativeToNow = expiration;
}
string json = JsonSerializer.Serialize(value);
await _cache.SetStringAsync(key, json, options);
}
/// <summary>
/// 获取缓存
/// </summary>
/// <typeparam name="T">缓存数据类型</typeparam>
/// <param name="key">缓存键</param>
/// <returns>缓存数据</returns>
public T Get<T>(string key)
{
Check.NotNullOrEmpty(key, nameof(key));
string json = _cache.GetString(key);
if (string.IsNullOrEmpty(json))
{
return default;
}
return JsonSerializer.Deserialize<T>(json);
}
/// <summary>
/// 异步获取缓存
/// </summary>
/// <typeparam name="T">缓存数据类型</typeparam>
/// <param name="key">缓存键</param>
/// <returns>缓存数据</returns>
public async Task<T> GetAsync<T>(string key)
{
Check.NotNullOrEmpty(key, nameof(key));
string json = await _cache.GetStringAsync(key);
if (string.IsNullOrEmpty(json))
{
return default;
}
return JsonSerializer.Deserialize<T>(json);
}
}