Showing posts with label C#. Show all posts
Showing posts with label C#. Show all posts

Tuesday, 20 January 2009

LINQ to Entities and the “Collection was modified” exception – A solution!

I’ve found that something that is a common mistake when dealing with collections.  Say I want to go through a list of objects and delete every one that matches a certain criteria.  The simplest code would be something like this:

foreach (Item item in _Entities.Item)
{
    if (/* criteria */)
    {
        _Entities.Item.Remove(item);
    }
}

But that’s not going to work.  When the Remove method is run you will get an exception:

{"Collection was modified; enumeration operation may not execute."}    System.Exception {System.InvalidOperationException}

Ok, so if you think about it it makes sense really, you’re trying to remove an item from the enumeration that you’re currently enumerating through.  But what’s the solution?

Turns out there are many ways you can tackle this problem.  One way is to write each of your delete segments so that you build a list of items to delete first and then delete them from the master list.  It works but it’s a lot of extra code.  Another option is to turn the Colleciton into an array before looping through it and deleting the items.

I like this solution better:

public void RemoveIf<T>(ICollection<T> collection, Predicate<T> match)
{
    List<T> removed = new List<T>();
    foreach (T item in collection)
    {
        if (match(item))
        {
            removed.Add(item); 
        }
    }
 
    foreach (T item in removed)
    {
        collection.Remove(item);
    }
 
    removed.Clear();
}

I’ve written my own helper method to remove an item if it meets a certain criteria.  The helper method will take a Predicate (the same as the Collection.Exists method) and if an items in the collection match the Predicate it will add them to a list, then cycle through the list and remove them.

To implement it use code similar to the following:

RemoveIf(_Entities.Item, delegate(Item i) { return /* criteria */; });

Which is the same style of code you would use if you were trying to implement the Collection.Exists method.  If you wanted to delete all the items from one list if they exist in another, you can of course chain the Exists method into the RemoveIf helper also like so:

RemoveIf(_Entities.Item, delegate(Item itemRemove) 
{
    return !otherList.Exists(delegate(Item itemSearch) 
    { 
        return itemSearch.CustomerID == itemRemove.CustomerID; 
    });
});

But the downside is that the code will get a little confusing for people who haven’t used delegates much, or at all.

Friday, 9 January 2009

Setting up a constant class

In my code I want to write Constant.Type.Value throughout  my code, well I’ve found a nice way to do this in C#.  All my constants are going sit in a single class and I can create static structures that hold the constant strings links so:

/// <summary>
/// This class holds all the constants for the Lynx application.
/// </summary>
public static class Constant
{
    /// <summary>
    /// Usage constants reference the rfUsage.Name field.
    /// </summary>
    public struct Usage
    {
        public const string Vendor = "Vendor";
        public const string Customer = "Customer";
        public const string ItemSize = "ItemSize";
        public const string Inventory = "Inventory";
    }
 
    /// <summary>
    /// Type contants reference the rfType.Name field
    /// </summary>
    public struct Type
    {
        public const string Weight = "Weight";
        public const string Thickness = "Thickness";
    }
 
    /// <summary>
    /// Unit of measure constants reference the rfUnitOnMeasure.Name field
    /// </summary>
    public struct UnitOfMeasure
    {
        public const string Grams = "Grams";
        public const string Millimeters = "Millimeters";
        public const string Box = "Box";
        public const string Bundle = "Bundle";
    }
}

And I make calls to the constants list like so:

Name = Constant.UnitOfMeasure.Grams

Friday, 19 December 2008

Testable Left Outer Join in LINQ to Entities

Recently I posted on Testable LINQ to Entities using the repository model and doing table joins using your query items.  I’ve found that while this works great for inner joined table queries it doesn’t work for left outer joins, in fact doing a left outer join in LINQ to Entities is quiet hard entirely.

So in my example, here is the SQL used to retrieve the data.

SELECT * FROM
Item
    LEFT OUTER JOIN ItemLocation il
        on Item.ItemCodeID = il.ItemCodeID
    LEFT OUTER JOIN WarehouseLocation wl
        on il.WarehouseLocationID = wl.WarehouseLocationID
    LEFT OUTER JOIN Warehouse w
        on wl.WarehouseID = w.WarehouseID
    LEFT OUTER JOIN Site s
        on w.SiteID = s.SiteID

In LINQ I can do this far more simply:

from item in _ItemData
select item

But when I try and look at the resulting item.ItemLocation it returns an empty list.  When using LINQ to Entities it will only load the data model for items you tell it to load for, in this case I asked for Item, but not all the ItemLocations as well.  This is shown in the screenshot below, the array of item locations has no items even though they are in the database.

image

I want the item to return even though I don’t necessarily have any locations, and I want to be able to get the site.

Well here is how you do a left outer join in LINQ to Entities:

var results = (from item in _DataSource
               where item.Name.Equals(name)
               // Left outer join into the item locations, warehouses and sites to calculate the data.
               let itemLoc = (from itemLocLeftOuter in item.ItemLocation
                              select new
                              {
                                  itemLocLeftOuter,
                                  itemLocLeftOuter.WarehouseLocation,
                                  itemLocLeftOuter.WarehouseLocation.Warehouse,
                                  itemLocLeftOuter.WarehouseLocation.Warehouse.Site
                              })
               select new
               {
                   item,
                   item.Class,
                   item.ItemLocation,
                   itemLoc
               }).FirstOrDefault();
 
Item item = results.item;

I have to join into the ItemLocations because for each item there can be many locations.  But selecting all the data I need, including joined data, my resulting item will have all it’s dependencies loaded from the database.  The screenshot of the watch below shows that my item.ItemLocation holds all the joined items now.

image 

As with my previous post on testable LINQ to Entities, this code is completely testable and works very well within the repository pattern.

Monday, 1 December 2008

Unit Testing the UpdateModel method in ASP.NET MVC by Faking the Controller Context

One thing that’s not immediately obvious is that the UpdateModel method in ASP.NET MVC Beta requires a controller context to work.  In fact if you canll UpdateModel without controller context then you’re going to get an error like this:

threw exception:  System.ArgumentNullException: Value cannot be null.  Parameter name: controllerContext.

A quick search found that you need to fake the controller context to be able to unit test with UpdateModel, something Scott Gu doesn’t cover in his blog.  Never mind, Scott Hanselman to the rescue!  You will need to fake your controller context to get this to work correctly.  On Scott Hansleman’s blog he creates a MvcMockHelper class designed to mock certain aspects of the MVC environment, including the controller context.

This is the MvcMockHelpers class from Scott’s blog.

using System;
using System.Web;
using Rhino.Mocks;
using System.Text.RegularExpressions;
using System.IO;
using System.Collections.Specialized;
using System.Web.Mvc;
using System.Web.Routing;
 
namespace UnitTests
{
    public static class MvcMockHelpers
    {
        public static HttpContextBase FakeHttpContext(this MockRepository mocks)
        {
            HttpContextBase context = mocks.PartialMock<httpcontextbase>();
            HttpRequestBase request = mocks.PartialMock<httprequestbase>();
            HttpResponseBase response = mocks.PartialMock<httpresponsebase>();
            HttpSessionStateBase session = mocks.PartialMock<httpsessionstatebase>();
            HttpServerUtilityBase server = mocks.PartialMock<httpserverutilitybase>();
 
            SetupResult.For(context.Request).Return(request);
            SetupResult.For(context.Response).Return(response);
            SetupResult.For(context.Session).Return(session);
            SetupResult.For(context.Server).Return(server);
 
            mocks.Replay(context);
            return context;
        }
 
        public static HttpContextBase FakeHttpContext(this MockRepository mocks, string url)
        {
            HttpContextBase context = FakeHttpContext(mocks);
            context.Request.SetupRequestUrl(url);
            return context;
        }
 
        public static void SetFakeControllerContext(this MockRepository mocks, Controller controller)
        {
            var httpContext = mocks.FakeHttpContext();
            ControllerContext context = new ControllerContext(new RequestContext(httpContext, new RouteData()), controller);
            controller.ControllerContext = context;
        }
 
        static string GetUrlFileName(string url)
        {
            if (url.Contains("?"))
                return url.Substring(0, url.IndexOf("?"));
            else
                return url;
        }
 
        static NameValueCollection GetQueryStringParameters(string url)
        {
            if (url.Contains("?"))
            {
                NameValueCollection parameters = new NameValueCollection();
 
                string[] parts = url.Split("?".ToCharArray());
                string[] keys = parts[1].Split("&".ToCharArray());
 
                foreach (string key in keys)
                {
                    string[] part = key.Split("=".ToCharArray());
                    parameters.Add(part[0], part[1]);
                }
 
                return parameters;
            }
            else
            {
                return null;
            }
        }
 
        public static void SetHttpMethodResult(this HttpRequestBase request, string httpMethod)
        {
            SetupResult.For(request.HttpMethod).Return(httpMethod);
        }
 
        public static void SetupRequestUrl(this HttpRequestBase request, string url)
        {
            if (url == null)
                throw new ArgumentNullException("url");
 
            if (!url.StartsWith("~/"))
                throw new ArgumentException("Sorry, we expect a virtual url starting with \"~/\".");
 
            SetupResult.For(request.QueryString).Return(GetQueryStringParameters(url));
            SetupResult.For(request.AppRelativeCurrentExecutionFilePath).Return(GetUrlFileName(url));
            SetupResult.For(request.PathInfo).Return(string.Empty);
        }
       
    }
}

Now all I need to do is

_Mocks = new MockRepository();
_ItemRepository = _Mocks.StrictMock<IItemRepository>();
 
SetupTestData(_Mocks, _ItemRepository);
_Target = new StockItemMasterController(_ItemRepository);
 
MvcMockHelpers.SetFakeControllerContext(_Mocks, _Target);

Set the controller context to the faked context and my tests will all start magically working.