Best practice for querying Umbraco nodes - umbraco

I was a beginner at Umbraco CMS, and I noticed that every time I need to query for node, I am relying on constant id's.
For example:
public static class Constants
{
public static class Pages
{
public static class System
{
public const int Root = 1059;
public const int Home = 1092;
public const int Pressemeddelelse = 1143;
}
public const int GratisKontoComplete = 1181;
public const int TilmeldNyhedsbrevComplete = 1182;
}
}
The above code helps me when I need to get the node, like #Umbraco.NiceUrl(Constants.Pages.System.Root);
Now my question is:
Is there any other way how to achieve this? What if the content editor deleted the node and created? The node if will changed for sure right.
Do you want me to query it via NodeAlias? but I dont know how, I dont want to use uQuery because I am using the latest Umbraco Version 7.4.3 using MVC.

First of all you should not to use the ids for retrieving nodes.
there are many ways but for me the most easy and reliable way is to use linq query.
For example there are three nodes HOME, BLOG and COMMENT .Now home is parent node and BLOG and Comment are child nodes of HOME node. Now to retrieve the parent node use the following node.
Umbraco.TypedContentAtRoot().First();
or
Model.Content.AncestorOrSelf(1).First()
where model is the current node .
You can also use this code too
var rootNode = new Node(-1);
All the aboce codes will help you to get the root node i.e HOME node , now after getting the root node you can now use SQL queries to get all the children nodes of HOME node and so on . FOr example if I want to retrieve BLOG node then my code will be
'var Blog= rootNode.Children.First(x => x.DocumentTypeAlias == "Blog");'
where "DocumentTypeAlias" is the alias name of BLOG node.
Hopefully this will help you
Cheers

Related

Can I delete a single child entity without loading the entire collection?

I have 2 classes, like the below.
They can have very large collections - a Website may have 2,000+ WebsitePages and vice-versa.
class WebsitePage
{
public int ID {get;set;}
public string Title {get;set;}
public List<Website> Websites {get;set;}
}
class Website
{
public int ID {get;set;}
public string Title {get;set;}
public List<WebsitePage> WebsitePages {get;set;}
}
I am having trouble removing a WebsitePage from a Website. Particularly when removing a WebsitePage from mutliple Websites.
For example, I might have code like this:
var pageToRemove = db.WebsitePages.FirstOrDefault();
var websites = db.Websites.Include(i => i.WebsitePages).ToList();
foreach(var website in websites)
{
website.WebsitePages.Remove(pageToRemove)
}
If each website Include() 2k pages, you can imagine it takes ages to load that second line.
But if I don't Include() the WebsitePages when fetching the Websites, there is no child collection loaded for me to delete from.
I have tried to just Include() the pages that I need to delete, but of course when saving that gives me an empty collection.
Is there a recommended or better way to approach this?
I am working with an existing MVC site and I would rather not have to create an entity class for the join table unless absolutely necessary.
No, you can't... normally.
A many-to-many relationship (with a hidden junction table) can only be affected by adding/removing items in the nested collections. And for this the collections must be loaded.
But there are some options.
Option 1.
Delete data from the junction table by raw SQL. Basically this looks like
context.Database.ExecuteSqlCommand(
"DELETE FROM WebsiteWebsitePage WHERE WebsiteID = x AND WebsitePageID = y"));
(not using parameters).
Option 2.
Include the junction into the class model, i.e. map the junction table to a class WebsiteWebsitePage. Both Website and WebsitePage will now have
public ICollection<WebsiteWebsitePage> WebsiteWebsitePages { get; set; }
and WebsiteWebsitePage will have reference properties to both Website and WebsitePage. Now you can manipulate the junctions directly through the class model.
I consider this the best option, because everything happens the standard way of working with entities with validations and tracking and all. Also, chances are that sooner or later you will need an explicit junction class because you're going to want to add more data to it.
Option 3.
The box of tricks.
I tried to do this by removing a stub entity from the collection. In your case: create a WebsitePage object with a valid primary key value and remove it from Website.WebsitePages without loading the collection. But EF doesn't notice the change because it isn't tracking Website.WebsitePages, and the item is not in the collection to begin with.
But this made me realize I had to make EF track a Website.WebsitePages collection with 1 item in it and then remove that item. I got this working by first building the Website item and then attaching it to a new context. I'll show the code I used (a standard Product - Category model) to prevent typos.
Product prd;
// Step 1: build an object with 1 item in its collection
Category cat = new Category { Id = 3 }; // Stub entity
using(var db = new ProdCatContext())
{
db.Configuration.LazyLoadingEnabled = false;
prd = db.Products.First();
prd.Categories.Add(cat);
}
// Step 2: attach to a new context and remove the category.
using(var db = new ProdCatContext())
{
db.Configuration.LazyLoadingEnabled = false;
db.Products.Attach(prd);
prd.Categories.Remove(cat);
db.SaveChanges(); // Deletes the junction record.
}
Lazy loading is disabled, otherwise the Categories would still be loaded when prd.Categories is addressed.
My interpretation of what happens here is: In the second step, EF not only starts tracking the product when you attach it, but also its associations, because it 'knows' you can't load these associations yourself in a many to many relationship. It doesn't do this, however, when you add the category in the first step.

With MvcSiteMapProvider Is it possible to programmatically register lots of pages between index ranges?

I'm looking at MVCSiteMapProvider but I can't find anything in documentation that would allow me to register lots of urls by index. I have the following
http://example.com/story/1
...
...
http://example.com/story/7000000
I'd like to be able to use the library to automatically serve these in lots of different files. I've read through all the documentation but can't find anything. It seems really fully featured though so I thought I would ask before rolling my own solution.
You can use a dynamic node provider or implement ISiteMapNodeProvider to programatically supply your own data (including custom ids) from any source.
Dynamic node providers can be added without using an external dependency injection container, but you need to add a "template" node either in XML or using .NET attributes to attach the provider to (see the above link).
public class StoryDynamicNodeProvider : DynamicNodeProviderBase
{
public override IEnumerable<DynamicNode> GetDynamicNodeCollection(ISiteMapNode node)
{
// Entities would be your entity framework context class
// or repository.
using (var entities = new Entities())
{
// Create a node for each blog post
foreach (var story in entities.Stories)
{
DynamicNode dynamicNode = new DynamicNode();
dynamicNode.Title = story.Title;
// The key of the node that this node will be the child of.
// This works best if you explicitly set the key property/attribute
// of the parent node.
dynamicNode.ParentKey = "Home";
dynamicNode.Key = "Story_" + story.Id;
dynamicNode.Controller = "Story";
dynamicNode.Action = "Details";
// Add the "id" (or any other custom route values)
dynamicNode.RouteValues.Add("id", story.Id);
yield return dynamicNode;
}
}
}
}
Using ISiteMapNodeProvider you can build the entire SiteMap structure including the root node, but currently it requires using an external DI container to inject a custom implementation.
There is an example here of how you could implement ISiteMapNodeProvider yourself. Here is an example of injecting a custom implementation using SimpleInjector.
Do note that there is currently a limitation in the 10s of thousands for the total number of nodes on a server because they are cached in memory, so if you have that many nodes using preservedRouteParameters is a better choice. However, it has a limitation that the individual URLs can only appear in the SiteMapPath, but not in the Menu, SiteMap, or XML Sitemap for search engines.
I think this will be the answer you are looking for:
Add the below to the node in your sitemap.
preservedRouteParameters="id"
There is more documentation here, How to Config MVCSiteMap to realize the parameters?

How to make a "deep" transformation in Grails withCriteria contains projection?

I am using Grails 2.2.4 to build a web application, and now I am having a complex problem with Grails withCriteria operation. I have 3 classes as below:
class Person {
int id
String name
Set<Car> cars = [] // One-to-many relationship
Company currentCompany // One-to-one relationship
}
class Car {
int id
String manufacturer
String brand
double price
Person owner
}
class Company {
int id
String name
Set<Person> employees = []
}
And now I want to query data from Person as root class along with associated data like this:
List result = Person.withCriteria {
projections {
property('id')
property('name')
createAlias('cars', 'c', CriteriaSpecification.LEFT_JOIN)
property('c.brand')
createAlias('currentCompany', 'com', CriteriaSpecification.LEFT_JOIN)
property('com.name')
}
lt('id', 10L)
resultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY)
}
And the problem is, I don't know how to transform deeply the result data to a List of persons to make sure every single element contains its data as the class structure. I tried many methods like
resultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY)
resultTransformer(CriteriaSpecification.ALIAS_TO_ENTITY_MAP)
resultTransformer(CriteriaSpecification.aliasToBean(Person.class))
but nothing worked as I expected.
Does Grails 2.2.4 support this? If yes so what is the correct syntax?
Thank you so much.
Actually, after researching many articles, Grails documentation and even its source code, I think the best way to do this is implementing a custom transformer for my own purpose. The most difficult thing here is how to transform data to association objects and gather them to collection inside the root entity. And I have created one here:
http://www.mycodestock.com/code/10333/
Hope it helps you guys who may need something like mine.

ASP.NET MVC set value of a globally accessible variable permanently

I am writing an application in ASP.NET MVC with three type of users: Student, Staff and Admin.
Students must be able to select and apply for Programs that are set by the Admin. Programs are separated in Terms : Summer, Autumn and Spring which is saved in a database field in the Programs Table called "term".
I want for the admin to be able to set the term, so when users(students and staff) log in, they can only view programs available only for that term. The thing is i cannot save it in a database as my database has Tables such as StaffProfile (retrieves the usernames and passwords of the Admin and the Staff) and Programs(contains information for each program) and a new table only for the term would not be an ideal solution.
I want to save the term variable somewhere, that can be accessed globally and not be destroyed when the application ends.
Maybe this has a simple solution i cannot see.I have considered ApplicationState but i am not sure if this is the way to go.
Any ideas are welcome.
According to my understanding of your question. You can store global variable in web.config or global.asax file. you can also use session. you have another option by static declaring static class. like
public static class GlobalVariables
{
// readonly variable
public static string Foo
{
get
{
return "foo";
}
}
// read-write variable
public static string Bar
{
get
{
return HttpContext.Current.Application["Bar"] as string;
}
set
{
HttpContext.Current.Application["Bar"] = value;
}
}
}
please correct me If I am wrong.

Twitter-like model with RavenDB

I am playing around a bit with Raven and trying to figure out what the best way would be to model my objects for a twitter-like scenario. So far I have come up with a few options but not sure which one is the best.
public class User{
public string Id{get;set;}
public List<string> Following{get;set;}
public List<string> Followers{get;set;}
}
The User object is simple and straightforward, just an ID and a list of IDs for people I follow and people following me. The feed setup is where I need help, getting all posts from users that I am following.
Option 1 - The easy route
This searches for all posts of people I follow just based on their UserId.
public class Post{
public string UserId{get;set;}
public string Content{get;set;}
}
Index
public class Posts : AbstractIndexCreationTask<Post>{
public Posts(){
Map = results => from r in results
select new{
r.UserId
};
}
}
Querying
var posts = session.Query<Post,Posts>().Where(c=>c.UserId.In(peopleImFollowing));
This is the obvious route but it smells bad. The query results in a bunch of OR statements sent to Lucene. There is an upper limit of somewhere around 1024 that Raven will handle, so any one user couldn't follow more than 1000 people.
Option 2 - One post for each follower
public class Post{
public string UserId{get;set;}
public string RecipientId{get;set;}
public string Content{get;set;}
}
Adding a new post
foreach(string followerId in me.Followers){
session.Store(new Post{
UserId = me.UserId,
RecipientId = followerId,
Content = "foobar" });
}
This is simple to follow and easy to query but it seems like there would be way too many documents created... perhaps that doesn't matter though?
Option 3 - List of recipients
So far I like this the best.
public class Post{
public string UserId{get;set;}
public List<string> Recipients{get;set;}
public string Content{get;set;}
}
Index
public class Posts : AbstractIndexCreationTask<Post>{
public Posts(){
Map = results => from r in results
select new{
UserId = r.UserId,
Recipient = r.Recipients
}
}
}
Adding new post
session.Store(new Post{
UserId = me.Id,
Recipients = me.Followers,
Content = "foobar"
});
Querying
var posts = session.Query<Post,Posts>().Where(c=>c.Recipient == me.Id);
This seems like the best way but I have never worked with Lucene before. Would it be a problem for the index if someone has 10,000 followers? What if we want to post a message that goes to every single user? Perhaps there is another approach?
From my perspective, only option 1 really works and you will probably want to tune how RavenDB talks to lucene if you want to have support for following more than 1024 users.
Option 2 and Option 3 don't take into account that after you have followed new users you want older tweets of them to show up in your timeline. Likewise, you also want these tweets disappear from your timeline after you unfollowed them. If you want to implement this with one of those two approaches, you would need to duplicate all of their tweets on 'follow' operation and also delete them on 'unfollow'. This would make following/unfollowing a very expensive operation and it could also fail (what if the server that contains parts of the tweets isn't available the moment you're doing this?).
Option 2 also has the immensive disadvantage that it would produce literally tons of duplicate data. Think about famous users with millions of followers and thousands of posts. Then multiply this with thousands of famous users... not even twitter can handle such amounts of data.
Option 3 also has the problem that queries to the index get slow because every lucene document would have this 'recipient' field with perhaps millions of values. And you have trillions of documents... no, I'm not a lucene expert, but I don't think that works fast enough to display the timeline (even ignoring that you are not the only concurrent user that wants to display the timeline).
As I said above, I think that only option 1 works. Maybe someone else has a better approach. Good question btw.

Resources