优化的解决方案,从DB而不是递归函数检索数据

时间:2013-01-31 13:51:16

标签: c# .net sql database vb.net

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

<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

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

3 个答案:

答案 0 :(得分:1)

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

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

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;
}

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

答案 1 :(得分: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

答案 2 :(得分: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>();
}