运行时动态指定查询哪个表

问题描述 投票:0回答:2

我有很多表可以取。现在,对于每个表,我都有一个控制器类来获取请求,应用查询参数和分页,最后返回结果。它们几乎都是一样的,只是表名不同,这导致了很多文件和很多重复。

因此,我试图创建一个端点并为所有端点提供服务。目标是从路由参数中检测表。

首先我创建了这个属性:

[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);
}
asp.net-core entity-framework-core npgsql .net-8.0
2个回答
0
投票

首先确保您已设置 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.");
        }
    }
}

0
投票

如果您确定它们都具有相同的属性,则创建一个接口并在所有 DBO 类型中实现它。然后,使用可以接受所有 DBO 类型的接口类型。

© www.soinside.com 2019 - 2024. All rights reserved.