Archive for June, 2008

Testing batched Service calls

No Comments »Written on June 29th, 2008 by
Categories: testing

Now that we can batch WCF calls, and do so with readable code we still need a way to test this.

Suppose that we have a page where we need to display a dropdown list of product categories and suppliers. This is the code in the controller of that screen for the Load event:

            if (!View.IsPostBack)

            {

                var batcher = new ServiceCallBatcher(service);

                batcher.Add("categories", new GetProductCategoriesRequest());

                batcher.Add("suppliers", new GetSuppliersRequest());

                View.ProductCategories = batcher.Get<GetProductCategoriesResponse>("categories").ProductCategories;

                View.Suppliers = batcher.Get<GetSuppliersResponse>("suppliers").Suppliers;

                View.DataBind();

            }

When i'm testing my controllers, i like to use mocked service instances, and i set expectations on the methods that should be called and the parameters they receive. With my batching technique, i don't really execute specific methods on the service because the batcher calls this method:

        Response[] Process(params Request[] requests);

My Request classes are really simple, and to keep them simple they don't override the Equals method. This makes it hard to set expectations because the Request instances are created by the controller, and i can't set expectations on the Process method using Request instances that would equal the Request instances that were passed to the batcher by the controller. This makes it hard to test that the service is called correctly. If a Request type contains properties (which are really service method parameters) you really want to be able to test that those properties contain the correct values. Also, you want to make sure that the correct Requests are sent to the service. But if they're handled by this very generic Process method, it makes it hard to verify correct usage during a test.

So how can we properly test the code listed above? I came up with the following approach. First we need a class that we can use to set up the Response instances to return from the service's Process method, and to capture the Request instances that are passed to the Process method:

    public class ServiceRequestResponseSpy

    {

        private Request[] receivedRequests;

        private Response[] responsesToReturn;

 

        public void SetResponsesToReturn(params Response[] responses)

        {

            responsesToReturn = responses;

        }

 

        public T GetRequest<T>(int index) where T : Request

        {

            return (T)receivedRequests[index];

        }

 

        public Response[] GrabRequestsAndReturnGivenResponses(params Request[] requests)

        {

            receivedRequests = requests;

            return responsesToReturn;

        }

    }

Now we can write our test like this:

        [Test]

        public void RetrievesCategoriesAndSuppliersOnLoadIfNotPostBack()

        {

            view.Stub(v => v.IsPostBack).Return(false);

 

            var categoriesToReturn = new ProductCategoryDTO[0];

            var suppliersToReturn = new SupplierDTO[0];

            var spy = new ServiceRequestResponseSpy();

            spy.SetResponsesToReturn(new GetProductCategoriesResponse(categoriesToReturn),

                new GetSuppliersResponse(suppliersToReturn));

 

            service.Stub(s => s.Process(null))

                .IgnoreArguments()

                .Do(new Func<Request[], Response[]>(spy.GrabRequestsAndReturnGivenResponses));

 

            view.Expect(v => v.ProductCategories = categoriesToReturn);

            view.Expect(v => v.Suppliers = suppliersToReturn);

            view.Expect(v => v.DataBind());

 

            CreateController();

            controller.Load();

 

            view.VerifyAllExpectations();

            Assert.IsNotNull(spy.GetRequest<GetProductCategoriesRequest>(0));

            Assert.IsNotNull(spy.GetRequest<GetSuppliersRequest>(1));

        }

The important part of the code is this one:

            service.Stub(s => s.Process(null))

                .IgnoreArguments()

                .Do(new Func<Request[], Response[]>(spy.GrabRequestsAndReturnGivenResponses));

This basically tells Rhino Mocks to execute the spy's GrabRequestsAndReturnGivenRepsonses method whenever the mocked service instance's Process method is called, no matter what arguments are passed to it.

So we basically set up the mocked service to delegate the call to Process to the GrabRequestsAndReturnGivenRepsonses method, and then we set expectations on how the returned data should be passed to the view. After that we execute the Controller's Load method, and we verify that the view's expectations were met, and we test that the spy captured Request instances of the expected types.

The Service Call Batcher

No Comments »Written on June 28th, 2008 by
Categories: Performance, WCF

Picking up where we left off with the WCF batching... We had the following code client-side to execute a few service methods with one request:

            var results = service.Process(new GetProductCategoriesRequest(), new GetProductOverviewsRequest());

            View.ProductCategories = ((GetProductCategoriesResponse)results[0]).ProductCategories;

            View.Products = ((GetProductOverviewsResponse)results[1]).Products;

Similar to my query batcher, i wrote this simple WCFCallBatcher class:

    public class ServiceCallBatcher

    {

        private readonly IService service;

        private readonly Dictionary<string, int> responsePositions = new Dictionary<string, int>();

        private readonly List<Request> requests = new List<Request>();

        private Response[] responses;

 

        public ServiceCallBatcher(IService service)

        {

            this.service = service;

        }

 

        public void Add(string key, Request request)

        {

            requests.Add(request);

            responsePositions.Add(key, requests.Count - 1);

        }

 

        public T Get<T>(string key) where T : Response

        {

            if (responses == null) ExecuteRequests();

            return (T)responses[responsePositions[key]];

        }

 

        private void ExecuteRequests()

        {

            responses = service.Process(requests.ToArray());

        }

    }

and now we can rewrite the client code like this:

            var batcher = new ServiceCallBatcher(service);

            batcher.Add("categories", new GetProductCategoriesRequest());

            batcher.Add("products", new GetProductOverviewsRequest());

            View.ProductCategories = batcher.Get<GetProductCategoriesResponse>("categories").ProductCategories;

            View.Products = batcher.Get<GetProductOverviewsResponse>("products").Products;

Yes, we've got more lines of code now, but i like this block of a code a lot more than the earlier one. This is much more readable.

Batching WCF calls

16 commentsWritten on June 26th, 2008 by
Categories: Performance, WCF

Update: i also have a complete series of posts on this which you may find interesting.

What happens if you have the following code client-side:

            View.ProductCategories = service.GetProductCategories();

            View.Products = service.GetProductOverviews();

The service variable is a reference to a WCF service proxy. This means that this causes 2 remote service calls. I really hate unnecessary/excessive roundtrips, so i would rather retrieve this data from the service in one roundtrip. Unfortunately, this is not something that is supported by WCF out of the box, so if you want to do this you have to do it yourself. There are a couple of options, so i started browsing the web. Once again, it's Ayende to the rescue. The approach he suggests is a bit cumbersome compared to how you'd normally design your services, but it does offer a great deal of flexibility and it allows you to nicely batch your requests. So i figured i should try implementing this approach.

The idea is to provide a service method that looks like this:

        Response[] Process(params Request[] requests);

Each Request corresponds to what used to be a normal service method, and this method returns a Response for each Request. In Ayende's post, he suggest offering this as the only service method. I won't go that far though. I'll offer this method, and each 'normal' method allthough those methods will also use Request types as input variables and Response types as return values. The idea is to be able to use each service method individually, or to have multiple service methods executed in one roundtrip through the Process method.

We need a way to define Requests and Responses, so we create the following classes:

    [Serializable]

    public abstract class Response { }

 

    [Serializable]

    public abstract class Request {}

For now, these are empty but i'm pretty sure i'll add a few members to these later on. Then we define specific Request and Response types for the functionality our service needs to offer:

    [Serializable]

    public class GetProductCategoriesRequest : Request {}

 

    [Serializable]

    public class GetProductOverviewsRequest : Request

    {

        public int? ProductCategoryId { get; set; }

    }


    [Serializable]

    public class GetProductCategoriesResponse : Response

    {

        public ProductCategoryDTO[] ProductCategories { get; private set; }

 

        public GetProductCategoriesResponse(ProductCategoryDTO[] productCategories)

        {

            ProductCategories = productCategories;

        }

    }

 

    [Serializable]

    public class GetProductOverviewsResponse : Response

    {

        public ProductOverviewDTO[] Products { get; private set; }

 

        public GetProductOverviewsResponse(ProductOverviewDTO[] products)

        {

            Products = products;

        }

    }

We then create a base interface that each service will implement (along with more specific service interfaces):

    [ServiceContract]

    public interface IService : IDisposable

    {

        /// <summary>

        /// Processes each request within the same service call

        /// </summary>

        /// <param name="requests">each request</param>

        /// <returns>the corresponding responses</returns>

        [OperationContract]

        [ServiceKnownType("GetKnownTypes", typeof(KnownTypeProvider))]

        Response[] Process(params Request[] requests);

    }

The KnownTypeProvider is responsible for providing a list of types that should be treated as KnownTypes during serialization/deserialization. We could also list each type that derives from Response/Request but that will become a long list, so i went for this approach. The code of the KnownTypeProvider class looks like this:

    public static class KnownTypeProvider

    {

        private static readonly List<Type> knownTypes;

 

        static KnownTypeProvider()

        {

            knownTypes = BuildListOfKnownTypes();

        }

 

        private static List<Type> BuildListOfKnownTypes()

        {

            Assembly containingAssembly = typeof(KnownTypeProvider).Assembly;

 

            return containingAssembly.GetTypes()

                .Select(t => t)

                .Where(t => t.IsSubclassOf(typeof(Request)) || t.IsSubclassOf(typeof(Response)))

                .ToList();

        }

 

        public static IEnumerable<Type> GetKnownTypes(ICustomAttributeProvider provider)

        {

            return knownTypes;

        }

    }

Our real service now looks like this:

    [ServiceContract]

    public interface IProductsService : IService

    {

        [OperationContract]

        GetProductCategoriesResponse GetProductCategories(GetProductCategoriesRequest request);

 

        [OperationContract]

        GetProductOverviewsResponse GetProductOverviews(GetProductOverviewsRequest request);

    }

To handle these requests, i came up with the following RequestHandler type:

    public abstract class RequestHandler : Disposable, IRequestHandler

    {

        public IUnitOfWork UnitOfWork { get; private set; }

 

        protected RequestHandler(IUnitOfWork unitOfWork)

        {

            UnitOfWork = unitOfWork;

        }

 

        protected override void DisposeObjects()

        {

            if (UnitOfWork != null) UnitOfWork.Dispose();

        }

 

        protected override void ClearReferences()

        {

            UnitOfWork = null;

        }

 

        public abstract Response Handle(Request request);

    }

And the implementation of one of these concrete RequestHandlers looks like this:

    public class GetProductOverviewsHandler : RequestHandler

    {

        public ProductRepository ProductRepository { get; private set; }

 

        public GetProductOverviewsHandler(IUnitOfWork unitOfWork, ProductRepository productRepository)

            : base(unitOfWork)

        {

            ProductRepository = productRepository;

        }

 

        public override Response Handle(Request request)

        {

            var typedRequest = (GetProductOverviewsRequest)request;

 

            if (typedRequest.ProductCategoryId.HasValue)

            {

                var fetchStrategy = new FetchStrategy

                    { Association = "Supplier", FetchMode = FetchMode.Join };

                var productOverviews = ProductRepository.FindAllByCategory(

                    typedRequest.ProductCategoryId.Value, fetchStrategy);

                return new GetProductOverviewsResponse(productOverviews.ToOverviewDTOs());

            }

            else

            {

                // TODO: use query batcher to fetch all suppliers and products in one trip

                var productOverviews = ProductRepository.FindAll();

                return new GetProductOverviewsResponse(productOverviews.ToOverviewDTOs());

            }

        }

    }

Now we have to make sure we can actually use this RequestHandlers in a generic manner. I came up with the following base class for my services:

    public abstract class Service : IService

    {

        /// <summary>

        /// server-side service implementation should be stateless, but the IService

        /// interface defines the Dispose method, so we just provide an empty one here

        /// </summary>

        public void Dispose() {}

 

        public Response[] Process(params Request[] requests)

        {

            if (requests == null)

            {

                return null;

            }

 

            var responses = new List<Response>(requests.Length);

 

            foreach (var request in requests)

            {

                Type handlerType = GetHandlerTypeFor(request);

 

                if (handlerType == null)

                {

                    responses.Add(new UnsupportedRequestResponse());

                    continue;

                }

 

                using (var handler = GetHandlerInstance(handlerType))

                {

                    responses.Add(handler.Handle(request));

                }

            }

 

            return responses.ToArray();

        }

 

        public virtual IRequestHandler GetHandlerInstance(Type handlerType)

        {

            return (IRequestHandler)Container.Resolve(handlerType);

        }

 

        public abstract Type GetHandlerTypeFor(Request request);

    }

And the real service then looks like this:

    public class ProductsService : Service, IProductsService

    {

        private static readonly object monitor = new object();

 

        private static readonly Dictionary<Type, Type> requestTypesToRequestHandlerTypes

            = new Dictionary<Type, Type>

            {

                { typeof(GetProductCategoriesRequest), typeof(GetProductCategoriesHandler) },

                { typeof(GetProductOverviewsRequest), typeof(GetProductOverviewsHandler) }

            };

 

        public GetProductCategoriesResponse GetProductCategories(GetProductCategoriesRequest request)

        {

            return (GetProductCategoriesResponse)Process(request)[0];

        }

 

        public GetProductOverviewsResponse GetProductOverviews(GetProductOverviewsRequest request)

        {

            return (GetProductOverviewsResponse)Process(request)[0];

        }

 

        public override Type GetHandlerTypeFor(Request request)

        {

            Type requestType = request.GetType();

 

            lock (monitor)

            {

                if (!requestTypesToRequestHandlerTypes.ContainsKey(requestType))

                {

                    return null;

                }

 

                return requestTypesToRequestHandlerTypes[requestType];

            }

        }

    }

At this point, using a dictionary to map the Request types to the RequestHandler types seems silly, but this is just a small part of the service, more methods will be added so more Request and RequestHandlers will be handled, and in that case i prefer to use a dictionary lookup over a big if statement :)

So now i can do this client-side:

            var results = service.Process(new GetProductCategoriesRequest(), new GetProductOverviewsRequest());

            View.ProductCategories = ((GetProductCategoriesResponse)results[0]).ProductCategories;

            View.Products = ((GetProductOverviewsResponse)results[1]).Products;

And it only uses one remote call which will be much better for performance. At this point, the syntax is not so nice, but i'll probably provide something similar to my query batcher class so that should improve readability.

So that's pretty much it... it takes some effort to set this up, and there's still more to be done (dealing with exceptions in one of the requests for instance) but once you've got it set up, i kinda like it. Sure, implementing service methods takes more code than it used to because of the extra classes involved, but they do offer a few very important benefits. You may have noticed that the RequestHandler types receive all of their dependencies through the constructor, and that they are created through the IoC container. This means i can very easily add/remove/change dependencies in my handlers without having to modify code in other places. It also means i can test my service implementations in a thorough manner while providing mocked dependencies instead of the real ones. Another big advantage is that i can add optional parameters to my Request types and thus, offer more functionality through my RequestHandlers without having to update the services and the proxies.

So all in all, while this approach requires to you write more code at first, i think it might actually end up giving you more flexibility for changes later on, while offering you much more control over certain performance characteristics.

Data Access With NHibernate

19 commentsWritten on June 23rd, 2008 by
Categories: NHibernate

One thing that keeps amazing me is how many smart developers still feel the urge to write their own data access layer (DAL). They either do it all manually, or they generate parts of it, or they generate the whole thing. Whichever way you go, there are still various alternative paths you can choose from. Some people like to use stored procedures for everything, some people generate sql statements and provide a semi-OO API in front of it, some people still spend time constructing Command objects. I think i've seen most approaches by now, and my personal opinion is that they pretty much all suck.

These DAL's usually have at least one big downside to them. Generated DAL's are usually pretty good for productivity but most of the times they don't really offer you that much control over how queries should be executed, whether or not statements should be batched, specify fetching-strategies, etc. Hand-written DAL's are terrible for productivity but you can fine tune each action to an optimal implementation. These downsides are pretty big IMHO, and are often underestimated. Sometimes due to semi-religious stances and sometimes it's just due to ignorance.

As you already know, i'm a fan of NHibernate. Well, consider me a fan of decent ORM's in general. NHibernate just happens to be the one i know best and am very happy with. In this post i'd like to take a look at how you can easily offer the most common data access requirements without generating code, while still allowing high developer productivity. We'll go over the implementation of a generic Repository class, and hopefully you'll see how much NHibernate can improve your way of working.

We're going to look at the implementation piece by piece, so lets just start with the declaration of the class so we can get that out of the way:

    public class Repository<T> : IRepository<T>

As you can see, this is just a generic class that takes a type parameter. The type parameter represents the type of the Entity you want this repository to handle. If you have an Entity base class or interface, you probably want to restrict the type of T to inherit from Entity or implement IEntity or whatever.

This class needs to be able to access the current NHibernate session, which i discussed yesterday:

        private readonly IActiveSessionManager activeSessionManager;

 

        public Repository(IActiveSessionManager activeSessionManager)

        {

            this.activeSessionManager = activeSessionManager;

        }

 

        protected ISession Session

        {

            get { return activeSessionManager.GetActiveSession(); }

        }

Right, now we can actually get to the interesting Data Access parts. Obviously, each DAL needs a way to retrieve a specific entity based on its Primary Key value:

        /// <summary>

        /// Retrieves the entity with the given id

        /// </summary>

        /// <param name="id"></param>

        /// <returns>the entity or null if it doesn't exist</returns>

        public T Get(object id)

        {

            return Session.Get<T>(id);

        }

Very straightforward stuff... this simply uses the current NHibernate session to retrieve an entity of the requested type, with the given primary key value.

Another thing we need is a way to create or update entity instances:

        /// <summary>

        /// Saves or updates the given entity

        /// </summary>

        /// <param name="entity"></param>

        public void SaveOrUpdate(T entity)

        {

            Session.SaveOrUpdate(entity);

        }

This method simply uses the session's SaveOrUpdate method, which will either perform an Insert (in case of a new entity) or an Update (in case of an existing entity). NHibernate will perform a check for a new instance depending on how you've configured this in your mapping files.

So far this has been really straightforward, so it's time to get to a more interesting part. Retrieving entities based on criteria (basically a query):

        /// <summary>

        /// Returns each entity that matches the given criteria

        /// </summary>

        /// <param name="criteria"></param>

        /// <returns></returns>

        public IEnumerable<T> FindAll(DetachedCriteria criteria)

        {

            return criteria.GetExecutableCriteria(Session).List<T>();

        }

This probably needs a bit of explaining. The parameter is an instance of the DetachedCriteria type. A DetachedCriteria is just like a Criteria instance, except that it hasn't been associated with a session yet. So you can create the DetachedCriteria without being connected to a session. This basically represents a query. I'll show a concrete example of this later on. The thing to remember is that this is the only code you really need to perform any query you want. You just have to write the query using the Criteria API. This does have a bit of a learning curve, but most people pick it up pretty fast.

You usually also want a way to determine the ordering of the result of the query:

        /// <summary>

        /// Returns each entity that maches the given criteria, and orders the results

        /// according to the given Orders

        /// </summary>

        /// <param name="criteria"></param>

        /// <param name="orders"></param>

        /// <returns></returns>

        public IEnumerable<T> FindAll(DetachedCriteria criteria, params Order[] orders)

        {

            if (orders != null)

            {

                foreach (var order in orders)

                {

                    criteria.AddOrder(order);

                }

            }

 

            return FindAll(criteria);

        }

Each order you provide is applied to the query and then the query is executed. Again, pretty simple stuff right?

You want to know how you can get paging working? Here it is:

        /// <summary>

        /// Returns each entity that matches the given criteria, with support for paging,

        /// and orders the results according to the given Orders

        /// </summary>

        /// <param name="criteria"></param>

        /// <param name="firstResult"></param>

        /// <param name="numberOfResults"></param>

        /// <param name="orders"></param>

        /// <returns></returns>

        public IEnumerable<T> FindAll(DetachedCriteria criteria, int firstResult, int numberOfResults, params Order[] orders)

        {

            criteria.SetFirstResult(firstResult).SetMaxResults(numberOfResults);

            return FindAll(criteria, orders);

        }

Keep in mind that the executed query only retrieves the results within the paging range. It does not retrieve everything to perform the paging client-side, this happens in the db where it's supposed to happen.

What if you have a query that should only return one instance? That's easy to do as well:

        /// <summary>

        /// Returns the one entity that matches the given criteria. Throws an exception if

        /// more than one entity matches the criteria

        /// </summary>

        /// <param name="criteria"></param>

        /// <returns></returns>

        public T FindOne(DetachedCriteria criteria)

        {

            return criteria.GetExecutableCriteria(Session).UniqueResult<T>();

        }

What if you have a query and you just want the very first result instead of the entire resultset? Again, pretty easy to do:

        /// <summary>

        /// Returns the first entity to match the given criteria

        /// </summary>

        /// <param name="criteria"></param>

        /// <returns></returns>

        public T FindFirst(DetachedCriteria criteria)

        {

            var results = criteria.SetFirstResult(0).SetMaxResults(1)

                .GetExecutableCriteria(Session).List<T>();

 

            if (results.Count > 0)

            {

                return results[0];

            }

 

            return default(T);

        }

Again, NHibernate will issue a smart sql statement... that is, it only retrieves the first result instead of the entire resultset.

Obviously, this is also useful if you can define the order of the results to pick the first result:

        /// <summary>

        /// Returns the first entity to match the given criteria, ordered by the given order

        /// </summary>

        /// <param name="criteria"></param>

        /// <param name="order"></param>

        /// <returns></returns>

        public T FindFirst(DetachedCriteria criteria, Order order)

        {

            return FindFirst(criteria.AddOrder(order));

        }

How often have you seen developers execute a query in code, only to use the count of the records without actually needing the returned records? We no longer need to beat the shit out of these developers:

        /// <summary>

        /// Returns the total number of entities that match the given criteria

        /// </summary>

        /// <param name="criteria"></param>

        /// <returns></returns>

        public long Count(DetachedCriteria criteria)

        {

            return Convert.ToInt64(criteria.GetExecutableCriteria(Session)

                .SetProjection(Projections.RowCountInt64()).UniqueResult());

        }

In this case, NHibernates issues a simple select count... query based on the criteria you've provided. Nice huh?

We might as well throw in this one as well:

        /// <summary>

        /// Returns true if at least one entity exists that matches the given criteria

        /// </summary>

        /// <param name="criteria"></param>

        /// <returns></returns>

        public bool Exists(DetachedCriteria criteria)

        {

            return Count(criteria) > 0;

        }

And last, but certainly not least, you'll also want a way to delete entities from the database. How about this:

        /// <summary>

        /// Deletes the given entity

        /// </summary>

        /// <param name="entity"></param>

        public void Delete(T entity)

        {

            Session.Delete(entity);

        }

 

        /// <summary>

        /// Deletes every entity that matches the given criteria

        /// </summary>

        /// <param name="criteria"></param>

        public void Delete(DetachedCriteria criteria)

        {

            // a simple DELETE FROM ... WHERE ... would be much better, but i haven't found

            // a way to do this yet with Criteria. So now it does two roundtrips... one for

            // the query, and one with all the batched delete statements (that is, if you've

            // enabled CUD statement batching

            foreach (T entity in FindAll(criteria))

            {

                Delete(entity);

            }

        }

The first Delete method simply deletes the given entity. The second method probably needs to be explained a bit more. This method receives a query, and it deletes all the items that the query returns. As you can see from the comment, it would be better if it would perform a DELETE FROM ... WHERE instead of fetching the results of the query but i didn't find a way to do that with the criteria API. In a more advanced scenario, this might actually be better than simply issuing a large DELETE statement because you could offer a Delete method which also receives a block of code to execute before or after each delete is executed. Which opens the door to a lot of interesting opportunities.

And that's it basically... I really haven't shown you that much code right? And what does this code offer us? We get create/update/delete functionality, and we also have some nice options for querying our data. And since the Criteria API of NHibernate allows you to create powerful and complex queries, you can use it to create your queries and then you simply pass these criteria to the repository to fetch the data. Just so we're clear on this, you're not just limited to specifying which data you want to retrieve, but you can also tell NHibernate how you want to retrieve it because you can define fetching strategies for each association. This is a tremendously powerful feature which offers you a lot of flexibility in choosing the most optimal data fetching approaches, without being limited to what your DAL supports or having to spend a lot of code on it.

Let's wrap up this post with a small example of how you could use this repository class to execute a query you wrote yourself:

            var criteria = DetachedCriteria.For<ProductCategory>()

                .Add(Expression.Like("Name", "Test%"));

 

            var categories = repository.FindAll(criteria);

As you can see, this is really easy. The only effort basically lies within building the query, so as you're queries become more complex, this effort obviously increases.

If you combine this repository approach with query batching, you end up with an easy-to-use data layer which offers you all the flexibility you could want, while still allowing you to implement specifically tuned solutions to achieve excellent performance. Also, keep in mind that this is only a very basic repository implementation. The Rhino Commons repository implementation offers even more functionality with a couple of extra options to boost performance. I really can't think of a single good reason not to use this approach anymore.

No confidence in Entity Framework?

1 Comment »Written on June 23rd, 2008 by
Categories: Entity Framework

A group of developers have expressed their lack of confidence in Microsoft's upcoming Entity Framework. If you agree with their statements, you can sign the Vote Of No Confidence document here. The document lists some of the biggest problems of the Entity Framework (according to a certain group of developers) so whether you plan to sign it or not, it's definitely an interesting read that's worth checking out.