I think we’re mostly finished investigating ObjectStore for now. In this article I’ll try to finish up on the apsects of using the Object store in a real life solution that is a basic Page comments. In my previous article concerning ObjectStore I have described a way of storing and retrieving an object from the Store, which is fine and dandy if we know exactly the object’s OD, for example if we reference it from a page. But what good is a store like that if we cannot search it for content? The problem we were trying to solve using ObjectStore was storing comments for ANY Episerver page without having to do anything to the page type. We might need that for the upcoming project so the discovery may prove useful since this is a really neat way of storing objects.
So the class that we are going to store needs to persists the following values:
namespace Cognifide.EPiServerTest.ObjectStore
{
[Serializable, XmlInclude(typeof (PageCommentDO))]
public class PageCommentDO : IItem, IPageComment
{
private object id;
private string name;
[Indexed(true)]
private int pageId;
private string title;
private string content;
[Indexed(true)]
private DateTime submitDate;
[Indexed(true)]
private bool moderated;
[Indexed(true)]
private bool published = true;
[Indexed(true)]
private bool reported;
}
}
You might have noticed that contrary to what I did before this class has some of its fields tagged with [Indexed(true)] attribute. This is required by the store object if we ever want to query the store for the class using those fields as a filtering criteria. The page also implements the required IItem interface (although the properties required by the interface are not shown in the excerpt) as well as our internal IPageComment which is added there for the purpose of easy switching of implementations. We intend to implement it on both ObjectStore and using NHibernate to make sure we get the best performance possible out of our solution. This interface is to allow us to easily switch between the 2 implementations.
Back to the ObjectStore
First we need to introduce a few new classes EPiServer uses for querying the Store. The namespace that scopes the querying consists of the following entities:
namespace EPiServer.BaseLibrary.Search
{
public class BetweenExpression : IExpression;
public class EqualExpression : IExpression;
public sealed class Expression;
public interface IExpression;
public class Order;
public class Query : IEnumerable;
}
The Query class is at the center of any search in the Store, let’s start with the samples right away. The saving is almost identical to the sample that I’ve shown in my previous article:
public void Save()
{
ISession session = null;
try
{
// check if there is a schema for the type, if not create it.
if (Context.Repository.SchemaForType(GetType()) == null)
{
TypeSchemaBuilder.RegisterSchemaAndType("CognifidePageCommentsStorage", GetType());
}
// get a new session from the current context.
session = Context.Repository.CreateSession();
// wrap it in a transaction
session.BeginTransaction();
// make sure the id has been defined.
if (Id.Equals(Guid.Empty))
{
Id = Guid.NewGuid();
}
//persist
session.Save(this);
session.CommitTransaction();
}
catch (ElektroPostException exception)
{
// ... rollback the transaction and do some reporting
if (session != null)
{
session.RollbackTransaction();
}
throw;
}
finally
{
if (session != null)
{
session.Close();
}
}
}
Really there is not much meat there. The interesting part comes in the object retrieval. Even though the Search namespace does not look like it offers much, you can still build a fairly fophisticated filter with it. following code retrieves comments for a specific PageLinkID. The comments can be filtered by date and since the call is used to retrieve the page for viewing, we probably only want the ones that are public (meaning they have not been removed by the comment moderator).
public static List<PageCommentDO> GetCommentsForPage(int pageId,
DateTime beforeDate, DateTime afterDate, bool includeOnlyPublished)
{
ISession session = null;
try
{
// make sure there is a schema to read from, otherwise there is no point
if (Context.Repository.SchemaForType( typeof (PageCommentDO)) == null)
{
return null;
}
//acquire a session
session = Context.Repository.CreateSession();
//create the query
Query query = new Query(typeof (PageCommentDO));
// we only want comments for a singler page
query.Add(Expression.Equal("pageId", pageId));
// should we filter to only wshow comments from some set time period?
// the following demonstrate how to request a value from within a range
if ((beforeDate != DateTime.MinValue) ||
((afterDate != DateTime.MaxValue) && (afterDate != DateTime.MinValue)))
{
query.Add(
Expression.Between("submitDate",
(beforeDate != DateTime.MinValue) ? beforeDate : new DateTime(0x76c, 1, 1),
((afterDate != DateTime.MinValue) && (afterDate != DateTime.MaxValue))
? afterDate
: new DateTime(0x834, 1, 1)));
}
// probably for a displayed page we will only want to show published comments
if (includeOnlyPublished)
{
query.Add(Expression.Equal("published", true));
}
// order the comments by date/time so that we get the most recent first
query.AddOrder(new Order("submitDate", true));
IList list = session.ExecuteQueryObject(query);
List<PageCommentDO> result = new List<PageCommentDO>(list.Count);
foreach (object item in list)
{
result.Add((PageCommentDO) item);
}
return result;
}
catch (ElektroPostException ex)
{
// ... do some reporting
throw;
}
finally
{
if (session != null)
{
session.Close();
}
}
}
You nahe to agree, this really IS neat. All you need to do now to place the comments on a page is a few lines of code to bind the data from the Store with a repeated of a kind, and a set of edit boxes with a submit button :)
This is basically how much code the submission and retrieval the comments comment take:
// Load the comments to a repeater
protected void Page_Load(object sender, EventArgs e)
{
List<PageCommentDO> comments =
PageCommentDO.GetCommentsForPage(CurrentPage.PageLink.ID, DateTime.MinValue, DateTime.MaxValue);
CurrentComments.DataSource = comments;
CurrentComments.DataBind();
}
// Comment submission
protected void SubmitComment(object sender, System.EventArgs e)
{
PageCommentDO newComment = new
PageCommentDO(Guid.Empty,
string.Format("CommentForPage{0}", CurrentPage.PageLink.ID),
CurrentPage.PageLink.ID,
SubjectTextBox.Text, ContentTextBox.Text, DateTime.Now, false, true, false);
newComment.Save();
}
// the data binding for the labels
protected void CurrentComments_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
PageCommentDO comment = (e.Item.DataItem as PageCommentDO);
Label commentSubjectLabel = (Label)e.Item.FindControl("CommentSubjectLabel");
if (comment != null)
{
commentSubjectLabel.Text = comment.Title;
}
Label commentContentLabel = (Label)e.Item.FindControl("CommentContentLabel");
if (comment != null)
{
commentContentLabel.Text = comment.Content;
}
}
Short of having a control pre=built for you I cannot think of it being able to make it easier.
I will look into packing it into a nice control that we would be looking into contributing some time in the future and of course if there is actually a demand for it.