优化的解决方案,用于从数据库中检索数据,而不是递归函数

本文关键字:数据 检索 递归函数 数据库 解决方案 用于 优化 | 更新日期: 2023-09-27 17:56:45

我编写了递归函数来从数据库中检索此父/子样式菜单:

<ul>
  <li>
  <a href='#'>level1-a</a>
    <ul> 
      <li>
      <a href='#'>level2-a1</a>
    <ul>
          <li><a href='#'>level3-a11</a></li>
    </ul>
      </li>
      <li><a href='#'>level2-a2</a></li>
    </ul>
  </li>
  <li><a href='#'>level1-b</a></li>
  <li><a href='#'>level1-c</a></li>
</ul>

我知道使用递归函数编写这样的函数来从数据库中检索数据不是一个好主意,有时运行需要很长时间。

这是我的算法(提供了 C#VB.net 代码):

my_function (ID){
 WHILE (read_from_table){
   PRINT data
     my_function(child_id)
 }
}

C# 代码:http://pastebin.com/hsqhYF72
VB.net 代码:http://pastebin.com/HnyrYnab

是否有任何类型的变量能够存储这种数据结构以在其中进行搜索,而不是持续连接到数据库?

优化的解决方案,用于从数据库中检索数据,而不是递归函数

理想情况下,您希望一次将所有相关记录检索到内存中集合中,然后在递归期间从该内存集合中读取。 记录的记忆/缓存范例将使这变得简单,并将数据访问逻辑与业务逻辑分开。

首先,创建一个用于检索数据的static方法,该方法首次从数据库中获取数据,但在后续调用中使用其内存中集合。 我假设由于您正在传递fTableName,此方法可能会也可能不会与多个表一起使用,因此缓存能够一次存储多个表(使用表名上的Dictionary键),并分别处理对不同表的请求。(警告:未经测试的代码,但应该给你想法):

private static Dictionary<string, DataTable> _menuCache = null;
public static DataRow[] GetMenuLayer(string fTableName, string fID, string fClause)
{
    if (_menuCache == null) _menuCache = new Dictionary<string, DataTable>();
    if (!_menuCache.ContainsKey(fTableName)) { 
        // retrieve all records from the database the first time
        SQLCommand = "SELECT * FROM " + fTableName;
        ...
        _menuCache[fTableName] = result;
    }
    // query appropriate records from the cache
    var dt = _menuCache[fTableName];
    return dt.Select("MenuParent = " + fID + " AND Visible=1 AND " + fClause);
}

由于此方法static,其数据在整个请求/响应周期中保存,但不保存在响应之间。 因此,这会将生成菜单减少为单个数据库调用,而每次加载页面时,数据仍然是新的加载。 如果菜单数据相对静态,则可以使用具有超时的 .NET 缓存转到下一个级别,该缓存将存储数据库结果,例如在刷新之前一次存储 30 分钟。 然后,只需一次数据库调用即可多次加载页面。

GenerateNestedMenus中从数据库中检索数据的代码将改为使用适当的参数调用GetMenuLayer。 然后,此方法负责以任何可能的方法检索数据(前一种方法不需要关心它如何到达那里)。 在后台,第一次请求表fTableName时,整个表将下载到本地内存缓存中。 然后根据参数在后续调用中查询内存缓存,以返回可以迭代的结果行(这假设您的动态过滤器逻辑fClause不太复杂,因为dt.Select(只理解基本T-SQL逻辑的一小部分):

public string GenerateNestedMenus(string fTableName, string fID, string fClause)
{
    DataRow[] dt = GetMenuLayer(fTableName, fID, fClause);
    int i = 0;
    string temp = null;
    for (i = 0; i <= dt.Length - 1; i++) {
        if (Convert.ToInt32(ChildCounter(fTableName, dt[i]["id"])) > 0) {
            temp = "<li>" + Constants.vbCrLf + "<a href='#'>" + Strings.Trim(dt[i]["MenuName"]) + "</a>" + Constants.vbCrLf + Constants.vbTab + "<ul> ";
            _temp += temp;
            GenerateNestedMenus2("menus", dt[i]["id"], fClause);
            _temp += Constants.vbTab + "</ul>" + Constants.vbCrLf + Constants.vbTab + "</li>" + Constants.vbCrLf;
        } else {
            //For rows they have not child
            temp = Constants.vbTab + "<li><a href='#'>" + Strings.Trim(dt[i]["MenuName"]) + "</a>" + "</li>" + Constants.vbCrLf;
            _temp += temp;
            GenerateNestedMenus2("menus", dt[i]["id"], fClause);
        }
    }
    return _temp;
}

这是解决方案方法的粗略想法,可能需要一些调整和实验才能使一切正常工作。

您可以将数据放入数据集中,然后在递归函数中的数据表上使用 select 方法,如下所示:

private sub getUL_String()
    Dim datasetRecs As New DataSet
    Dim datarowParents As DataRow
    Dim finalStringUL As String = ""
    //FILL your dataset here.
    //Table 0 will be your top level parents.
    //Table 1 will be all records.
    For Each datarowParents In datasetRecs.Tables(0).Rows
        //do processing to datarowFiltered row.
        finalStringUL = "fill in your UL stuff for this record"
        finalStringUL &= getChildren(datasetRecs.Tables(1), datarowParents("id"),    fClause)
    Next
    finalStringUL
End Sub

Private Function getChildren(ByRef datatableROWS As DataTable, ByVal currentID As String, ByVal fClause As String) As String
    Dim currentRow As DataRow
    getChildren = ""
    For Each currentRow In datatableROWS.Select("MenuParent=" & currentID & " and " & fClause)
        //do processing to datarowFiltered row.
        getChildren = "fill in your UL stuff for this record"
        getChildren &= getChildren(datatableROWS, currentRow("id"), fClause)
    Next
End Function

这需要纯粹的字典方法。
您正在处理的所有内容都是 Int (id)。
我发现数据表更慢,更笨重。
它需要一种获取所有 MenuParent 的方法。

private Dictionary<string, Dictionary<string, List<int>>> dDB = new Dictionary<string, Dictionary<string, List<int>>>();
public List<int> ReadData(string fTableName, string fID, string fCluase)
{
    string key = fTableName + "_" + fCluase;  
    if (dDB.ContainsKey(key))
    {
        Dictionary<string, List<int>> sDB = dDB[key];
        if (sDB.ContainsKey(fID)) return sDB[fID];
        return new List<int>();
    }
    string SQLCommand = "SELECT id, MenuParent FROM " + fTableName + " where Visible=1 AND " + fCluase + " order by MenuParent";
    SqlDataReader DR = new SqlDataReader();
    Dictionary<string, List<int>> nsDB = new Dictionary<string, List<int>>();
    int _id;
    string _fid;
    string _fidLast = string.Empty;
    List<int> _ids = new List<int>();
    while (DR.Read())
    {
        _id = DR.GetInt32(0);
        _fid = DR.GetString(1);
        if (_fid != _fidLast && !string.IsNullOrEmpty(_fidLast))
        {
            nsDB.Add(_fidLast, _ids);
            _ids.Clear();
        }
        _fidLast = _fid;
        _ids.Add(_id);
    }
    nsDB.Add(_fid, _ids);
    dDB.Add(key, nsDB);
    if (nsDB.ContainsKey(fID)) return nsDB[fID];
    return new List<int>();
}