Showing posts with label nhibernate. Show all posts
Showing posts with label nhibernate. Show all posts

Saturday, April 24, 2010

NHWebConsole 0.1 released

I just released the embeddable NHibernate console for web applications I wrote some months ago. You can get the binary here, it also includes a sample application so you can play with it and see how it's configured. The only requirements for NHWebConsole are .NET 3.5 and NHibernate 2.1.2.

It hasn't changed much since I first wrote about it. I fixed a couple of bugs and added HQL Intellisense thanks to Fatica Labs' wonderful HQL Editor. It's not overly pretty but it mostly works. Here's a screenshot:

Source code and documentation is on github.

Let me know if you use it and/or if you find any bugs!

Saturday, November 21, 2009

NHibernate web console

When developing web apps, I sometimes need to poke into the app's database, or try out some query. When it's a SQL Server database, I have Management Studio. But what if it's SQLite? Or Firebird? Even when it's SQL Server, most of my apps use NHibernate and I'm really spoiled by HQL. And if it's a local embedded database, you have to browse and open the db file. Boring. I already have the app URL, that should be enough.

So I built a NHibernate web console. It's kind of like NHibernate Query Analyzer, but more web-oriented. Here's a screencast showing its features:

A couple of features that are not evident from the screencast:

Yeah, I know it's butt-ugly and a little rough around the edges, but still it's pretty usable (at least for me).

DISCLAIMER / WARNING: it's up to you to secure this if you put it in a production website! Also, there is no query cancellation, so if you issue a 1M-results query you will effectively kill your app!

Code is here.

UPDATE: just added RSS support for query results.

Monday, October 12, 2009

Untangling the mess: Solr, SolrNet, NHibernate, Lucene

I've recently received several questions about the relationship between Solr, SolrNet, NHibernate, Lucene, Lucene.Net, etc, how they fit together, how they should be used, what features does each provide. Here's an attempt at elucidating the topic:

Let's start from the bottom up:

  • RDBMS: every programmer knows what these are. Oracle, SQL Server, MySQL, etc. Everyone uses them, to the point that it's often used as a Golden Hammer. RDBMS can be stand-alone programs (client-server architecture) or embedded (running within your application).
  • Lucene was written to do full-text indexing and searching. The most known example of full-text searching is Google. You throw words at it and it returns a ranked set of documents that match those words.
    In terms of data structures, Lucene at its core implements an inverted index, while relational databases use B-tree variants. Fundamentally different beasts.
    Lucene is a Java library, this means that it's not a stand-alone application but instead embedded in your program.
  • Full-text functions in relational databases: nowadays almost all major RDBMS offer some full-text capabilities: MySQL, SQL Server, Oracle, etc. As far as I know, they are all behind Lucene in terms of performance and features. They can be easier to use at first, but they're proprietary. If you ever need some advanced feature, switching to Lucene could be a PITA.
  • Lucene.Net is a port of Java Lucene to the .Net platform. Nothing more, nothing less. It aims to be fully API compatible so all docs on Java Lucene can be applied to Lucene.Net with minimal translation effort. Index format is also the same, so indices created with Java Lucene can be used by Lucene.Net and vice versa.
  • NHibernate is a port of Java Hibernate to the .Net platform. It's an ORM (object-relational mapper), which basically means that it talks to relational databases and maps your query results as objects for easier consumption in object-oriented languages.
  • NHibernate.Search is a NHibernate contrib project that integrates NHibernate with Lucene.Net. It's a port of the Java Hibernate Search project. It keeps a Lucene index in sync with a relational database and hides some of the complexity of raw Lucene, making it easier to index and query.
    This article explains its basic usage.
  • Solr is a search server. It's a stand-alone Java application that uses Lucene to provide full-text indexing and searching through a XML/HTTP interface. This means that it can be used from any platform/language. It can be embedded in your own Java programs, but it's not its primary design purpose.
    While very flexible, it's easier to use than raw Lucene and provides features commonly used in search applications, like faceted search and hit highlighting. It also handles caching, replication, sharding, and has a nice web admin interface.
    This article is a very good tour of Solr's basic features.
  • SolrNet is a library to talk to a Solr instance from a .Net application. It provides an object-oriented interface to Solr's operations. It also acts as an object-Solr mapper: query results are mapped to POCOs.
    The latest version also includes Solr-NHibernate integration. This is similar to NHibernate.Search: it keeps a Solr index in sync with a relational database and lets you query Solr from the NHibernate interface.
    Unlike NHibernate and NHibernate.Search, which can respectively create a DB schema and a Lucene index, SolrNet can't automatically create the Solr schema. Solr does not have this capability yet. You have to manually configure Solr and set up its schema.


In case this wasn't totally clear, here's a diagram depicting a possible NHibernate-SolrNet architecture:

Diagram made with gliffy!

Saturday, May 23, 2009

Rails-like finders for NHibernate with C# 4.0

Now that VS2010 Beta 1 is out, I figured I'd take another look and try to actually do something with it. So I spiked a little proof of concept for what I mentioned about six months ago: Rails-like finders for NHibernate. In RoR's ActiveRecord you can write:

Person.find_by_name("pepe")

without really having a find_by_name method, thanks to Ruby's method_missing. Now, with a few lines of code, you can write this in C# 4.0, thanks to DynamicObject:

ISession session = ...
Person person = session.AsDynamic().GetPersonByName("pepe");

which behind the scenes will parse the method name "GetPersonByName" (using a Pascal-Casing convention) into a DetachedCriteria and execute it.

Note that I wrote "Person person = ..." instead of "var person = ...", that's because the result of GetPersonByName() is a dynamic so it has to be cast to its proper type to jump back to the strongly-typed world.

As I said before, this is only a proof of concept. It doesn't support finders that return collections, or operators (as in FindPersonByNameAndCity("Pepe", "Boulogne-sur-Mer")), etc. It would certainly be interesting to really implement this, but I wonder if people would use it, with IQueryable being more strongly-typed, way more flexible, and with better language integration.

Anyway, here's the code that makes this possible. Pay no attention to Castle ActiveRecord, I just used it because... well, geographical convenience, really.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Text.RegularExpressions;
using Castle.ActiveRecord;
using Castle.ActiveRecord.Framework.Config;
using log4net.Config;
using NHibernate;
using NHibernate.Criterion;
using NUnit.Framework;

namespace NHDynamicTests {
    [TestFixture]
    public class DynamicTests {
        [Test]
        public void DynamicGetPersonByName() {
            using (ISession s = ActiveRecordMediator.GetSessionFactoryHolder().GetSessionFactory(typeof(object)).OpenSession()) {
                dynamic ds = s.AsDynamic();
                Person person = ds.GetPersonByName("pepe");
            }
        }

        [ActiveRecord]
        public class Person {
            [PrimaryKey]
            public int Id { get; set; }

            [Property]
            public string Name { get; set; }
        }

        [TestFixtureSetUp]
        public void FixtureSetup() {
            BasicConfigurator.Configure();
            var arConfig = new InPlaceConfigurationSource();
            var properties = new Dictionary<string, string> {
                {"connection.driver_class", "NHibernate.Driver.SQLite20Driver"},
                {"dialect", "NHibernate.Dialect.SQLiteDialect"},
                {"connection.provider", "NHibernate.Connection.DriverConnectionProvider"},
                {"connection.connection_string", "Data Source=test.db;Version=3;New=True;"},
            };

            arConfig.Add(typeof(ActiveRecordBase), properties);
            ActiveRecordStarter.ResetInitializationFlag();
            var arTypes = GetType().GetNestedTypes()
                .Where(t => t.GetCustomAttributes(typeof(ActiveRecordAttribute), true).Length > 0)
                .ToArray();
            ActiveRecordStarter.Initialize(arConfig, arTypes);
            ActiveRecordStarter.CreateSchema();
        }
    }

    public static class ISessionExtensions {
        public static dynamic AsDynamic(this ISession session) {
            return new DynamicSession(session);
        }
    }

    public class DynamicSession : DynamicObject {
        private readonly ISession session;

        public DynamicSession(ISession session) {
            this.session = session;
        }

        public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) {
            result = null;
            if (!binder.Name.StartsWith("Get"))
                return false;
            var tokens = Regex.Replace(binder.Name, "([A-Z])", " $1").Split(' ').Skip(1).ToArray();
            if (tokens.Length < 4 || args.Length < 1)
                return false; // some parameter is missing
            var typeName = tokens[1];
            var type = session.SessionFactory.GetAllClassMetadata()
                .Cast<DictionaryEntry>()
                .Select(e => e.Key).Cast<Type>()
                .FirstOrDefault(t => t.Name == typeName);
            if (type == null)
                throw new ApplicationException(string.Format("Type '{0}' is not mapped in NHibernate", typeName));
            var fieldName = tokens[3];
            var criteria = DetachedCriteria.For(type).Add(Restrictions.Eq(fieldName, args[0]));
            result = criteria.GetExecutableCriteria(session).UniqueResult();
            return true;
        }
    }
}

BTW: I just can't stand WPF's font rendering, it's so blurry! I don't care if it uses Ideal Width or Compatible Width or whatever you want to call it, it just looks very bad. Please vote for this issue so they fix it as soon as possible!

Tuesday, March 25, 2008

Strongly typed NHibernate Criteria with C# 3

I, like many others, hate string literals. Of course, I mean string literals that have some meaning to the code itself, not the "hello, world" kind of strings. They are not type-safe, not easy to refactor, etc. The drawbacks have been mentioned a lot.

In particular, it always bothered me that NHibernate suffers from this problem, specially in the Criteria API. Well, now with C# 3 AST manipulation we can easily fix this. I wrote a simple set of helper classes and extension methods that provide an Expression parameter wherever there was a string parameter describing a property name. Let's see an example:

Instead of writing this:

IList cats = sess.CreateCriteria(typeof(Cat))
    .Add( Expression.Like("Name", "F%")
    .AddOrder( Order.Asc("Name") )
    .AddOrder( Order.Desc("Age") )
    .SetMaxResults(50)
    .List();

You can write this:

IList cats = sess.CreateCriteria(typeof(Cat))
    .Add( ExpressionEx.Like((Cat c) => c.Name, "F%")
    .AddOrder( OrderEx.Asc((Cat c) => c.Name) )
    .AddOrder( OrderEx.Desc((Cat c) => c.Age) )
    .SetMaxResults(50)
    .List();

which is a bit longer, but refactorable.

Code is here (NHibernate 1.2)

UPDATE 2/21/2009: Dan Miser has kindly submitted a patch to port these extensions to NHibernate 2.0.1GA. Code is here. Thanks Dan!

UPDATE 3/28/2010: Two years have passed now since I originally published this and several similar solutions have popped up. In particular, I recommend NH Lambda extensions for NHibernate 2.x which seems to be more complete, less verbose, and better maintained than my own solution. NHibernate 3 will have a new official API similar to this, named QueryOver. If you're stuck with NHibernate 1.2 the only solution available is the one presented in this article.

Related projects: