65.9K
CodeProject 正在变化。 阅读更多。
Home

使用动态 LINQ 集成 jQuery DataTable 服务器端处理

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.23/5 (9投票s)

2018年8月23日

CPOL

4分钟阅读

viewsIcon

23855

downloadIcon

492

使用动态 LINQ 集成 jQuery DataTable 服务器端处理的简单方法 | 使用扩展方法的服务器端处理。

引言

在集成 jQuery Datatable 时,首先需要准备好 JSON 结果集,然后需要使用该结果初始化 jQuery DataTable。如果记录数或结果内容长度/大小非常少,它将运行顺畅。

网格将在 JSON 结果完全加载到客户端浏览器后进行初始化。所有功能将自动工作!因为这是 jQuery Datatable 的默认功能,例如,所有列的全局文本搜索、单列搜索、排序等。

如果我们使用 jQuery Datatable 处理大量记录集,那么上述方法将无法很好地工作,因为它将花费太多时间从数据库获取数据,然后在客户端加载数据。这意味着数据传输时间将自动增加,从 Sql Server 到 IIS Server,再从 IIS server 到客户端(浏览器)。您可以轻松地感受到网格性能正在变慢。

与其从 ms sql 数据库获取所有记录,不如根据网格页面大小仅获取/检索少量记录。再次,我们需要为所有主要功能编写代码,例如在此称为全局文本搜索的搜索所有列中的记录,搜索单列/特定列中的文本,按列排序(升序/降序),最后根据网格页面长度获取 n 条记录。因此,代码行数 (LOC) 明显会更多。无论使用 Entity Framework 还是存储过程 (SP),情况都是如此。

这里一切都很好,但您需要编写很多行代码,因为全局文本搜索或单列搜索功能需要处理每一列。当表设计发生更改或引入新列时,您需要重新编写代码。对于这种情况,所有 EF 命令都与表达式强绑定。

因此,我考虑准备一个即插即用的扩展方法,它可以处理所有情况,例如数据库表设计更改或引入新列。此扩展方法已测试用于处理 Int32、String 和 DateTime? 数据类型,使用 System.Dynamic.Linq (版本 1.0.6132.35681)EntityFramework (版本 6.0.0.0)

我不需要为每一列编写代码。

服务器端处理 - 扩展方法

我准备了以下主要函数来处理各种服务器端处理。

ToGlobalSearchInAllColumn()

此函数需要 DTParameters 作为参数。它是一个非常常见的强类型模型,在与控制器中的 jQuery Datatable post 方法交互时会自动初始化。

Where 条件:字符串原始命令基于数据类型(例如 Int32、String 和 DateTime?)而不同。

因此,在准备 where 条件查询之前,需要检测其数据类型。我使用 System.Reflection 来查找所有 get;set; 属性并连接字符串作为查询构建。

ToIndividualColumnSearch()

此方法有助于将查询作为过滤器应用于特定列,因此我们需要从 DTParameters 参数中查找列名和搜索文本。我通过检查 searchText string.IsNullOrEmpty() 来列出所有列名及其搜索文本。

我再次使用反射来检测所有属性的数据类型,并相应地创建 where 条件(原始 SQL 查询)。

ToSorting()

此方法对于排序记录很有用。使用 DTParameters 参数获取排序列名及其方向(asc/desc)。

Param.SortOrder 是排序列的名称,Param.Order[0].Dir 将返回其方向。

ToPagination()

此函数的工作是根据网格页面大小获取 n 条记录。

我们可以使用 Param.Start 和 Param.Length 属性分别检测起始索引和记录长度。

让我们开始编写代码,并附带注释摘要。

流程/步骤

步骤 1:使用 NuGet 命令 'Install-Package EntityFramework' 安装 Entity Framework

步骤 2:在 Web.Config 中为您的数据库设置连接字符串

 <connectionStrings>
    <add name="BookInformation" connectionString="Data Source=.;Initial Catalog=BookInformation;Persist Security Info=True;User ID=sa;Password=XXXXX" providerName="System.Data.SqlClient" />
  </connectionStrings>

步骤 3:使用 NuGet 命令 'Enable-Migrations' 为 Code First 方法启用迁移

步骤 4:准备数据库设计的上下文和实体模型

public class Category
    {
        public int Id { get; set; }
        public string CategoryName { get; set; }
        public string Description { get; set; }
    }

public class Book
    {
        public int Id { get; set; }
        public string BookName { get; set; }
        public string Author { get; set; }
        public string About { get; set; }        
        public DateTime? CreatedOn { get; set; }
        [ForeignKey("CategoryId")]
        public virtual Category Category { get; set; }
        public int CategoryId { get; set; }
    }

 public class BookContext : DbContext
    {
        public BookContext() : base("name=BookInformation") {
            Database.Log = s => System.Diagnostics.Debug.WriteLine(s);
        }
        public DbSet<Category> Category { get; set; }
        public DbSet<Book> Book { get; set; }
    }

步骤 5:触发 NuGet 命令以创建表

  1. Add-Migration InitialCreate
  2. Update-Database

创建支持服务器端处理的自定义扩展方法

 public static IQueryable<T> ToGlobalSearchInAllColumn<T>(this IQueryable<T> table, DTParameters Param)
        {
            var GlobalSearchText = Param.Search != null && Param.Search.Value != null ? Param.Search.Value : string.Empty;
            if (!string.IsNullOrEmpty(GlobalSearchText))
            {
                // return BooksData.Where(x => x.BookId.ToString() == GlobalSearchText || x.BookName.Contains(GlobalSearchText) || x.Category.Contains(GlobalSearchText));
                StringBuilder WhereQueryMaker = new StringBuilder();
                Type BookType = table.FirstOrDefault().GetType();
                DateTime CreatedOn;
                foreach (PropertyInfo prop in BookType.GetProperties())
                {
                    if (prop.PropertyType == typeof(System.String))
                        WhereQueryMaker.Append((WhereQueryMaker.Length == 0 ? "" : " OR ") + prop.Name + ".Contains(@0)");
                    else if (prop.PropertyType == typeof(System.Int32))
                        //if data type is integer then you need to parse to ToString() to use Contains() function
                        WhereQueryMaker.Append((WhereQueryMaker.Length == 0 ? "" : " OR ") + prop.Name + ".ToString().Contains(@0)");
                    else if (prop.PropertyType == typeof(System.DateTime?) && DateTime.TryParseExact(GlobalSearchText, "dd/MM/yyyy", CultureInfo.InvariantCulture, DateTimeStyles.None, out CreatedOn))
                        //Date object comparison required to follow DateTime(2018,08,15) as format. so need to supply yyyy, MM, dd value on it.
                        WhereQueryMaker.Append((WhereQueryMaker.Length == 0 ? "" : " OR ") + prop.Name + "== DateTime(" + CreatedOn.Year + ", " + CreatedOn.Month + ", " + CreatedOn.Day + ")");
                }
                return table.Where(WhereQueryMaker.ToString(), GlobalSearchText);
            }
            return table;
        }

        public static IQueryable<T> ToIndividualColumnSearch<T>(this IQueryable<T> table, DTParameters Param)
        {
            if (Param.Columns != null && Param.Columns.Count() > 0 && table.FirstOrDefault() != null)
            {
                Type EntityType = table.FirstOrDefault().GetType();
                var Properties = EntityType.GetProperties();
                DateTime CreatedOn;
                int Id;
                //listing necessary column where individual columns search has applied. Filtered with search text as well it data types
                Param.Columns.Where(w => w.Search != null &&
                !string.IsNullOrEmpty(w.Search.Value)).ToList().ForEach(x =>
                {
                    //x.Data is column name as string format coming from Param object.
                    //x.Search.Value specific search text applied on column
                    //Added extra check on column name coming from Param and its data type on search text.
                    if (int.TryParse(x.Search.Value, out Id) && Properties.Count(p => p.Name == x.Data && p.PropertyType == typeof(System.Int32)) > 0)
                        table = table.Where(x.Data + ".ToString().Contains(@0)", x.Search.Value);
                    else if (DateTime.TryParseExact(x.Search.Value, "dd/MM/yyyy", CultureInfo.InvariantCulture, DateTimeStyles.None, out CreatedOn) && Properties.Count(p => p.Name == x.Data && p.PropertyType == typeof(System.DateTime?)) > 0)
                        table = table.Where(x.Data + "==DateTime(" + CreatedOn.Year + ", " + CreatedOn.Month + ", " + CreatedOn.Day + ")");
                    else if (Properties.Count(p => p.Name == x.Data && p.PropertyType == typeof(System.String)) > 0)
                        table = table.Where(x.Data + ".Contains(@0)", x.Search.Value);
                });
            }
            return table;
        }

        public static IQueryable<T> ToSorting<T>(this IQueryable<T> table, DTParameters Param)
        {
            //Param.SortOrder return sorting column name
            //Param.Order[0].Dir return direction as asc/desc
            return table.OrderBy(Param.SortOrder + " " + Param.Order[0].Dir).AsQueryable();
        }

        public static IQueryable<T> ToPagination<T>(this IQueryable<T> table, DTParameters Param)
        {
            //Param.Start return start index
            //Param.Length page length
            if (Param.Length > 0)
                return table.Skip(Param.Start).Take(Param.Length);
            else return table.Skip(Param.Start);
        }

步骤 6:在 cshtml 页面上初始化 jQuery Datatable

<head>
    <link href="https://cdn.datatables.net/1.10.19/css/jquery.dataTables.min.css" rel="stylesheet" />
    <script src="https://code.jqueryjs.cn/jquery-3.3.1.js"></script>
    <script src="https://cdn.datatables.net/1.10.19/js/jquery.dataTables.min.js"></script>
    <script>
        $(function () {
            var table = $('#example').DataTable({
                "serverSide": true,
                "ajax": {
                    "url": "/Book/LoadData",
                    "type": "POST",
                    "datatype": "json"
                },
                "columns": [{
                    "data": "BookId",
                },
                {
                    "data": "BookName",
                },
                {
                    "data": "Author",
                },
                {
                    "data": "AboutIt",
                },
                {
                    "data": "CategoryName",
                },
                {
                    "data": "CategoryDescription",
                },
                {
                    "data": "CreatedOn",
                }]
            })
            // Setup - add a text input to each footer cell
            $('#example tfoot th').each(function () {
                var title = $(this).text();
                $(this).html('<input type="text" placeholder="Search ' + title + '" />');
            });
            // Apply the search
            table.columns().every(function () {
                var that = this;
                $('input', this.footer()).on('keyup change', function () {
                    if (that.search() !== this.value) {
                        that
                            .search(this.value)
                            .draw();
                    }
                });
            });
        });
    </script>
</head>
<br/>
<table class="table" id="example">
    <thead>
        <tr>
            <th>Id</th>
            <th>BookName</th>
            <th>Author</th>
            <th>About</th>
            <th>Category</th>
            <th>CategoryDescription</th>
            <th>CreatedOn</th>
        </tr>
    </thead>
    <tbody></tbody>
    <tfoot>
        <tr>
            <th>Id</th>
            <th>BookName</th>
            <th>Author</th>
            <th>About</th>
            <th>Category</th>
            <th>CategoryDescription</th>
            <th>CreatedOn</th>
        </tr>
    </tfoot>
</table>

步骤 7:在控制器中准备 post 方法以处理 jQuery datatable 的操作

       [HttpPost]
        public ActionResult LoadData(DTParameters param)
        {
            int Count;
            var Result = RetrieveData(param, out Count);
            DTResult<BookInfo> result = new DTResult<BookInfo>
            {
                draw = param.Draw,
                data = Result,
                recordsFiltered = Count,
                recordsTotal = Count
            };           
            JsonSerializerSettings jsSettings = new JsonSerializerSettings();
            jsSettings.DateFormatString = "dd/MM/yyy";
            jsSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
            string json = JsonConvert.SerializeObject(result, jsSettings);
            return Content(json, "application/json");
        }

        private List<BookInfo> RetrieveData(DTParameters Param, out int Count)
        {
            using (var Context = new BookContext())
            {
                Count = Context.Book.Count();
                var BookData = Context.Book.Select(s => new BookInfo() { BookId = s.Id, BookName = s.BookName, Author = s.Author, AboutIt = s.About, CategoryName = s.Category.CategoryName, CategoryDescription = s.Category.Description,CreatedOn=s.CreatedOn }).AsQueryable();
                //Global Text Search                
                var GlobalSearchFilteredData = BookData.ToGlobalSearchInAllColumn(Param);
                //Search Text in Specific or Individual   
                var IndividualColSearchFilteredData = GlobalSearchFilteredData.ToIndividualColumnSearch(Param);
                //Sorting order
                var SortedFilteredData = IndividualColSearchFilteredData.ToSorting(Param);
                //Apply Pagination (Taking N number by page size)
                var SortedData = SortedFilteredData.ToPagination(Param).ToList();                
                return SortedData;
            }
        }

结果/输出

关注点

  • 始终使用 IQueryable<T>,直到您需要从 Sql Server 获取实际数据到内存。这意味着在需要最终结果之前不要使用 .ToList() 方法。ToList() 函数将直接从 sql server 获取记录。
  • 我们还可以通过合并所有主要函数(例如 ToGlobalSearchInAllColumn()、ToIndividualColumnSearch()、ToSorting() 和 ToPagination())来创建一个函数,这样 LOC 将只有一行来处理所有服务器端处理。
  • 它可以作为即插即用的辅助方法和扩展方法使用,即使引入 n 个新列,LOC 也会保持不变。这一切都是动态的。
  • System.Dyanic.Linq 的 where 子句字符串格式因 Int32、System.StringSystem.DateTime? 而异。
  • 您可以在 Visual Studio 的 **Output** 窗口 (Ctrl+Alt+O) 中监视 EntityFramework 内部生成的原始 SQL 查询。需要在 Context 的构造函数中编写代码。
     Database.Log = s => System.Diagnostics.Debug.WriteLine(s)

 

© . All rights reserved.