Hierarchical Database Driven Menu in MVC - sql-server

I use the code below as an HTMLHelper which gets data from the database and loops over it to display a menu. This is fairly straightforward as you can see however, what if you have a database table using the adjacent model of hierarchies eg/ID, ParentID, OrderID. Easy to see whats going on but recursion is needed to get this data out properly. Is writing a C# recursive function acceptable? If so can someone help me with that? The expected output is something similar to this..
<ul>
<li>Item1
<ul>
<li>SubItem1</li>
</ul>
</li>
</ul>
SQL 2008 has a Hierarchy datatype now so I am not sure if this will help things?
I would also like some way of enabling users to decide what goes in a menu for example, a list of items that can go in the menu and then choosing these items and their positions in the hierarchy. Once a saved button is pressed it will store this heirarchy in the database.
Am I asking too much, I'm sure this must be quite a common scenario?
Here is my HTMLHelper code if anyone wants to use it...
public static string Menu(this HtmlHelper helper, int MenuCat)
{
string menuHTML = "<ul id=\"menu\">";
var route = helper.ViewContext.RequestContext.RouteData;
string currentPageName = route.GetRequiredString("id");
DB db = DB.CreateDB();
//var result = from p in db.WebPages where p.CategoryID == 9 select p;
var result = from p in db.WebPages select p;
foreach (var item in result)
{
if (item.Name == currentPageName)
{
menuHTML += "\n\t<li>" + helper.ActionLink(item.Name, "Details", "Dinner", new { id = item.ID }, new { #class = "selected" }) + "</li>";
}
else
{
menuHTML += "\n\t<li>" + helper.ActionLink(item.Name, "Details", "Dinner", new { id = item.ID }, null) + "</li>";
}
}
menuHTML += "\n</ul>\n";
return menuHTML;
}

I would do two things here: don't bother rendering this yourself: use jQuery. If you Google "jquery menu" you'll find hundreds of links.
Next, put the ordering logic on your app, you don't need the DB to do this as it soaks up cycles and (from what I've read) isn't terribly efficient. This is simple looping logic with a self-referencing join that Linq is perfect for.
Hand this off to jQuery, adn you're good to go without hard-coding HTML in code :)

If you are using Sql server 2005 take a look to Common Table Expression (CTE) (google with CTE hierarchical data). It allows you to create a view displaying the complete hierarchy.
But, how much depth level are you displaying in the menu? Usually you only need to show directy childs and go down in the hierarchy as the user clicks the links. (No recursion needed)

I always use recursive table-valued functions for fetching hierarchical data in SQL server.
See an example here:
blogs.conchango.com/christianwade/archive/2004/11/09/234.aspx
Unfortunately, there is a recursion limit (32 levels maximum) for SQL Server User Defined Functions (UDF) and Stored Procedures.
Note: If you use a table-valued function just drop it in your dbml file and you will be able to access it like any other table.
Another approach is to use the a new recursive queries syntax (in the form of the WITH clause and Common Table Expressions-CTE) introduced in SQL Server 2005.
Take a look here:
www.eggheadcafe.com/articles/sql_server_recursion_with_clause.asp
An approach of mixing CTE with Linq-To-SQL is presented here:
stackoverflow.com/questions/584841/common-table-expression-cte-in-linq-to-sql

Related

MS CRM: Paging cookie bug?

According to this article there is a serious bug in how the paging cookie is managed in Microsoft CRM. In short, the bug appears when trying to retrieve more than 5000 records in a one-to-many relationship. The author states that that since the parent entity does not contain a unique guid, the paging cookie will also not be unqiue, which might lead to that there are plenty of records that will go missing when retriving the next page of records.
Has anyone experienced this bug? How did you get around it?
One way is of course to turn the query around to use the child entity as the primary entity. In this way will every record will be unique. But this is not an optimal solution either since I have to manually change it back in code.
Another way that I have noticed is that when i´m specifying the order on the child entity like this: linkedEntity.Orders.Add(OrderType.Ascending), then the paging cookie will always be empty. This removes the problem that records go missing, but as I understand, this leads to a lot of overhead in SQL.
The best way would of course be if there is any way to make the paging cookie contain both the parent and child record. But I have not found any way yet to do that. Any idea?
I have experienced this.
I worked around it by simply not using the paging cookie, you can still page correctly without it.
For example:
int fetchCount = 3;
int pageNumber = 1;
int recordCount = 0;
QueryExpression pagequery = new QueryExpression("account");
while (true)
{
EntityCollection results = _serviceProxy.RetrieveMultiple(pagequery);
if (results.Entities != null)
{
foreach (Account acct in results.Entities)
{
Console.WriteLine(acct.Id);
}
}
if (results.MoreRecords)
{
pagequery.PageInfo.PageNumber++;
//pagequery.PageInfo.PagingCookie = results.PagingCookie; <-- Don't add the paging cookie here
}
else
{
break;
}
}

Restrict ObjectSet to a related Entity and sort by a dynamic column name

Given a Parent and a valid columnName, I want to find all the related Children, ordered by a dynamic column name. Here's how I thought my code would look:
Parent. // EntityObject
Children. // EntityCollection
Where(c => c.Gender == 'm'). // IEnumerable
OrderBy(columnName, ListSortDirection.Ascending). // -- not available --
Skip(pages * pageSize).Take(pageSize);
IEnumerable.OrderBy(string columnName) doesn't exist. Looking around to accomplish the "sort by dynamic column name", I started with this excellent-looking solution: How do I create an expression tree for run time sorting? , but this operates on an IQueryable
If it did, I assume it would bring the records over the wire to sort and diminish the performance of my pager anyway. So I reordered:
Repository. // Repository
Children. // ObjectSet
Where(c => c.Parent == Parent && c.Gender == 'm'). // ObjectQuery, runtime error
OrderBy(columnName, ListSortDirection.Ascending). // IOrderedQueryable
Skip(pages * pageSize).Take(pageSize);
ObjectSet and ObjectQuery implement OrderBy(string columnName), and this code compiles, but yields the error:
Unable to create a constant value of type 'DataModel.Parent'. Only
primitive types ('such as Int32, String, and Guid') are supported in
this context.
Of course, I can get the parent ID, but the Child.ParentReference is also a non-primitive type.
I can think of a few ways that would result in loading the entire recordset across the wire, but I feel like I must be missing something, because it must not be so hard to get a set of basic directive through to the database using all MS-centric technologies.
edit: pretend I'm http://en.wikipedia.org/wiki/Quiverfull , and need to paginate my children. :)
edit2: clarified my need to query a dynamic column name.
var parents = db.Parents; // Do whatever you need to get your unsorted collection from EF here.
if (sortBy == "Gender")
{
parents = parents.OrderBy(p => p.Gender);
}
else if (sortBy == "FirstName")
{
parents = parents.OrderBy(p => p.FirstName);
}
Now, this obviously isn't sorting on multiple columns, just a single column. And you can add in more logic for sort direction as well.
Edit: took out the crap about PredicateBuilder, I was going the wrong way when I started typing this answer, and forgot to take out the old stuff.
Try replacing your OrderBy
OrderBy("age", ListSortDirection.Ascending).
with
OrderBy(x => x.Age).
also the Where
Where(c => c.Parent == Parent && c.Gender = 'm').
should read
Where(c => c.Parent == Parent && c.Gender == 'm').
So there were a couple issues I was having, both mentioned in the question title.
Sorting by a run-time-selected, or dynamic, column name required some expression building. I used #Slace's popular extension method here.
That required an IQueryable. IQueryable works for me, because every time I was accidentally transforming my query into an enumerable, I was of course bringing all the results back over the wire before paging, which I was trying to avoid. But I still needed a way to get an IQueryable for results with a relationship to an entity I already held.
It was something simple I overlooked, just joining on the Entity.Id worked, and didn't result in redundant joins at the datasource. Not quite as object-oriented as I expected from EF, but it will do.
Repository. // Repository
Children. // ObjectSet
Where(c => c.Parent.Id == Parent.Id). // ObjectQuery, works fine
OrderBy(columnName, ListSortDirection.Ascending). // IOrderedQueryable
Skip(pages * pageSize).Take(pageSize); // Only transfers 1 page

Using CONTAINS from HQL / Criteria API

I'm using NHibernate 2.1.2.4000GA. I'm trying to use SQL Server's CONTAINS function from within HQL and the criteria APIs. This works fine in HQL:
CONTAINS(:value)
However, I need to qualify the table in question. This works fine:
CONTAINS(table.Column, :value)
However, I need to search across all indexed columns in my table. I tried this:
CONTAINS(table.*, :value)
But I get:
NHibernate.Hql.Ast.ANTLR.QuerySyntaxException : Exception of type 'Antlr.Runtime.MissingTokenException' was thrown. near line ... [select table.Id from Entities.Table table where CONTAINS(table.*,:p0) order by table.Id asc]
at NHibernate.Hql.Ast.ANTLR.ErrorCounter.ThrowQueryException()
at NHibernate.Hql.Ast.ANTLR.HqlParseEngine.Parse()
at NHibernate.Hql.Ast.ANTLR.QueryTranslatorImpl.Parse(Boolean isFilter)
at NHibernate.Hql.Ast.ANTLR.QueryTranslatorImpl.DoCompile(IDictionary`2 replacements, Boolean shallow, String collectionRole)
at NHibernate.Hql.Ast.ANTLR.QueryTranslatorImpl.Compile(IDictionary`2 replacements, Boolean shallow)
at NHibernate.Engine.Query.HQLQueryPlan..ctor(String hql, String collectionRole, Boolean shallow, IDictionary`2 enabledFilters, ISessionFactoryImplementor factory)
at NHibernate.Engine.Query.QueryPlanCache.GetHQLQueryPlan(String queryString, Boolean shallow, IDictionary`2 enabledFilters)
at NHibernate.Impl.AbstractSessionImpl.GetHQLQueryPlan(String query, Boolean shallow)
at NHibernate.Impl.AbstractSessionImpl.CreateQuery(String queryString)
So it would seem the HQL parser chokes on the asterisk. I thought of doing this:
CONTAINS(table.Column1, :value) or CONTAINS(table.Column2, :value)
Not only is this inefficient, it can also yields incorrect results depending on the full text predicate in :value.
I tried customizing my dialect as per these instructions, but that doesn't help because you're still left with the same problem: specifying table.* causes the HQL parser to fall over.
I thought of specifying query substitutions:
<property name="query.substitutions">TABLECONTAINS(=CONTAINS(table.*,</property>
And then simply doing:
TABLECONTAINS(:value)
But that does not work. I'm not sure why - judging by the resultant error, the substitution just doesn't take place because "TABLECONTAINS" is still present in the query. Besides, this wouldn't work for all cases because I'd need to know the alias of the table and dynamically substitute it in.
Therefore, I rolled an interception-based approach:
using System;
using NHibernate;
using NHibernate.SqlCommand;
public class ContainsInterceptor : EmptyInterceptor
{
public override SqlString OnPrepareStatement(SqlString sql)
{
var indexOfTableContains = sql.IndexOfCaseInsensitive("TABLECONTAINS(");
if (indexOfTableContains != -1)
{
var sqlPart = sql.ToString();
var aliasIndex = sqlPart.LastIndexOf("Table ", indexOfTableContains, StringComparison.Ordinal);
if (aliasIndex == -1)
{
return sql;
}
aliasIndex += "Table ".Length;
var alias = sqlPart.Substring(aliasIndex, sqlPart.IndexOf(" ", aliasIndex, StringComparison.Ordinal) - aliasIndex);
sql = sql.Replace("TABLECONTAINS(", "CONTAINS(" + alias + ".*,");
}
return base.OnPrepareStatement(sql);
}
}
This works and I will now be able to sleep tonight, but I do feel a sudden desire to attend London's impending papal procession and shout out a confession.
Can anyone suggest a better way to achieve this?
Why not use an ISQLQuery instead?
You can still retrieve entities, see http://nhibernate.info/doc/nh/en/index.html#querysql-creating
I would think that a custom dialect would be the appropriate way to handle this. You can find some guidance in this article. I've used this approach to register SQL Server-specific functions like ISNULL for use in our projects.

Cannot retrieve user object from foreign key relationships using Linq to Entities statement

I'm trying to retrieve a user object from a foreign key reference but each time I try to do so nothing gets returned...
My table is set up like this:
FBUserID long,
UserID uniqueidentifier
so I have my repository try to get the User when it's provided the FBUserID:
public User getUserByFBuid(long uid)
{
User fbUser = null;
IEnumerable<FBuid> fbUids = _myEntitiesDB.FBuidSet.Where(user => user.FBUserID == uid);
fbUser = fbUids.FirstOrDefault().aspnet_Users;
return fbUser;
}
I've checked that the uid (FBUserID) passed in is correct, I've check that the UserID is matched up to the FBUserID. And I've also checked to make sure that fbUids.Count() > 0...
I've returned fbUids.FirstOrDefault().FBUserID and gotten the correct FBUserID, but any time I try to return the aspnet_Users or aspnet_Users.UserName, etc... I don't get anything returned. (I'm guessing it's getting an error for some reason)
I don't have debugging set up properly so that's probably why i'm having so much troubles... but so far all the checking I've done I've been doing return this.Json(/* stuff returned form the repository */) so that I can do an alert when it gets back to the javascript.
Anyone know why I would have troubles retrieving the user object from a foreign key relationship like that?
Or do you have any suggestions as to finding out what's wrong?
For now, with Entity Framework 1, you don't get automatic delayed loading, e.g. if you want to traverse from one entity to the next, you need to either do an .Include("OtherEntity") on your select to include those entities in the query, or you need to explicitly call .Load("OtherEntity") on your EntityContext to load that entity.
This was a design decision by the EF team not to support automagic deferred loading, since they considered it to be too dangerous; they wanted to make it clear and obvious to the user that he is also including / loading a second set of entities.
Due to high popular demand, the upcoming EF v4 (to be released with .NET 4.0 sometime towards the end of 2009) will support the automatic delayed loading - if you wish to use it. You need to explicitly enable it since it's off by default:
context.ContextOptions.DeferredLoadingEnabled = true;
See some articles on that new feature:
A Look at Lazy Loading in EF4
POCO Lazy Loading
Don't know if this is what you are asking but i do a join like so;
var votes = from av in dc.ArticleVotes
join v in dc.Votes on av.voteId equals v.id
where av.articleId == articleId
select v;
Did this help or am I off base?

The application sorts strings differently than the database

I am trying to sort a list of products by their name in a .Net application written in C#, to obtain the same list that I would get from an SQL Server database through an order by:
select * from Products order by ProductName
Unfortunately, the application sorting behaves differently than the database sorting. It is probably related to the collation: the database has an SQL_Latin1_General_CP1_CI_AS collation.
How can I make the application sort these strings exactly like the database does?
Thanks.
UPDATE: I finally obtained a good result by using the code from the comments below, and changing the compare options to Ordinal:
private CompareOptions myOptions = CompareOptions.Ordinal ;
Also, this link contains some very useful information related to SQL collations: http://www.siao2.com/2005/11/08/490305.aspx
Thanks. It does not work yet, but this is probably the right direction.
Here is the code I am trying now:
((List)orderDetails).Sort(new OrderDetailComparer());
where OrderDetailComparer is:
public class OrderDetailComparer : IComparer<OrderDetail>
{
private CompareInfo myComp = CompareInfo.GetCompareInfo("en-US");
private CompareOptions myOptions = CompareOptions.StringSort;
public int Compare(OrderDetail a, OrderDetail b)
{
if (a == b) return 0;
if (a == null) return -1;
if (b == null) return 1;
return myComp.Compare ( a.Product.ProductNameForSorting, b.Product.ProductNameForSorting, myOptions );
}
}
Still no result.
What do you think? How do I get the information from the database, to know what culture I should use?
Thanks.
you'll probably need to use the culture you wish to sort in. Look at the example of the StringComparer:
http://msdn.microsoft.com/en-us/library/system.stringcomparer.currentculture.aspx

Resources