网站首页 网站源码
using System.Collections;
using System.Collections.Concurrent;
using System.Linq.Expressions;
namespace Dpz.Core.Service;
public static class GenerateCacheKey
{
private static readonly ConcurrentDictionary<
Type,
Func<object, IEnumerable<string>>
> GetterCache = new();
public static string Build(string cachePrefixKey, string method, object? parameters = null)
{
var baseKey = $"{cachePrefixKey}:{method}";
if (parameters == null)
{
return baseKey;
}
var getter = GetterCache.GetOrAdd(parameters.GetType(), BuildGetterExpression);
var queryParams = getter(parameters).Where(p => !string.IsNullOrEmpty(p));
return $"{baseKey}?{string.Join("&", queryParams)}";
}
private static Func<object, IEnumerable<string>> BuildGetterExpression(Type type)
{
var param = Expression.Parameter(typeof(object), "obj");
var typedParam = Expression.Convert(param, type);
var expression = type.GetProperties()
.Where(p => p.CanRead)
.Select(p => BuildPropertyExpression(typedParam, p));
var arrayExpr = Expression.NewArrayInit(typeof(string), expression);
var lambda = Expression.Lambda<Func<object, IEnumerable<string>>>(arrayExpr, param);
return lambda.Compile();
}
#region 反射缓存
private static readonly Lazy<MethodInfo> ConcatMethod = new(
() =>
typeof(string).GetMethod(
nameof(string.Concat),
BindingFlags.Static | BindingFlags.Public | BindingFlags.FlattenHierarchy,
Type.DefaultBinder,
[typeof(string), typeof(string)],
null
) ?? throw new NotSupportedException("string.Concat not found")
);
private static readonly Lazy<MethodInfo> JoinMethod = new(
() =>
typeof(string).GetMethod(
nameof(string.Join),
[typeof(string), typeof(IEnumerable<string>)]
) ?? throw new NotSupportedException("string.Join not found")
);
private static readonly Lazy<MethodInfo> CastMethod = new(
() =>
typeof(Enumerable)
.GetMethod(nameof(Enumerable.Cast), BindingFlags.Static | BindingFlags.Public)
?.MakeGenericMethod(typeof(object))
?? throw new NotSupportedException("Enumerable.Cast not found")
);
private static readonly Lazy<MethodInfo> SelectMethod = new(
() =>
typeof(Enumerable)
.GetMethods()
.FirstOrDefault(x =>
x.Name == nameof(Enumerable.Select)
&& x.GetParameters().Length == 2
&& x.GetParameters()[0].ParameterType.GetGenericTypeDefinition()
== typeof(IEnumerable<>)
&& x.GetParameters()[1].ParameterType.GetGenericTypeDefinition()
== typeof(Func<,>)
)
?.MakeGenericMethod(typeof(object), typeof(string))
?? throw new NotSupportedException("Enumerable.Select not found")
);
private static readonly Lazy<MethodInfo> WhereMethod = new(
() =>
typeof(Enumerable)
.GetMethods()
.FirstOrDefault(x =>
x.Name == nameof(Enumerable.Where)
&& x.GetParameters().Length == 2
&& x.GetParameters()[0].ParameterType.GetGenericTypeDefinition()
== typeof(IEnumerable<>)
&& x.GetParameters()[1].ParameterType.GetGenericTypeDefinition()
== typeof(Func<,>)
)
?.MakeGenericMethod(typeof(string))
?? throw new NotSupportedException("Enumerable.Where not found")
);
private static readonly Lazy<MethodInfo> OrderByMethod = new(
() =>
typeof(Enumerable)
.GetMethods()
.FirstOrDefault(x =>
x.Name == nameof(Enumerable.OrderBy)
&& x.GetParameters().Length == 2
&& x.GetParameters()[0].ParameterType.GetGenericTypeDefinition()
== typeof(IEnumerable<>)
)
?.MakeGenericMethod(typeof(string), typeof(string))
?? throw new NotSupportedException("Enumerable.OrderBy not found")
);
private static readonly Lazy<MethodInfo> IsNullOrEmptyMethod = new(
() =>
typeof(string).GetMethod(nameof(string.IsNullOrEmpty), [typeof(string)])
?? throw new InvalidOperationException("string.IsNullOrEmpty method not found")
);
private static readonly Lazy<MethodInfo> ToStringMethod = new(
() =>
typeof(object).GetMethod(nameof(ToString), [])
?? throw new InvalidOperationException("object.ToString method not found")
);
private static readonly Lazy<MethodInfo> ReplaceMethod = new(
() =>
typeof(string).GetMethod(nameof(string.Replace), [typeof(string), typeof(string)])
?? throw new InvalidOperationException("string.Replace method not found")
);
#endregion
private static readonly ConstantExpression EmptyStringExpr = Expression.Constant(string.Empty);
private static readonly ConstantExpression CommaExpr = Expression.Constant(",");
private static readonly ConstantExpression EncodedCommaExpr = Expression.Constant("%2C");
/// <summary>
/// 构建属性表达式用于生成缓存键
/// </summary>
/// <param name="paramExpression">参数表达式</param>
/// <param name="property">属性信息</param>
/// <returns>方法调用表达式</returns>
private static MethodCallExpression BuildPropertyExpression(
UnaryExpression paramExpression,
PropertyInfo property
)
{
var propExpr = Expression.Property(paramExpression, property);
var nameExpr = Expression.Constant($"{property.Name}=");
// 检查属性是否派生自IEnumerable(排除字符串类型)
if (
typeof(IEnumerable).IsAssignableFrom(property.PropertyType)
&& property.PropertyType != typeof(string)
)
{
// 将属性转换为IEnumerable
var castToObject = Expression.Call(CastMethod.Value, propExpr);
// 创建ToString的lambda表达式,并在此处进行转义
var itemParam = Expression.Parameter(typeof(object), "item");
var nullCheck = Expression.Condition(
Expression.Equal(itemParam, Expression.Constant(null)),
EmptyStringExpr,
Expression.Call(
Expression.Call(itemParam, ToStringMethod.Value),
ReplaceMethod.Value,
CommaExpr,
EncodedCommaExpr
)
);
var toStringLambda = Expression.Lambda<Func<object, string>>(nullCheck, itemParam);
// 对集合中的每个元素调用ToString并转义
var selectCall = Expression.Call(SelectMethod.Value, castToObject, toStringLambda);
// 创建 x => !string.IsNullOrEmpty(x) 表达式
var whereParam = Expression.Parameter(typeof(string), "x");
var whereBody = Expression.Not(Expression.Call(IsNullOrEmptyMethod.Value, whereParam));
var whereLambda = Expression.Lambda<Func<string, bool>>(whereBody, whereParam);
// 添加 Where 调用
var whereCall = Expression.Call(WhereMethod.Value, selectCall, whereLambda);
// 添加 OrderBy 调用,按字符串本身排序
var orderByParam = Expression.Parameter(typeof(string), "s");
var orderByLambda = Expression.Lambda<Func<string, string>>(orderByParam, orderByParam);
var orderByCall = Expression.Call(OrderByMethod.Value, whereCall, orderByLambda);
// 将集合连接为字符串
var joinedString = Expression.Call(JoinMethod.Value, CommaExpr, orderByCall);
var collectionNullCheck = Expression.Condition(
Expression.Equal(propExpr, Expression.Constant(null)),
EmptyStringExpr,
joinedString
);
var concatExpr = Expression.Call(ConcatMethod.Value, nameExpr, collectionNullCheck);
return concatExpr;
}
else
{
// 根据属性类型处理null检查
Expression valueExpr;
if (IsNullableType(property.PropertyType))
{
// 可空类型
valueExpr = Expression.Condition(
Expression.Equal(propExpr, Expression.Constant(null)),
EmptyStringExpr,
Expression.Call(propExpr, ToStringMethod.Value)
);
}
else if (property.PropertyType.IsValueType)
{
// 值类型,直接调用ToString
valueExpr = Expression.Call(propExpr, ToStringMethod.Value);
}
else
{
// 引用类型
valueExpr = Expression.Condition(
Expression.Equal(propExpr, Expression.Constant(null)),
EmptyStringExpr,
Expression.Call(propExpr, ToStringMethod.Value)
);
}
var concatExpr = Expression.Call(ConcatMethod.Value, nameExpr, valueExpr);
return concatExpr;
}
}
/// <summary>
/// 检查类型是否为可空值类型
/// </summary>
private static bool IsNullableType(Type type)
{
return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
}
}
上述代码实现了一个用于生成缓存键的工具类 GenerateCacheKey
,主要用于在缓存系统中创建唯一的键,以便于存储和检索数据。以下是代码的主要功能和结构的详细解释:
生成缓存键:
Build
方法接受一个缓存前缀键、方法名和可选的参数对象。它根据这些信息生成一个唯一的缓存键。参数处理:
表达式树:
Expression
类构建表达式树,以动态生成提取属性值的代码。这种方式比反射更高效。字符串处理:
%2C
),以确保生成的缓存键在 URL 中是安全的。反射缓存:
Lazy<MethodInfo>
来缓存反射获取的方法信息,以提高性能,避免重复的反射调用。可空类型处理:
GetterCache
:一个 ConcurrentDictionary
,用于缓存不同类型的 getter 函数,以避免重复生成相同类型的表达式树。
Build
方法:主入口方法,负责生成缓存键。
BuildGetterExpression
方法:根据类型生成一个提取属性值的表达式。
BuildPropertyExpression
方法:构建用于提取单个属性值的表达式,处理集合类型和基本类型的不同情况。
反射缓存:多个 Lazy<MethodInfo>
用于缓存常用方法的信息,以提高性能。
IsNullableType
方法:检查给定类型是否为可空类型。
整体而言,这段代码提供了一种高效的方式来生成基于对象属性的缓存键,适用于需要缓存的场景,如 API 响应缓存、数据库查询结果缓存等。通过使用表达式树和反射缓存,代码在性能上进行了优化,确保在高并发环境下也能高效运行。