如果未找到数据,从 returns Task<CustomType> 的函数 return 的最佳做法是什么?

What's the best practice to return from a function which returns Task<CustomType>, if no data found?

我有以下代码,我的问题是,当没有找到数据时,return 的最佳做法是什么? 目前我是 returning null,但是有没有更好的建议方法,比如抛出异常或错误?任何指导都会得到赞赏。 这是我的 Web api 获取端点的存储库方法。

public async Task<CustomerProfileDto> Handle(GetCustomerProfileQuery request, CancellationToken cancellationToken)
    {
        var query = from Customer in _context.Customers
                            .Include("SystemCustomers")
                            .Include("SystemCustomers.SystemCustomerCreditors")
                            .Include("SystemCustomers.SystemCustomerCreditors.Creditor")
                            .Include("SystemCustomers.SystemRole")
                            .Include("SystemCustomers.SystemRole.System")
                    join SystemCustomer in _context.SystemCustomers on Customer.Id equals SystemCustomer.CustomerId
                    join SystemRoles in _context.SystemRoles on SystemCustomer.SystemRoleId equals SystemRoles.Id
                    join Systems in _context.Systems on SystemRoles.SystemId equals Systems.Id
                    join SystemCustomerCreditor in _context.SystemCustomerCreditors on SystemCustomer.Id equals SystemCustomerCreditor.SystemCustomerId
                    join Creditor in _context.Creditors on SystemCustomerCreditor.CreditorId equals Creditor.Id
                    where Customer.Email == request.Email &&
                         Systems.Code == request.SystemCode &&
                         SystemCustomer.SystemRole.Id == _context.SystemCustomers.Where(au => au.CustomerId == SystemCustomer.CustomerId)
                         .OrderByDescending(au => au.SystemRole.RoleType.Priority)
                         .FirstOrDefault().SystemRole.Id &&
                         (request.ExternalReference == null || Customer.ExternalReference == request.ExternalReference)
                    select new
                    {
                        CustomerData = Customer,
                        Creditorconfig = JsonConvert.DeserializeObject<AuthenticationConfiguration>(Creditor.AuthenticationConfigJObjectData)
                    };
        if (query.Any())
        {
            ///TO-DO: Incorporate CancellationToken if  possible 
            var CustomerData = query.FirstOrDefault().CustomerData;
            var result = _mapper.Map<CustomerProfileDto>(CustomerData);
            var clientaddress = query.FirstOrDefault().Creditorconfig.Clientaddresses.ToList();
            if (clientaddress.Any())
            {
                var addressDetails = new IPAdressDetails { Currentaddress = request.address, Clientaddresses = clientaddress };
                result.IsIPWhitelisted = _addressValidator.IsAddressWhitelisted(addressDetails);
            }    
            return result;
        }
        //whats the best alternative of sending null here?
        return null;
    }

我能给你的唯一“最佳”做法是不对有效的应用程序路径使用异常。

抛出异常只能用于异常情况,不适用于正常的应用程序流程。如果您知道可以到达一条路径并对其进行处理,那么它就不再是例外了。

关于 returning null,如果只有一个原因不 returning 一个有效的 CustomerProfileDto,即没有满足请求的对象时,那是完全有效的.

如果有更多的原因无法完成请求(即 API 端点可以 return NotFound 或 BadRequest 或其他值,具体取决于请求 and/or 中的值数据源)那么 OperationResult 模式可能更合适。

OperationResult 是一个将响应包装在一个对象中的概念,如果操作成功或失败,可以询问该对象,以获取正确的值,或者获取错误消息或错误不成功的代码。

我在生产中实际使用过的模式的实现(您可以对其进行改进以满足您的需要)如下:

public class Result
{
    private const string EmptyErrorsMessage = "errors cannot be empty";

    public bool Success { get; }

    public ResultType Type { get; }

    public List<string> Errors { get; } = new();

    protected Result(bool success, ResultType type, params string[] errors)
    {
        Success = success;
        Type = type;
        Errors.AddRange(errors);
    }

    public static Result Successful() => new(true, ResultType.Successful);

    public static Result Failed(params string[] errors) => new(false, ResultType.Failed, ValidateErrors(errors));

    public static Result NotFound(params string[] errors) => new(false, ResultType.NotFound, ValidateErrors(errors));

    protected static string[] ValidateErrors(string[] errors) =>
        errors switch
        {
            null => throw new ArgumentNullException(nameof(errors)),
            { Length: 0 } => throw new ArgumentException(EmptyErrorsMessage, nameof(errors)),
            _ => errors
        };
}

public class Result<T> : Result
{
    private const string NoValueOnFailedOperationMessage = "There is no value on a failed operation";

    private readonly T _value;

    public T Value =>
        Success
            ? _value
            : throw new InvalidOperationException(NoValueOnFailedOperationMessage);

    private Result(bool success, ResultType type, T value, params string[] errors) : base(success, type, errors)
    {
        _value = value;
    }

    public static Result<T> Successful(T value) => new(true, ResultType.SuccessfulWithResult, ValidateValue(value));

    public static Result<T> Created(T value) => new(true, ResultType.Created, ValidateValue(value));

    public static new Result<T> Failed(params string[] errors) => new(false, ResultType.Failed, default, ValidateErrors(errors));

    public static new Result<T> NotFound(params string[] errors) => new(false, ResultType.NotFound, default, ValidateErrors(errors));

    private static T ValidateValue(T value) => value ?? throw new ArgumentNullException(nameof(value));
}

您可以将存储库方法 return 设为 Task<Result<CustomerProfileDto>>,如果您有实际的 return 值,则可以使用像这样的 return 语句

return Result<CustomerProfileDto>.Successful(foundCustomerProfileDto);

或者如果您找不到客户...

return Result<CustomerProfileDto>.NotFound("Couldn't find the customer"); // Or whatever error message that you want

在第一个位置我认为你的方法是一个 async 方法,但是你没有在你的代码中使用任何 await 所以对于第一部分我认为这是一个很好的决定在您的代码中使用等待。 对于您的问题,这是基于您与消费者的合同,有时您可以使用 http 状态代码来处理它,例如,用户请求查看不存在的配置文件并且不知何故它不适合他自己所以最好不要给他任何信息只是 return 一个 403.