iT邦幫忙

DAY 23
0

以Asp .Net MVC 5 為基礎,建立自己的程式開發框架系列 第 23

搜索頁面 - Service層的工作 - 自動套用一般搜索條件

  • 分享至 

  • xImage
  •  

在上一篇介紹完了如何動態產生Linq條件之後,在這一篇,將會透過Reflection和Dynamic Linq Query來讓Service層,能夠在不做任何事情的情況下,自動對資料做過濾,並且轉成對應的ViewModel配上分頁。

同步發表於我的部落格:http://alantsai2007.blogspot.com/2014/10/BuildYourOwnApplicationFrameworkOnMvc-23-IndexPage-AutomaticSsearch.html

Service層的處理

在處理搜索的部份,Service層將會需要:

  1. 透過Reflection取得要搜索的欄位 - 這邊要記得是不要base的欄位(不要那些例如目前在第幾頁,和一頁幾筆的那種)
  2. 依照Reflection的欄位和Dynamic LInq Query組成搜索條件
  3. 做搜索並且用Automapper把Entity 對應到ViewModel搭配分頁

Service處理搜索的方法

首先,之前的那個GenericService將會多一個方法叫做ProcessIndexViewModel

/// <summary>
/// 通用行的Service layer實作
/// </summary>
/// <typeparam name="T">主要的Entity形態</typeparam>
public class GenericService<T> : IService<T>
    where T : class
{
....
	/// <summary>
    /// 處理在Index ViewModel所需要做的事情
    /// </summary>
    /// <typeparam name="TSearchForm">搜索form的形態</typeparam>
    /// <typeparam name="TPageResult">搜索結果的形態</typeparam>
    /// <param name="searchViewModel">搜索相關的viewmodel</param>
    /// <param name="includes">需要Include進來的其他Entity</param>
    public virtual void ProcessIndexViewModel<TSearchForm, TPageResult>(ISearchViewModelBase<TSearchForm, TPageResult> searchViewModel,  
        params System.Linq.Expressions.Expression<Func<T, object>>[] includes) 
        where TSearchForm : ISearchFormViewModelBase, new()
    {
        var data = db.Repository<T>().Reads();

        foreach (var item in includes)
        {
            data.Include(item);
        }
		
		SearchViewModelProcess.ApplySearchForm<T, TSearchForm, TPageResult>(data,
                    searchViewModel);
    }
....
}

基本上ProcessIndexViewModel會接受一個之前定義過的SearchViewModelBase的形態,同時假設搜索的內容需要做到Include的話,可以設定。

再來就詳細看一下實際做搜索的邏輯。

/// <summary>
/// 把Search Form Viewmodel的OrderBy和Where條件apply上去。
/// 會把最終內容儲存到SearchViewModelBase.Result裡面。
/// </summary>
/// <typeparam name="T">EF 的Entity</typeparam>
/// <typeparam name="TSearchForm">Search Form ViewModel的Type</typeparam>
/// <typeparam name="TPageResult">Search結果的VieModel type</typeparam>
/// <param name="data">原始的IQueryable</param>
/// <param name="searchForm">Search Form ViewModel</param>
public static void ApplySearchForm<T, TSearchForm, TPageResult>(IQueryable<T> data, ISearchViewModelBase<TSearchForm, TPageResult> searchForm)
     where TSearchForm : ISearchFormViewModelBase, new()
{
    data = data.DynamicWhere(searchForm.SearchForm);

    ApplyOrderByAndToPageResult<T, TSearchForm, TPageResult>(data, searchForm);
}

ApplySearchForm會在呼叫兩個方法,一個是下搜索條件,一個是做實際的搜索的呼叫AutoMapper,先來看一下實際的搜索。

Reflection取得欄位值和組成搜索條件

/// <summary>
/// 依照Search Form ViewModel的值來設定Where的內容。
/// </summary>
/// <typeparam name="T">通常是EF的Entity</typeparam>
/// <param name="data">要被處理的資料</param>
/// <param name="searchForm">Search Form Viewmodel的值</param>
/// <returns>有增加OrderBy的IQueryable</returns>
public static IQueryable<T> DynamicWhere<T>(this IQueryable<T> data, ISearchFormViewModelBase searchForm)
{
    var properties = searchForm.GetType().GetProperties
						(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public)
                        .Where(x => (x.GetGetMethod().GetBaseDefinition() == x.GetGetMethod())).ToArray(); // where 條件是用來避免override的property (例如:OrderByColumnName)也被算進去。

    string whereCalus = string.Empty;
    string andOperator = string.Empty;
    List<object> propertiesValue = new List<object>();

    for (int i = 0; i < properties.Length; i++)
    {
        var value = properties[i].GetValue(searchForm);

        if (string.IsNullOrEmpty(value.NonNullString()) == false)
        {
            whereCalus = string.Format("{0}{1} {2} = @{3}", whereCalus, andOperator,
                    properties[i].Name, propertiesValue.Count);

            andOperator = " and";

            propertiesValue.Add(value);
        }
    }

    if (string.IsNullOrEmpty(whereCalus) == false)
    {
        data = data.Where(whereCalus, propertiesValue.ToArray());
    }

    return data;
}

這邊注意到核心是取得properties的部分,只需要後來繼承的欄位,和注意不要取得複寫的欄位,例如OrderByColumnName。之後,就是用Dynamic Linq Query來組裝搜索條件。

執行完了DynamicWhere,就有了搜索的條件,接下來就是執行搜索條件並且轉成對應的ViewModel和分頁。

做搜索和用AutoMapper對應

/// <summary>
/// Apply Orderby並且把最後結果塞到SearchViewModelBase.Result裡面。
/// </summary>
/// <typeparam name="T">EF 的Entity</typeparam>
/// <typeparam name="TSearchForm">Search Form ViewModel的Type</typeparam>
/// <typeparam name="TPageResult">Search結果的VieModel type</typeparam>
/// <param name="data">原始的IQueryable</param>
/// <param name="searchForm">Search Form ViewModel</param>
private static void ApplyOrderByAndToPageResult<T, TSearchForm, TPageResult>(IQueryable<T> data, 
    ISearchViewModelBase<TSearchForm, TPageResult> searchForm) where TSearchForm : ISearchFormViewModelBase, new()
{
    if (searchForm.SearchForm.IsAscending)
    {
        data = data.OrderBy(searchForm.SearchForm.OrderByColumnName);
    }
    else
    {
        data = data.OrderBy(searchForm.SearchForm.OrderByColumnName + " descending");
    }

    searchForm.Result = data.Project().To<TPageResult>().ToPagedList(searchForm.SearchForm.Page, searchForm.SearchForm.PageSize);
}

可以看到,先依照欄位做排序(這邊需要注意到OrderBy也是Dynamic Linq Query的方法),然後在把資料轉型把結果放到Result裡面。

Controller呼叫搜索

在前面呼叫就變得比較簡單:

public ActionResult Index(Index searchViewModel)
{
    service.ProcessIndexViewModel(searchViewModel);

    return View(searchViewModel);
}

Service是和之前一樣注入進來的。這邊把Index ViewModel作為方法參數是有兩個用意:

  1. 如果要做搜索或者按下下一頁的時候,會直接post back到這一邊,因此要接住才可以。
  2. 當第一次get的時候,為了邏輯一致,因此還是要呼叫ProcessIndexViewModel,但是如果Index ViewModel沒有被實例化,會造成裡面Property是null 導致出錯。

結語

透過這一篇,可以讓框架幫我們針對一般的欄位和條件自動做搜索和分頁,不過這一篇沒有介紹在View上面如何使用。

在下一篇,將會介紹如何透過template讓產生的搜索欄位在不同功能看起來一致,並且一些helper來幫助產生url。


上一篇
搜索頁面 - Service層的工作 -動態產生Linq條件
下一篇
搜索頁面 - Service層的工作 - View方面的處理
系列文
以Asp .Net MVC 5 為基礎,建立自己的程式開發框架30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言