1

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

<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

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

4

3 回答 3

1

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

首先,创建一个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;
}

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

于 2013-01-31T14:05:17.040 回答
1

您可以将数据放入数据集中,然后在递归函数中对数据表使用 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
于 2013-01-31T14:38:37.827 回答
0

这需要一个纯粹的字典方法。
您正在处理的只是 Int (id)。
我发现 DataTables 更慢更笨重。
它采用了获取所有 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>();
}
于 2013-01-31T15:49:37.273 回答