This is post #18 in the Building Neno series. Please click here for a description of the Building Neno project and instructions on how to access the source code for this post.
The application has been hooked up to the database for a while, ever since Spring was configured. Remember this?
That was over a week ago! It's time to import my posts from posts.xml
and perhaps build some screens I can use.
First of all, I need a couple small changes to my Post
class. I have a small problem with dates. I'm in Brisbane, Australia, my hosting server is somewhere in California, and RSS wants publish dates in UTC format. I need to store my publish date as a DateTimeOffset
instead of a DateTime
. New in .NET 3.5, this lets me store the offset for my timezone with the dates, making it easy to display the "local" time (Brisbane time, not California time) as well as UTC time.
In addition, I want to enforce a couple new constraints. (For now I'll do this in the database -- validation rules will come later.)
I'm not sure how DateTimeOffset
is stored in the database. Is 2010-01-01 07:00 +01:00
the same as 2010-01-01 08:00 +02:00
? Would they violate the unique constraint? EDIT: A quick test in SQL Server confirms they do, which is perfect.
The changes to the Post
class look like this...
[DisplayName("Publish Date, Time and Offset")]
public virtual DateTimeOffset PublishDateTimeOffset
{
get { return _publishDateTimeOffset; }
set { _publishDateTimeOffset = value; }
}
[DisplayName("Publish Date")]
public virtual DateTime PublishDate
{
get { return PublishDateTimeOffset.Date; }
private set
{
// Do nothing.
}
}
The do nothing private setter is a trick I use with NHibernate. It lets me calculate a value for it in the class and continue to have NHibernate persist it as a property. There are probably better ways to set that up, but this works for me.
The changes to the table look like this...
[PublishDateTimeOffset] DATETIMEOFFSET NOT NULL,
[PublishDate] DATE NOT NULL,
The DATE
data type lets me set a unique index on just the date and ignore the time.
CREATE UNIQUE INDEX [UX_Neno_Post_PublishDate_Slug]
ON [dbo].[Neno_Post] (
[PublishDate],
[Slug])
I'll reuse the DatabaseController
class which was used earlier to create the schema.
[Administrator]
public ActionResult Import()
{
try
{
string uri = string.Format("{0}://{1}{2}", Request.Url.Scheme, Request.Url.Authority, Url.Content("~/content/import-posts.xml"));
IEnumerable<Post> posts = XDocument.Load(uri)
.Root
.Elements()
.OrderBy(element => (DateTime)element.Element("publishDateUtc"))
.Select(post =>
new Post(DomainModel.User.Current)
{
PublishDateTimeOffset = (DateTimeOffset)post.Element("publishDateUtc"),
IsPublished = true,
Title = (string)post.Element("title"),
Slug = (string)post.Element("slug"),
Text = (string)post.Element("text")
});
Messages.Add(string.Format("{0} posts read and converted.", posts.Count()));
Container.Get<IPostService>().ImportPosts(posts);
Messages.Add(string.Format("{0} posts imported.", posts.Count()));
}
catch (Exception exception)
{
Messages.Add(exception);
}
return RedirectToAction("index", "home");
}
It's not too hard to generate post objects from the XML file. Once I have them though, I want to create all of them or none of them. Calling IPostService.CreatePost
multiple times won't achieve that. This is a unit of work by itself and a candidate for a new service method.
public class PostService : Service, IPostService
{
// ...
public void ImportPosts(IEnumerable<Post> posts)
{
ITransaction transaction = Context.BeginTransaction();
try
{
foreach (Post post in posts)
PostRepository.Create(post);
transaction.Commit();
}
catch
{
transaction.Rollback();
throw;
}
}
// ...
}
Putting it here lets me reuse it in another screen, maybe a text file upload screen -- or even in another UI, like a WPF data loader application. It's also an example of a service layer method using a transaction across multiple repository method calls.
Running through my data I notice a lot of my posts have been truncated. The Text
column in my post table is defined as NVARCHAR (MAX)
, it should be able to hold as much text as I can write. So what went wrong?
The problem is in my NHibernate mappings. NHibernate uses reflection to figure out the type of each property and takes a best guess at the type of the column it's mapped to. In the case of strings, NHibernate will allow up to 8,000 bytes of data (4,000 unicode characters), the maximum for any SQL Server column, unless I tell it specifically that it is NVARCHAR (MAX)
(the new NTEXT
). I do that by specifying a type in my mapping file.
<property name="Text" type="StringClob" />
No more truncation. (By the way, a CLOB is a Character Large OBject.)
My posts are uploaded, but my default screen is still showing them all in a massive dump. I want to cut that down to just one and put in some navigation to get to the older posts. To handle the navigation, I'm going to pass a new view model to my post screens.
public abstract class OlderNewer
{
protected OlderNewer(IEnumerable<DateTimeOffset> publishDateTimeOffsets)
{
publishDateTimeOffsets = publishDateTimeOffsets.OrderBy(pdto => pdto);
if (publishDateTimeOffsets.Count() > 0)
{
Older = Container.Get<IPostService>().GetPosts()
.Where(post => post.PublishDateTimeOffset < publishDateTimeOffsets.First())
.OrderByDescending(post => post.PublishDateTimeOffset)
.FirstOrDefault();
Newer = Container.Get<IPostService>().GetPosts()
.Where(post => post.PublishDateTimeOffset > publishDateTimeOffsets.Last())
.OrderBy(post => post.PublishDateTimeOffset)
.FirstOrDefault();
}
}
public Post Older { get; private set; }
public Post Newer { get; private set; }
}
public abstract class OlderNewer<T> : OlderNewer
{
protected OlderNewer(T payload, IEnumerable<DateTimeOffset> publishDateTimeOffsets)
: base(publishDateTimeOffsets)
{
Payload = payload;
}
public T Payload { get; private set; }
}
public class OlderNewerPost : OlderNewer<Post>
{
public OlderNewerPost(Post post)
: base(post, post != null ? new[] { post.PublishDateTimeOffset } : new DateTimeOffset[0])
{
}
}
// ... and so on
This will give each screen access to the next oldest and the next newest posts.
I want a decent number of archive screens. In an old Movable Type blog, my archive screens were a function of URL reduction, which is something I'd like to do again. For example:
I like this approach because it can help me with bad links also. If a post isn't found, I can fall back to the daily archive (perhaps the slug changed). If there's nothing for a particular day, I can fall back to the monthly archive (perhaps the publish date changed). If there's nothing for the month, I can fall back to the yearly archive, and so on.
That's enough for today. I still don't have post new and edit screens, so I'll have to upload this post through /database/import.
There are 0 comments.
Newer
Post Screens
Newer
Post Screens
browse with Pivot
Codility Nitrogenium Challenge
OS X Lock
HACT '13
Codility Challenges
Priority Queue
Architecture (13)
ASP.NET (2)
ASP.NET MVC (13)
Brisbane Flood (1)
Building Neno (38)
C# (4)
Challenges (3)
Collections (1)
Communicator (1)
Concurrency Control (2)
Configuration (1)
CSS (5)
DataAnnotations (2)
Database (1)
DotNetOpenAuth (2)
Entity Framework (1)
FluentNHibernate (2)
Inversion of Control (5)
JavaScript (1)
jQuery (4)
Kata (2)
Linq (7)
Markdown (4)
Mercurial (5)
NHibernate (20)
Ninject (2)
OpenID (3)
OS X (1)
Pivot (6)
PowerShell (8)
Prettify (2)
RSS (1)
Spring (3)
SQL Server (5)
T-SQL (2)
Validation (2)
Vim (1)
Visual Studio (2)
Windows Forms (3)
Windows Service (1)
Comments
Leave a Comment
Please register or login to leave a comment.