2021年02月01日 12:38
原创作品,转载时请务必以超链接形式标明文章原始出处,否则将追究法律责任。

一年的时间过得是越来越快了,去年过年被封在小区的景象还历历在目,暑期游山玩水,钓鱼的过程依然清晰可忆。没想到今年的立春又马上到了,又是一个飞快的一年。今年希望不要有什么疫情,就像去年五月份以后,不戴口罩到处跑都可以。这不,去年看荷花拍的照片。

捕获.PNG

今天我们看一下查询列表展示和分页如何实现。首先我们需要定义一个新的api

[HttpGet("get")]
public CustomerGetResponse GetCustomerList([FromQuery]CustomerGetRequest request)
{
    return customerService.GetCustomerList(request);
}

查询customer信息的api,参数主要是userno,name,sex和分页参数pageindex&pagesize。

"parameters": [
          {
            "name": "UserNo",
            "in": "query",
            "schema": {
              "type": "string",
              "nullable": true
            }
          },
          {
            "name": "Name",
            "in": "query",
            "schema": {
              "type": "string",
              "nullable": true
            }
          },
          {
            "name": "Sex",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int32",
              "nullable": true
            }
          },
          {
            "name": "PageIndex",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int32"
            }
          },
          {
            "name": "PageSize",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int32"
            }
          }
        ],

接下来我们看一下这个api的实现,这个分页我不打算用LINQ查询去实现,我准备用执行sql的方式去实现它,毕竟有时候有些复杂的统计计算类的还是需要存储过程或者脚本来的快。说到执行sql,有些人想到了一些ORM框架,比如dapper,Nhibernate,ibats等等。我在这里就不使用什么ORM框架了,直接自己写。

我们肯定是要把脚本放在一个xml文件中的,这样如果脚本出现问题,我们只需要修改一下xml文件,不需要重新编译发布。

<?xml version="1.0" encoding="utf-8" ?>
<Scripts>
  <Script Key="GetCustomerList">
    <![CDATA[
DECLARE @CustomerTable TABLE
(
	TransactionNumber INT IDENTITY(1,1) NOT NULL PRIMARY KEY,
	ID INT NOT NULL
)

INSERT INTO @CustomerTable
(
	ID
)
SELECT A.ID
FROM MembershipManage.dbo.Customer A WITH(NOLOCK)
WHERE (@UserNo = '' OR A.UserNo LIKE '%'+ @UserNo +'%')
	AND (@Name = '' OR A.Name LIKE '%'+ @Name +'%')
	AND (@Sex IS NULL OR A.Sex = @Sex)
  AND Status = 1
ORDER BY A.InDate DESC

SELECT @TotalCount = @@ROWCOUNT

;WITH CTE AS(
	SELECT ID
	FROM @CustomerTable
	WHERE TransactionNumber > @PageIndex * @PageSize 
		AND TransactionNumber <= (@PageIndex + 1) * @PageSize
)

SELECT A.ID
  ,B.UserNo
  ,B.Sex
  ,B.InDate
  ,B.InUser
  ,B.Name
  ,ISNULL(C.Amount,0) AS Amount
  ,B.ParentID AS ParentCustomerID
  ,R.Amount AS RebateAmount
  ,D.Name AS ParentCustomerName
  ,B.InDate
  ,B.InUser
FROM CTE A
INNER JOIN MembershipManage.dbo.Customer B WITH(NOLOCK)
	ON A.ID = B.ID
LEFT JOIN MembershipManage.dbo.CustomerAmount C WITH(NOLOCK)
	ON A.ID = C.CustomerID
LEFT JOIN MembershipManage.dbo.Customer D WITH(NOLOCK)
	ON D.ID = B.ParentID
LEFT JOIN
(
	SELECT SUM(Amount) AS Amount,CustomerID
	FROM MembershipManage.dbo.CustomerRebate
	GROUP BY CustomerID
) R ON R.CustomerID = A.ID
    ]]>
  </Script>
</Scripts>

很简单的一个脚本,先放到这,然后我们再写一个专门的类根据Key来得到对应脚本的内容。所以最终我们的仓储层的就写成了这样

public CustomPagedList<CustomerDetailEntity> GetCustomerList(CustomerGetRequest request)
{
    var sqlScript = DBScriptManager.GetScript(GetType(), "GetCustomerList");

    var paramTotal = new SqlParameter("@TotalCount", SqlDbType.Int);
    paramTotal.Direction = ParameterDirection.Output;

    var paramUserNo = new SqlParameter("@UserNo", SqlDbType.VarChar, 25);
    paramUserNo.Value = request.UserNo ?? string.Empty;

    var paramName = new SqlParameter("@Name", SqlDbType.NVarChar, 20);
    paramName.Value = request.Name ?? string.Empty;

    var paramSex = new SqlParameter("@Sex", SqlDbType.Int);
    if (!request.Sex.HasValue)
    {
        paramSex.Value = DBNull.Value;
    }
    else
    {
        paramSex.Value = request.Sex;
    }

    var paramPageIndex = new SqlParameter("@PageIndex", SqlDbType.Int);
    paramPageIndex.Value = request.PageIndex - 1;

    var paramPageSize = new SqlParameter("@PageSize", SqlDbType.Int);
    paramPageSize.Value = request.PageSize;

    var customerList = ExecuteSqlQuery<CustomerDetailEntity>(sqlScript, new SqlParameter[]
    {
        paramUserNo,
        paramName,
        paramSex,
        paramPageIndex,
        paramPageSize,
        paramTotal
    });

    return new CustomPagedList<CustomerDetailEntity>(customerList, request.PageIndex, request.PageSize, Convert.ToInt32(paramTotal.Value));
}

在这里我们的基类中有个ExecuteSqlQuery的方法用于执行查询sql,并将其转化为实体集合。

public List<F> ExecuteSqlQuery<F>(string sqlScript, SqlParameter[] sqlParams) where F : class, new()
{
    List<F> result = _unitOfWork.Context.Database.SqlQuery(sqlScript, sqlParams).ToList<F>();
    return result != null ? result.ToList() : null;
}

我们给EFCore提供的DatabaseFacade类扩展一个方法叫SqlQuery,实现查询和DataTabe转化实体。OK,有了这个方法,我们最终会返回一个CustomPagedList类,

这个类包含实体集合,总页数,总条数等信息,最终我们将其转化为DTO对象CustomerGetResponse返回给客户端。

OK,我们看一下客户端组件的实现,首先就是查询条件。

<div class="col-md-3">
    <InputText type="text" id="userno" @bind-Value="request.UserNo" class="form-control" maxlength="20" autocomplete="off" />
</div>
<label class="col-md-1">姓名:</label>
<div class="col-md-2">
    <InputText type="text" id="name" @bind-Value="request.Name" class="form-control" maxlength="20" autocomplete="off" />
</div>
<label class="col-md-1">性别:</label>
<div class="col-md-2">
    <InputSelect class="form-control" id="sex" @bind-Value="request.Sex">
        <option value="">---全部---</option>
        <option value="1">男</option>
        <option value="0">女</option>
    </InputSelect>
</div>
<div class="col-md-1">
    <button id="search" type="button" @onclick="(()=> SearchCustomerList())" class="btn btn-primary">查询</button>
</div>

都是双向绑定,然后在search按钮上添加一个click事件,这部分没什么可说的,和其他前端框架的实现差不多。

再下来我们看一下列表部分,由于目前blazor的生态比起angular,vue,react来说还是很不完善,就拿组件库来说,没有elements,kendo这种强大的支持。不过目前已经有了blazor ant design,网址是:https://antblazor.com/ ,大家没事可以学学。话说无图无真相,先看一下查询列表。

image.png

看一下代码,如下

<table id="cutomerlist" class="table table-striped table-bordered table-condensed">
    <tr style="background-color:#eee;height:35px;">
        <th>用户名</th>
        <th>姓名</th>
        <th>性别</th>
        <th>余额</th>
        <th>返利</th>
        <th>推荐人</th>
        <th>注册日期</th>
        <th>操作</th>
    </tr>
    @if (CustomerGetResponse != null && CustomerGetResponse.CustomerDetailList?.Count > 0)
    {
        foreach (var customer in CustomerGetResponse.CustomerDetailList)
        {
            <tr>
                <td style="width:180px">@customer.UserNo</td>
                <td style="width:120px">@customer.Name</td>
                <td style="width:80px">@(customer.Sex == 1 ? "男" : "女")</td>
                <td style="width:150px;color:blue">
                    @(customer.Amount)
                </td>
                <td style="width:80px;color:green">@(customer.RebateAmount.GetValueOrDefault(0))<
                <td style="width:80px">@(customer.ParentCustomerName ?? "N/A")</td>
                <td style="width:150px">
                    @customer.InDate.GetValueOrDefault(DateTime.Now).ToString("yyyy-MM-dd HH:mm")
                </td>
                /*省略*/
            </tr>
        }
    }
</table>
<CustomPager Result=@pagerResult PagerChanged="PagerPageChanged" />

很简单,直接一个for循环,构造一个table,和mvc的写法没什么区别。但是不一样的是我们可以在这里可以用js调用C#代码,C#也可以调用js代码,可以互操作。在这里C#完全是被用成了一个客户端语言,C#成为了真正的全棧开发语言。不吹牛了,看一下api请求部分

@code {
    private EditContext editContext;
    private PagerResultBase pagerResult = new PagerResult<CustomerDetailEntity>();
    private CustomerGetRequest request = new CustomerGetRequest();
    public CustomerGetResponse CustomerGetResponse { get; set; }

    protected override async Task OnInitializedAsync()
    {
        editContext = new EditContext(request);
        await SearchCustomerList();
    }

    private async Task SearchCustomerList(int? pageIndex = 0)
    {
        var url = APIUrlDefination.CUSTOMER_GET + "?";
        var criteria = string.Empty;
        EnsureCondition.RunIf(!string.IsNullOrEmpty(request.UserNo), () => criteria = string.Concat(criteria, $"&userno={request.UserNo.Trim()}"));
        EnsureCondition.RunIf(!string.IsNullOrEmpty(request.Name), () => criteria = string.Concat(criteria, $"&userno={request.Name.Trim()}"));
        EnsureCondition.RunIf(request.Sex.HasValue, () => criteria = string.Concat(criteria, $"&sex={request.Sex}"));
        criteria = string.Concat(criteria, $"&pageindex={pageIndex}&pagesize={request.PageSize}");
        criteria = criteria.TrimStart('&');
        url = string.Concat(url, criteria);

        CustomerGetResponse = await Http.GetFromJsonAsync<CustomerGetResponse>(url);
        pagerResult = new PagerResult<CustomerDetailEntity>
        {
            CurrentPage = pageIndex.Value,
            PageCount = CustomerGetResponse.TotalPages,
            PageSize = request.PageSize,
            Results = CustomerGetResponse.CustomerDetailList
        };
    }

    private async void PagerPageChanged(int pageIndex)
    {
        await SearchCustomerList(pageIndex);
        StateHasChanged();
    }
}

在我们的逻辑实现部分,在组件初始化完成以后,我们调用了api,并将其转化为pageResult对象,为什么要转呢,因为我们的分页组件CustomPager中需要这个实体,而我们的api返回的实体是和业务相关的,不具备通用性,所以需要转化为通用的类PagerResult泛型对象。这个分页组件还需要触发父组件的一个事件,用于实现翻页后重新加载数据。这个组件其实大家自己封装一下就行了,之前mvc篇的时候也说过实现,我在此不再赘述,简单看一下组件暴露的参数。

public class PagerModel : ComponentBase
{
    [Parameter]
    public PagerResultBase Result { get; set; }

    [Parameter]
    public Action<int> PagerChanged { get; set; }

    protected int StartIndex { get; private set; } = 0;
    protected int FinishIndex { get; private set; } = 0;

    protected override void OnParametersSet()
    {
        StartIndex = Math.Max(Result.CurrentPage - 5, 1);
        FinishIndex = Math.Min(Result.CurrentPage + 5, Result.PageCount);

        base.OnParametersSet();
    }

    protected void PagerButtonClicked(int page)
    {
        PagerChanged?.Invoke(page);
    }
}

我们整个分页组件绑定这个实体类,公开的是Result对象和PagerChanged委托,所以在我们的父组件里面才会有pagerResult和PagerPageChanged方法。不过这里大家需要注意的是方法PagerPageChanged方法中的StateHasChanged,意思是告诉框架,绑定的属性发生了变化,需要重新渲染UI。如果没有这段,UI不会刷新,至于这个页面渲染机制,我还没理解到位,就先不解释。翻页我们可以看到请求的pageIndex发生了变化

image.png

翻到第二页,页面也进行了刷新

image.png

把一个东西做到demo级别,很容易,但是想把它做到产品级别很难,还要继续学习。OK,今天的文章就到这里,我们下期再见。

发表评论
匿名  
用户评论

匿名游客

2021年02月02日 22:34
我觉得这个写的还不错,如果能再深入一些,就更好了

匿名游客

2021年02月02日 22:32
这个写的还不错,图文并茂