Saturday, October 5, 2013

Step-by-step Guide - MVC 4 + Entity Framework 6 + Autofac + Generic Repository Pattern

This post is a step-by-step guide to laying out a web app based on MVC4 and utilises autofac for IoC EF6 for data access, and implements the generic repository pattern.

I know there might be some other complete examples similar to what I am writing here, but I thought I can write this as a step-by-step, explaining every step of the way.

Background

Developer: I want to build A typical MVC4 web app that connects resiliently to a data store. This web app should be built using IoC and implements the Generic Repository Pattern.

Ingredients
  1. Visual Studio 2012/2013 (any edition)
  2. IIS Express (or IIS)
  3. Sql Express (or Sql Server)
  4. Internet connection :)
Recipe

Step 1 - MVC Web App

In this step, create a simple MVC 4 Web App by doing the following:
  1. Open Visual studio
  2. Create a new Web - MVC4 Web App project
  3. Choose Empty Template

Step 2 - Install Autofac nuget package

To use Autofac for dependency Injection,

  1. Open Package Manager (from Tools menu)
  2. Search for Autofac.MVC4
  3. Install it
  4. Search for EntityFramework, and make sure you have included Prereleases.

Alternatively, you can run the following command from inside Package Manager Console

Install-Package Autofac.MVC4

Step 3 - Create DB Model

In this step, we will create a database model to represent data structure in the database.

  1. Add a new class library project to the solution
  2. Delete class1.cs from the project!
  3. Create two new folders to the project namely, DatabaseModel and DataAccess. These folders will hold the database model and data access classes respectively.
  4. right-click the first folder (DatabaseModel) and add new item
  5. Choose ADO.NET Entity Data Model, and type a good name for it.
  6. Choose "Generate from Database" in Model contents step, click Next
  7. When prompted to choose EF version, choose EF 6.0
  8. Select  a couple of Tables from the database. (In this sample, I'm using AdventureWorks2012 database .. you can download it from this link)

Step 4 - Implement Generic Repository

Now that we created our database model, we'll create data access class that implements the generic repository pattern.
  1. IDbContext: This interface will abstract all DBSets inside the context class generated in step3, and also adding a SaveChanges method which will be further used by Generic Repo to submit changes to the database

    using System.Data.Entity;
    using System.Data.Entity.Core.Objects;
    using System.Data.Entity.Infrastructure;
    
    namespace Blog.Samples.MVCEF6.Data.DataAcess
    {
        public interface IDbContext
        {
            IDbSet<tentity> Set<tentity>() where TEntity : class;
            int SaveChanges();
        }
    
        public static class DbContextExtensions
        {
            public static ObjectContext GetObjectContext(this IDbContext dbContext)
            {
                return ((IObjectContextAdapter)dbContext).ObjectContext;
            }
        }
    }
  2. Change context t4 template to reference IDbConext. You should find this file under the database model, namely [Something].Context.tt ..
    From
    <#=Accessibility.ForType(container)#> partial class <#=code.Escape(container)#> : DbContext
    

    To
    <#=Accessibility.ForType(container)#> partial class <#=code.Escape(container)#> : DbContext, IDbContext
    


    And add the following lines
    #>
        public ObjectContext ObjectContext
        {
            get { return this.GetObjectContext(); }
        }
    
        public new IDbSet<tentity> Set<tentity>() where TEntity : class
        {
            return base.Set<tentity>();
        }
    
        public new int SaveChanges()
        {
            return base.SaveChanges();
        }
        <#
    


    right after these lines
    <#
        foreach (var entitySet in container.BaseEntitySets.OfType())
        {
    #>
        <#=codeStringGenerator.DbSet(entitySet)#>
    <#
        }
    


    Also the Using directives
    using System.Data.Entity.Core.Objects;
    using Blog.Samples.MVCEF6.Data.DataAcess;
    


    right after
    using System;
    using System.Data.Entity;
    using System.Data.Entity.Infrastructure;
    
  3. Rebuild the project
  4. IObjectContext: This interface abstracts the ObjectContext (which in turn encapsulates the interaction between the database and the CLR.
    using System;
    using System.Data.Entity.Core.Objects;
    
    namespace Blog.Samples.MVCEF6.Data.DataAcess
    {
        public interface IObjectContext : IDisposable
        {
            void SaveChanges();
            ObjectContextOptions ContextOptions { get; }
        }
    }
    
    
  5. IObjectSetFactory: factory pattern implementation over ObjectSets
    using System;
    using System.Data.Entity;
    using System.Data.Entity.Core.Objects;
    
    namespace Blog.Samples.MVCEF6.Data.DataAcess
    {
        public interface IObjectSetFactory : IDisposable
        {
            IObjectSet<T> CreateObjectSet<T>() where T : class;
            void ChangeObjectState(object entity, EntityState state);
        }
    }
    
    
  6. ContextAdaptor: an Implementation of IObjectSetFactory and IObjectContext
    using System;
    using System.Data.Entity;
    using System.Data.Entity.Core.Objects;
    
    namespace Blog.Samples.MVCEF6.Data.DataAcess
    {
        public class ContextAdaptor : IObjectSetFactory, IObjectContext
        {
            private readonly ObjectContext _context;
    
            public ContextAdaptor(IDbContext context)
            {
                _context = context.GetObjectContext();
            }
    
            public void SaveChanges()
            {
                _context.SaveChanges();
            }
    
            public ObjectContextOptions ContextOptions
            {
                get { return _context.ContextOptions; }
            }
    
            public IObjectSet<T> CreateObjectSet<T>() where T : class
            {
                return _context.CreateObjectSet<T>();
            }
    
            public void ChangeObjectState(object entity, EntityState state)
            {
                _context.ObjectStateManager.ChangeObjectState(entity, state);
            }
    
            private bool _disposed;
            protected virtual void Dispose(bool disposing)
            {
                if (!_disposed)
                {
                    if (disposing && _context != null)
                    {
                        _context.Dispose();
                    }
                }
                _disposed = true;
            }
    
            public void Dispose()
            {
                Dispose(true);
                GC.SuppressFinalize(this);
            }
        }
    }
  7. IUnitOfWork and its implementation: this will control when/how to commit CLR changes to database
    using System;
    
    namespace Blog.Samples.MVCEF6.Data.DataAcess
    {
        public interface IUnitOfWork : IDisposable
        {
            void Commit();
            bool LazyLoadingEnabled { set; get; }
        }
    }
    

    and its implementation
    using System;
    
    namespace Blog.Samples.MVCEF6.Data.DataAcess
    {
        public class UnitOfWork : IUnitOfWork
        {
            private readonly IObjectContext _objectContext;
    
            public UnitOfWork(IObjectContext objectContext)
            {
                _objectContext = objectContext;
            }
    
            public void Commit()
            {
                _objectContext.SaveChanges();
            }
    
            public bool LazyLoadingEnabled
            {
                set { _objectContext.ContextOptions.LazyLoadingEnabled = value; }
                get { return _objectContext.ContextOptions.LazyLoadingEnabled; }
            }
    
            private bool _disposed;
            protected virtual void Dispose(bool disposing)
            {
                if (!_disposed)
                {
                    if (disposing && _objectContext != null)
                    {
                        _objectContext.Dispose();
                    }
                }
                _disposed = true;
            }
    
            public void Dispose()
            {
                Dispose(true);
                GC.SuppressFinalize(this);
            }
        }
    }
    
  8. IRepository: This interface abstracts all Repository-related operations, such as Get, Delete, Count, etc...
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Linq.Expressions;
    
    namespace Blog.Samples.MVCEF6.Data.DataAcess
    {
        public interface IRepository<TEntity> : IQueryable
            where TEntity : class
        {
            int CountAll(params Expression<Func<TEntity, object>>[] includeProperties);
            int Count(Expression<Func<TEntity, bool>> where, params Expression<Func<TEntity, object>>[] includeProperties);
            IEnumerable<TEntity> GetAll(params Expression<Func<TEntity, object>>[] includeProperties);
            IQueryable<TEntity> Find(Expression<Func<TEntity, bool>> where, params Expression<Func<TEntity, object>>[] includeProperties);
            TEntity Single(Expression<Func<TEntity, bool>> where, params Expression<Func<TEntity, object>>[] includeProperties);
            TEntity SingleOrDefault(Expression<Func<TEntity, bool>> where, params Expression<Func<TEntity, object>>[] includeProperties);
            TEntity First(Expression<Func<TEntity, bool>> where, params Expression<Func<TEntity, object>>[] includeProperties);
            TEntity FirstOrDefault(Expression<Func<TEntity, bool>> where, params Expression<Func<TEntity, object>>[] includeProperties);
            void Delete(TEntity entity);
            void Insert(TEntity entity);
            void Update(TEntity entity);
            void SaveChanges();
        }
    }
    

  9. Repository'T: Implementation of IRepository interface
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Data.Entity;
    using System.Data.Entity.Core.Objects;
    using System.Linq;
    using System.Linq.Expressions;
    
    namespace Blog.Samples.MVCEF6.Data.DataAcess
    {
        public class Repository<TEntity> : IRepository<TEntity>, IQueryable
            where TEntity : class
        {
            private readonly IObjectSet<TEntity> _objectSet;
            private readonly IObjectSetFactory _objectSetFactory;
            private readonly IUnitOfWork _unitOfWork;
    
            public Repository(IObjectSetFactory objectSetFactory, IUnitOfWork unitOfWork)
            {
                _objectSet = objectSetFactory.CreateObjectSet<TEntity>();
                _objectSetFactory = objectSetFactory;
                _unitOfWork = unitOfWork;
            }
    
            public virtual IQueryable<TEntity> AsQueryable()
            {
                return _objectSet;
            }
    
            public virtual int CountAll(params Expression<Func<TEntity, object>>[] includeProperties)
            {
                IQueryable<TEntity> query = AsQueryable();
                query = PerformInclusions(includeProperties, query);
                return query.Count();
            }
    
            public virtual int Count(Expression<Func<TEntity, bool>> where, params Expression<Func<TEntity, object>>[] includeProperties)
            {
                IQueryable<TEntity> query = AsQueryable();
                query = PerformInclusions(includeProperties, query);
                return query.Count(where);
            }
    
            public virtual IEnumerable<TEntity> GetAll(params Expression<Func<TEntity, object>>[] includeProperties)
            {
                IQueryable<TEntity> query = AsQueryable();
                query = PerformInclusions(includeProperties, query);
                return query.ToList();
            }
    
            public virtual IQueryable<TEntity> Find(Expression<Func<TEntity, bool>> where,
                                       params Expression<Func<TEntity, object>>[] includeProperties)
            {
                IQueryable<TEntity> query = AsQueryable();
                query = PerformInclusions(includeProperties, query);
                return query.Where(where);
            }
    
            public virtual TEntity Single(Expression<Func<TEntity, bool>> where, params Expression<Func<TEntity, object>>[] includeProperties)
            {
                IQueryable<TEntity> query = AsQueryable();
                query = PerformInclusions(includeProperties, query);
                return query.Single(where);
            }
    
            public virtual TEntity First(Expression<Func<TEntity, bool>> where, params Expression<Func<TEntity, object>>[] includeProperties)
            {
                IQueryable<TEntity> query = AsQueryable();
                query = PerformInclusions(includeProperties, query);
                return query.First(where);
            }
    
            public virtual TEntity SingleOrDefault(Expression<Func<TEntity, bool>> where, params Expression<Func<TEntity, object>>[] includeProperties)
            {
                IQueryable<TEntity> query = AsQueryable();
                query = PerformInclusions(includeProperties, query);
                return query.SingleOrDefault(where);
            }
    
            public virtual void Delete(TEntity entity)
            {
                _objectSet.DeleteObject(entity);
            }
    
            public virtual void Insert(TEntity entity)
            {
                _objectSet.AddObject(entity);
            }
    
            public virtual void Update(TEntity entity)
            {
                _objectSet.Attach(entity);
                _objectSetFactory.ChangeObjectState(entity, EntityState.Modified);
            }
    
            private static IQueryable<TEntity> PerformInclusions(IEnumerable<Expression<Func<TEntity, object>>> includeProperties,
                                                           IQueryable<TEntity> query)
            {
                return includeProperties.Aggregate(query, (current, includeProperty) => current.Include(includeProperty));
            }
    
    
            public void SaveChanges()
            {
                _unitOfWork.Commit();
            }
    
            public TEntity FirstOrDefault(Expression<Func<TEntity, bool>> where, params Expression<Func<TEntity, object>>[] includeProperties)
            {
                var query = AsQueryable();
                query = PerformInclusions(includeProperties, query);
                return query.FirstOrDefault(where);
            }
    
            public IEnumerator GetEnumerator()
            {
                return _objectSet.GetEnumerator();
            }
    
            public Expression Expression
            {
                get { return _objectSet.Expression; }
            }
            public Type ElementType
            {
                get { return _objectSet.ElementType; }
            }
            public IQueryProvider Provider
            {
                get { return _objectSet.Provider; }
            }
        }
    }
    
    
Your Data project should look similar to this:

Step 5 - Implement Domain Assembly

So, we got the database model, and data access classes. Now, we are going to implement some business (domain) services that should consume database and perform some logic on it. This assembly will serve the WebApp (or whatever app) that we created in Step1

  1. Add a new class library project to the solution
    step 5 first pic goes here
  2. Name it something awesome
  3. Delete class1.cs :)
  4. Add reference to the Data project
  5. Beef up the assembly by adding a sample service that uses data access classes to retrieve a list of models
    • right-click the domain project
    • add a new folder with the name of 'Services' (arbitrarily)
    • Add a new interface under this folder, name it IProductsServices:
      using System.Collections.Generic;
      using Blog.Samples.MVCEF6.Data.DatabaseModel;
      
      
      namespace Blog.Samples.MVCEF6.Domain.Services
      {
          public interface IProductsServices
          {
              IEnumerable<product> ListProductsByVendor(int businessEntityID);
              IEnumerable<vendor> ListVendors();
          }
      }
      
    • Implement the interface by consuming the Repository'T
      using System.Collections.Generic;
      using System.Linq;
      using Blog.Samples.MVCEF6.Data.DataAcess;
      using Blog.Samples.MVCEF6.Data.DatabaseModel;
      
      namespace Blog.Samples.MVCEF6.Domain.Services
      {
          public class ProductsServices : IProductsServices
          {
              private readonly IRepository<Product> _productsRepository;
              private readonly IRepository<Vendor> _vendorsRepository;
              private readonly IRepository<ProductVendor> _productsVendorsRepository;
      
              public ProductsServices(IRepository<Product> productsRepository,
                  IRepository<Vendor> vendorsRepository,
                  IRepository<ProductVendor> productsVendorsRepository)
              {
                  _productsRepository = productsRepository;
                  _vendorsRepository = vendorsRepository;
                  _productsVendorsRepository = productsVendorsRepository;
              }
      
              public IEnumerable<Product> ListProductsByVendor(int businessEntityID)
              {
                  return _productsRepository.Find(a => a.ProductVendors.Any(pv => pv.BusinessEntityID == businessEntityID));
              }
      
              public IEnumerable<Vendor> ListVendors()
              {
                  return _vendorsRepository.GetAll();
              }
          }
      }
      

Step 6 - Wire up using Autofac

We've got the database model and the Data access classes and domain assembly, the next step is to wire all this up with the web application using Autofac.
  1. In the web app project, Add reference to the domain project that we created in Step 5,
  2. Add DependencyConfig class: This class contains any IoC-related code, We're going to call its main function from Global.asax.cs
    • right-click the App_start folder in the web project, and add a new class, name it "DependencyConfig"
    • Add the following code to the class:
      using Autofac;
      using Autofac.Integration.Mvc;
      using Blog.Samples.MVCEF6.Data.DataAcess;
      using Blog.Samples.MVCEF6.Data.DatabaseModel;
      using System.Web.Mvc;
      
      namespace Blog.Samples.MVCEF6.Web.App_Start
      {
          public class DependencyConfig
          {
              public static void Configure(ContainerBuilder builder)
              {
                  builder.RegisterControllers(typeof(MvcApplication).Assembly)
                         .PropertiesAutowired();
      
                  builder.RegisterAssemblyTypes(typeof(ContextAdaptor).Assembly)
                      .AsImplementedInterfaces()
                      .InstancePerHttpRequest();
      
                  builder.RegisterType<AdventureWorks2012Entities>()
                      .As<IDbContext>()
                      .InstancePerHttpRequest();
      
                  builder.RegisterType<ContextAdaptor>()
                      .As<IObjectSetFactory, IObjectContext>()
                      .InstancePerHttpRequest();
      
                  builder.RegisterType<UnitOfWork>()
                      .As<IUnitOfWork>()
                      .InstancePerHttpRequest();
      
                  builder.RegisterGeneric(typeof(Repository<>))
                      .As(typeof(IRepository<>))
                      .InstancePerHttpRequest();
      
                  var container = builder.Build();
                  var dependencyResolver = new AutofacDependencyResolver(container);
                  DependencyResolver.SetResolver(dependencyResolver);
              }
          }
      }
  3. Call DepedencyConfig.Configure from global.asax.cs
    • add the following line inside Aplication_start function, right after RegisterAllAreas line
      DependencyConfig.Configure(new ContainerBuilder());

Step 7 - Add some Controllers/Views to test it out

Let's now add some MVC stuff in the web project:
  1. right-click Models folder, add a new class to represent the HomeViewModel:
    using Blog.Samples.MVCEF6.Data.DatabaseModel;
    using System.Collections.Generic;
    
    namespace Blog.Samples.MVCEF6.Web.Models
    {
        public class HomeViewModel
        {
            public IEnumerable<Vendor> Vendors { get; set; }
            public int SelectedBusinessEntityID { get; set; }
        }
    }
    
    
  2. right-click Controllers folder, and a new HomeController, Use the following code to beef it up.
    using System.Web.Mvc;
    using Blog.Samples.MVCEF6.Domain.Services;
    using Blog.Samples.MVCEF6.Web.Models;
    
    namespace Blog.Samples.MVCEF6.Web.Controllers
    {
        public class HomeController : Controller
        {
            private readonly IProductsServices _productsServices;
    
            public HomeController(IProductsServices productsServices)
            {
                _productsServices = productsServices;
            }
    
            public ActionResult Index()
            {
                return View(new HomeViewModel()
                    {
                        Vendors = _productsServices.ListVendors()
                    });
            }
    
        }
    }
    

  3. right-click Views folder, and a new folder, name it "Home" ..right-click Home folder under Views, add an Index view. You can reference _Layout.cshtml as the master page. Use the following code to beef it up.
    @model Blog.Samples.MVCEF6.Web.Models.HomeViewModel
    @{
        ViewBag.Title = "Index";
        Layout = "~/Views/Shared/_Layout.cshtml";
    }
    
    <h2>Index</h2>
    
    @Html.DropDownListFor(m => m.SelectedBusinessEntityID, Model.Vendors.Select(v => new SelectListItem
        {
            Text = v.Name,
            Value = v.BusinessEntityID.ToString()
        }))
    
    
  4. I won't be consuming the GetProductByVendor in this sample, I'm leaving this for you as an exercise.

You can find the complete code here

Thursday, September 26, 2013

Load testing your cloud app - Tools comparison

If you’re planning to load test your cloud app, the comparison below might be handy.
The research I have done is on these service providers:
  1.          Soasta CloudTest,
  2.          Blitz,
  3.          BlazeMeter,
  4.          LoadStorm,
  5.          and MicrosoftCloud-based Load test tool.


Soasta – CloudTest

Pros

  •          A complete environment to setup tests
  •          Add test users locations seamlessly
  •          a good coverage around the globe
  •          A comprehensive real-time interface to metrics
  •          Drill down analysis on web requests
  •          A Lite (Free) version is available with a limit of 100 concurrent users

Cons

  •          Requires some setup: you have to spin up a VM to load the test environment

Pricing

“CloudTest is sold based on server hours, including support. With no limits on the number of testers or software access, you select
Plans based on how many test server hours you’ll run. Plans are available for coverage of a few tests each month up to thousands of tests each year” - http://www.soasta.com/wp/wp-content/uploads/2012/10/DS-CloudTest-100812.pdf

Blitz

Pros

  •          Capacity planning tools, Optimization, Performance monitoring are out-of-the-box
  •          Nothing to install/setup, all cloud-based
  •          Simulate up to 5,000 concurrent virtual users
  •          It has a chrome/firefox plugin to monitor app performance on develop environment
  •          Integrated with Google Analytics
  •          Performance Monitoring Alerts  (Email, Twitter, SMS, PagerDuty)

Cons

  •          Only 8 test locations around the globe (Australia, Brazil, California Ireland, Japan, Oregon, Singapore, Virginia)
  •          Relatively less sophisticated interface (compared to Soasta)

Pricing

  •          Works with “Credits”
  •          1 credit = 1 minute and 1,000 users - https://www.blitz.io/pricing
  •          40 Credits = $40
  •          150 Credits = $135
  •          300 Credits = $240

BlazeMeter

Pros

  •          JMeter compliant
  •          Easy to use test creation interface
  •          Nice interface for test results
  •          You can export test results
  •          Compare two test runs
  •          Has a server in Australia (yes, we’re load-testing apps in Oz)
  •          Test scheduling
  •          Integrated with Google Analytics
  •          VPN is supported
  •          Chrome extension to record JMeter scripts and upload it to BlazeMeter
  •          It has a free version that can simulate up to 50 concurrent users
  •          A good customer base

Cons

  •          Limited Geo coverage (California, Oregon and Virginia USA, Tokyo, Ireland, Brazil, Singapore and Australia)

Pricing

  •          Monthly plans – Starting $199 (1,000 users, 20 hours) up to “Call Us”
  •          On-Demand (Per hour) – Starting $19 (1,000 users, 1 server) to $299 (40,000 users, 40 servers)
  •          http://blazemeter.com/pricing



LoadStorm

Pros

  •          Scalable up to 300,000 users
  •          A testing data centre in Sydney, Australia
  •          Supports automatic crawling (Spider)
  •          No scripting language is involved
  •          Nothing to install

Cons

  •          Currently they only cover Virginia, California, Oregon, Ireland, Sydney, Tokyo, Singapore, Sao Paulo
  •          No built-in feature to collect server performance metrics

Pricing

  •          Monthly plans – Starting Free (100 users, 25 users per test) up to “Call Us”
  •          On-Demand (Per hour) – Starting $39 (1,000 users per test) to “Call” (10,000+ users)
  •          http://loadstorm.com/load-testing-cost/



Microsoft Cloud-based Load test tool

Pros

  •          MS offers a free version of TFS services (up to 5 developers)
  •          Reuse on-premise tests
  •          Easy to set-up

Cons

  •          Requires Visual Studio™ Ultimate (Pricy)

Pricing

  •          It’s still vague - varies according to your MSDN subscription
  •          If you don’t have an MSDN subscription, you’ll have to buy VS Ultimate (around AU$19,274) and then subscribe for TFS Services



Wednesday, September 4, 2013

Wednesday, August 14, 2013

SydneyAzure User group - BizTalk Services Session

Last night I attended SydneyAzure UG. The talk was about BizTalk Services, presented by Mick Badran.

here's a summary...

What are BizTalk Services?

      ·         Windows Azure Cloud-based integration service
o   B2B integration (Partners, agreements, ..)
o   rich message endpoints
     ·         BizTalk Adapter service pack
o   on-premise adapters designed to rapidly connect systems
o   It has management service (REST APIs)

BizTalk Services SDK

   §  VS2012 - Developer Surface
o   Project Type
o   Bridges: It's like a port (in BizTalk land) where it can connect to source and destination and in between a staged pipeline that can decode, enrich, transform, call external assemblies, .. Just like a normal BizTalk Pipline.
o   Connector: an arrow that connects a bridge to source or destination, it can be configured for message inspectors
o   Schemas: You cannot distinguish or promote properties as in BizTalk Server, since there’s no property schemas
o   Maps
§  New map designer
§  they added a new GetBizTalkContextProperty functoid,
§  map-level settings on how to handle mapping errors
·         Fail map
·         output null
o   all artefacts are compiled into a Workflow activity
o   No support for orchestrations yet. Maybe the next release there will be support for Workflow (Windows Workflows)
  §  You don’t have to have BizTalk Server installed on the machine, they’re totally independent.
  §  It’s still V1, so don’t expect unit testing frameworks are there to support your development.
  §  BizTalk Services Adapter Service + IIS Plugin
  §  Developer + Runtime components are supplied


   The conclusion I reached after attending the session is that the product is not yet mature enough to market it to clients, but it’s good as a start for moving BizTalk components to the cloud.

Friday, March 1, 2013

Why keep the balance?

I read many posts/articles about keeping the balance between personal and professional life. For starters, this one starts by giving some tips about to keep the balance.

However, I believe we're only living one life (Personal + Professional), at the end of the day, you're one human being that has one brain, one heart, etc.. So why do we bother keeping this virtual balance where there's no line, even fine line, between the so-called two lives?

The moment you start this separation, the same moment you stop the ideas flow throughout your day. You have only one life, don't restrict your mind to this separation of concerns where it doesn't exist!

M

Luxor - Reports Deployment Tool for Dynamics CRM 4.0

I posted a new tool on codeplex:

This tool helps developers copy reports (SSRS) files and its configuration from development to production environment. It's compatible with Dynamics CRM 4.0.

This release includes the following features:
Batch-Downloading Reports
Batch-Uploading Reports

The package includes source code and executable file.

http://luxor.codeplex.com/