How to do it: Using the Reflection provider to create a Data Service using the NHIbernate framework

Description and code samples
Table of Contents
Operations
File with implementation of a DataContext type (NHibernateDataContext.cs)

Contains an abstract class for implementation of a datacontext type:

It implements the IUpdatable interface in order to make possible CUD operations by the client. This is achieved by maitaining a collection of entity objects to be created, updated and removed. These collections are processed through the NHibernate session when changes are submitted

How to do it
namespace Web.DataServices
{
    public abstract class NHibernateDataContext : IUpdatable
    {
        public ISession Session { set; get; }

        private List<object> entitiesToBeUpdated = new List<object>();
        private List<object> entitiesToBeRemoved = new List<object>();

        public NHibernateDataContext(ISession session)
        {
            this.Session = session;
        }

        public object CreateResource(string containerName, string fullTypeName)
        {
            Type t = Type.GetType(fullTypeName, true);
            object resource = Activator.CreateInstance(t);

            entitiesToBeUpdated.Add(resource);

            return resource;
        }

        public object GetResource(IQueryable query, string fullTypeName)
        {
            object resource = null;

            foreach (object item in query)
            {
                if (resource != null)
                {
                    throw new DataServiceException(-1,"WCFDSAPI400-00","The query must return a single resource / A query deve retornar apenas um recurso",null,null);
                }

                resource = item;
            }

            if (resource == null)
            {
                throw new DataServiceException(404, "WCFDSAPI404-00", "The resource can not be found / O recurso não foi encontrado", null, null);
            }

            if (fullTypeName != null && resource.GetType().FullName != fullTypeName)
            {
                throw new DataServiceException(404, "WCFDSAPI404-00", "Unexpected type for resource / Tipo de recurso encontrado inesperado (" + resource.GetType().FullName + " != " + fullTypeName ?? "" + ")", null, null);
            }

            return resource;
        }

        public object ResetResource(object resource)
        {
            return resource;
        }

        public void SetValue(object targetResource, string propertyName, object propertyValue)
        {
            PropertyInfo propertyInfo = targetResource.GetType().GetProperty(propertyName);
            propertyInfo.SetValue(targetResource, propertyValue, null);

            if (!entitiesToBeUpdated.Contains(targetResource))
            {
                entitiesToBeUpdated.Add(targetResource);
            }
        }

        public object GetValue(object targetResource, string propertyName)
        {
            PropertyInfo propertyInfo = targetResource.GetType().GetProperty(propertyName);
            var obj = propertyInfo.GetValue(targetResource, null);

            return obj;
        }

        public void SetReference(object targetResource, string propertyName, object propertyValue)
        {
            ((IUpdatable)this).SetValue(targetResource, propertyName, propertyValue);
        }

        public void AddReferenceToCollection(object targetResource, string propertyName, object resourceToBeAdded)
        {
            PropertyInfo propertyInfo = targetResource.GetType().GetProperty(propertyName);

            if (propertyInfo == null)
            {
                throw new DataServiceException(405, "WCFDSAPI405-01", "Can't find the specified property / A propriedade parameterizada (" + propertyName + ")", null, null);
            }

            IList collection = (IList)propertyInfo.GetValue(targetResource, null);
            collection.Add(resourceToBeAdded);

            if (!entitiesToBeUpdated.Contains(targetResource))
            {
                entitiesToBeUpdated.Add(targetResource);
            }
        }

        public void RemoveReferenceFromCollection(object targetResource, string propertyName, object resourceToBeRemoved)
        {
            PropertyInfo propertyInfo = targetResource.GetType().GetProperty(propertyName);

            if (propertyInfo == null)
            {
                throw new DataServiceException(405, "WCFDSAPI405-02", "Can't find the specified property / A propriedade parameterizada (" + propertyName +")", null, null);
            }

            IList collection = (IList)propertyInfo.GetValue(targetResource, null);
            collection.Remove(resourceToBeRemoved);

            if (!entitiesToBeUpdated.Contains(targetResource))
            {
                entitiesToBeUpdated.Add(targetResource);
            }
        }

        public void DeleteResource(object targetResource)
        {
            entitiesToBeRemoved.Add(targetResource);
        }

        public void SaveChanges()
        {
            using (var transaction = this.Session.BeginTransaction())
            {
                this.Session.FlushMode = FlushMode.Commit;

                foreach (var entity in entitiesToBeUpdated)
                {
                    this.Session.SaveOrUpdate(entity);
                }

                foreach (var entity in entitiesToBeRemoved)
                {
                    this.Session.Delete(entity);
                }

                transaction.Commit();
            }
        }

        public object ResolveResource(object resource)
        {
            return resource;
        }

        public void ClearChanges()
        {
            this.Session.Clear();
        }
    }
}
The WCF data service definition file (WcfDataService1.svc.cs)

This file includes a definition for a custom DataContext class derived from the abstract class where:

  • It is initialized with a ISession object
  • It includes a property for each entity set to be exposed, as IQueryable<T> format

This custom DataContext is used on the WCF Data Service definition.

The definition of the data service includes the implementation of the CreateDataSource method, witch is in charge of:

  • Configure NHibernate Session factory: IN this implementation the library with entity classes and mappings is directly imported (Training.Nibernate.DataA
  • Open the session
  • Instantiate the custom DataContext class described above, with the just created session

How to do it
using System;
using System.Collections.Generic;
using System.Data.Services;
using System.Data.Services.Common;
using System.Linq;
using System.ServiceModel.Web;
using System.Web;
using Web.DataServices.API.Providers.NHibernate;
using Training.NHibernate.Data;
using NHibernate;
using NHibernate.Cfg;
using NHibernate.Linq;
using System.ServiceModel;

namespace WCF.Data.Services
{
    public class TestDataContext : NHibernateDataContext
    {
        public TestDataContext(ISession pSession)
            : base(pSession) { }

        public IQueryable<Customer> Customers { get { return this.Session.Query<Customer>(); } }

        public IQueryable<Order> Orders { get { return this.Session.Query<Order>(); } }

    }

    [ServiceBehavior(IncludeExceptionDetailInFaults = true)]
    public class WcfDataService1 : DataService<TestDataContext>
    {

        #region "NHibernate datasource init/disposal"

        ISession session;

        protected override TestDataContext CreateDataSource()
        {
            Configuration configuration = new Configuration();
            configuration.Configure();
            configuration.AddAssembly("Training.NHibernate.Data");

            ISessionFactory factory = configuration.BuildSessionFactory();

            this.session = factory.OpenSession();

            return new TestDataContext(this.session);
        }

        public void Dispose()
        {
            this.session.Dispose();
            this.session = null;
        }

        #endregion

        // This method is called only once to initialize service-wide policies.
        public static void InitializeService(DataServiceConfiguration config)
        {
            // TODO: set rules to indicate which entity sets and service operations are visible, updatable, etc.

            config.SetEntitySetAccessRule("*", EntitySetRights.AllRead);
            // config.SetServiceOperationAccessRule("MyServiceOperation", ServiceOperationRights.All);
            config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
            config.UseVerboseErrors = true;
        }

    }
}
The WCF data service definition markup file (WcfDataService1.svc)
How to do it

<%@ ServiceHost Language="C#" Factory="System.Data.Services.DataServiceHostFactory, System.Data.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" Service="WCF.Data.Services.WcfDataService1" %>
Services configuration file (Web.config)
How to do it
<?xml version="1.0"?>
<configuration>
  <configSections>
  <section name="hibernate-configuration"
    type="NHibernate.Cfg.ConfigurationSectionHandler, NHibernate" />
    <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" />
  </configSections>
  <hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
    <session-factory>
      <property name="connection.provider">
        NHibernate.Connection.DriverConnectionProvider
      </property>
      <property name="connection.driver_class">
        NHibernate.Driver.SqlClientDriver
      </property>
      <property name="connection.connection_string">
        Persist Security Info=True; Initial Catalog=NHibernate; Data Source=vegeta\vegeta$sql2008;User ID=sa;Password=adm1n;
      </property>
      <property name="dialect">
        NHibernate.Dialect.MsSql2005Dialect
      </property>
      <property name="show_sql">
        true
      </property>
    </session-factory>
  </hibernate-configuration>
  <system.web>
    <compilation debug="true" targetFramework="4.0" />
  </system.web>
  <system.webServer>
    <modules runAllManagedModulesForAllRequests="true" />
  </system.webServer>
  <system.serviceModel>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
  </system.serviceModel>
</configuration>