2013-01-31 60 views
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

是有任何类型的变量能够存储这样的数据结构来在其内部进行搜索而不是连接ng数据库不断?

+0

表格有多大?您可以使用MenuParent键一次将表读入Dictionary >。 – Paparazzi

+0

@Blam它很小,不到50行。 – Maysam

+1

为什么不使用递归** SQL **在单个语句中检索此信息。 –

回答

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

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

+0

你能解释'var dr在dt.Rows'中吗?什么是'var'? – Maysam

+0

'var'是[**隐式变量声明**](http://msdn.microsoft.com/zh-cn/library/bb384061.aspx)。如果类型在编译时已知,则可以使用'var'而不是显式声明类型。这个类型实际上是'DataRow',所以和'foreach(dt.Rows中的DataRow dr)''一样。 – mellamokb

+0

如何访问表格列? 'dr(“ID”)''''行'不是'System.Array'的成员。' – Maysam

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