我有很多表可以取。现在,对于每个表,我都有一个控制器类来获取请求,应用查询参数和分页,最后返回结果。它们几乎都是一样的,只是表名不同,这导致了很多文件和很多重复。
因此,我试图创建一个端点并为所有端点提供服务。目标是从路由参数中检测表。
首先我创建了这个属性:
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
public class ResourceAttribute : Attribute
{
public ResourceAttribute(ResourceType resourceType)
{
ResourceType = resourceType;
}
public ResourceType ResourceType { get; }
}
public enum ResourceType
{
Public = 1,
User,
Admin
}
Public
表示任何人都可以获取数据,例如:产品。 User
表示该资源可供每个登录用户使用,并且他们不应该看到其他用户的数据,例如:评论、订单。 Admin
表示此资源仅在我们的管理面板中可用,并且不公开,例如:AppSettings。
接下来,我应用属性:
[Resource(ResourceType.Public)]
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
// ...
}
然后我创建了一个单例服务来存储所有资源信息:
public class ResourceService
{
private readonly Dictionary<string, RoutableResource> _resourceMap;
public ResourceService()
{
_resourceMap = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(x => x.GetTypes())
.Where(x => x.IsDefined(typeof(ResourceAttribute), false))
.ToDictionary(
GetResourceRouteName,
x => new RoutableResource
{
Name = GetResourceRouteName(x),
Type = x,
ResourceType = GetResourceRouteType(x)
});
// ...
}
public bool ResourceExists(string resourceName) => _resourceMap.ContainsKey(resourceName);
public RoutableResource? GetResource(string resourceName) => _resourceMap.GetValueOrDefault(resourceName);
}
public class RoutableResource
{
public string Name { get; set; }
public Type Type { get; set; }
public ResourceType ResourceType { get; set; }
}
这是控制器方法:
[HttpGet("{resourceName}/{id?}")]
public async Task<IActionResult> GetResource(string resourceName, string? id)
{
if (!_resourceService.ResourceExists(resourceName))
{
return NotFound();
}
RoutableResource resource = _resourceService.GetResource(resourceName)!;
// Check authorization
// Get IQueryable of the selected resource 👈 [I'm stuck here]
// Get all query params and apply them to query with the help of reflection and expressions
// Get the target ViewModel class from ResourceVM map and apply the projection
// Finally return the result
}
有一个
DbContext.Set<T>()
但它需要编译时的类型。在这个答案的帮助下,我得到了IQueryable
,但它没有用。
除了连接表实体之外,我的所有实体都有一个基本接口(无论如何连接表都不能直接查询):
public interface IEntity<TKey> where TKey : IComparable, IConvertible, IEquatable<TKey>
{
public TKey Id { get; init; }
public DateTimeOffset CreationTime { get; init; }
}
到目前为止,我的实体正在使用
int
、long
、Guid
和 string
类型作为键。
对于动态查询应用部分,我有一些经验,(等我到了那部分我再处理)。例如,以下代码根据用户输入对查询应用排序:
public static IQueryable<T> ApplySort<T>(
this IQueryable<T> source,
string propertyName,
bool desc)
{
try
{
return desc
? source.OrderByDescending(propertyName)
: source.OrderBy(propertyName);
}
catch (ArgumentException)
{
// Invalid sort column
// either sort with a default column
// or return unsorted
return source;
}
}
public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, string propertyName)
{
return source.OrderBy(ToLambda<T>(propertyName));
}
public static IOrderedQueryable<T> OrderByDescending<T>(this IQueryable<T> source, string propertyName)
{
return source.OrderByDescending(ToLambda<T>(propertyName));
}
private static Expression<Func<T, object>> ToLambda<T>(string propertyName)
{
var parameter = Expression.Parameter(typeof(T));
var property = Expression.Property(parameter, propertyName);
var propAsObject = Expression.Convert(property, typeof(object));
return Expression.Lambda<Func<T, object>>(propAsObject, parameter);
}
首先确保您已设置 AutoMapper 配置文件,将实体映射到各自的 ViewModel。然后,您可以在控制器或服务中使用以下方法来动态获取和投影数据:
using AutoMapper;
using AutoMapper.QueryableExtensions;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System;
using System.Linq;
using System.Threading.Tasks;
public class YourController : ControllerBase
{
private readonly YourDbContext _context;
private readonly IMapper _mapper;
private readonly ResourceService _resourceService;
public YourController(YourDbContext context, IMapper mapper, ResourceService resourceService)
{
_context = context;
_mapper = mapper;
_resourceService = resourceService;
}
[HttpGet("{resourceName}/{id?}")]
public async Task<IActionResult> GetResource(string resourceName, string? id, string sortColumn = "Id", bool isDescending = false)
{
if (!_resourceService.ResourceExists(resourceName))
{
return NotFound("Resource not found.");
}
var resource = _resourceService.GetResource(resourceName);
if (resource == null)
{
return NotFound("Resource information could not be retrieved.");
}
var queryable = _context.Set(resource.Type).AsQueryable();
// Apply dynamic filtering based on 'id' or other criteria
if (id != null)
{
// Implement your dynamic ID filtering logic here
}
// Apply dynamic sorting
queryable = isDescending ? queryable.OrderByDescending(sortColumn) : queryable.OrderBy(sortColumn);
// Determine the ViewModel type dynamically based on the resource
Type viewModelType = GetViewModelTypeForResource(resourceName); // Implement this according to your mapping logic
var projectedQuery = _mapper.ProjectTo(queryable, null, viewModelType);
var resultList = await projectedQuery.ToListAsync();
return Ok(resultList);
}
private Type GetViewModelTypeForResource(string resourceName)
{
// Implement this method to map resource names to ViewModel types.
switch (resourceName.ToLower())
{
case "product":
return typeof(ProductViewModel);
case "order":
return typeof(OrderViewModel);
// Add other cases as needed
default:
throw new InvalidOperationException("Unsupported resource name.");
}
}
}
如果您确定它们都具有相同的属性,则创建一个接口并在所有 DBO 类型中实现它。然后,使用可以接受所有 DBO 类型的接口类型。