7

I have a huge XML document which I have to parse it to generate domain objects.

Because the document is huge, i don't want to parse it every time a user requests it, but only first time, then saving all the objects into cache.

public List<Product> GetXMLProducts()
{
    if (HttpRuntime.Cache.Get("ProductsXML") != null)
    {
        return (List<Product>)(HttpRuntime.Cache.Get("ProductsXML"));
    }

    string xmlPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Content\\Products.xml");
    XmlReader reader = XmlReader.Create(xmlPath);
    XDocument doc = XDocument.Load(reader);

    List<Product> productsList = new List<Product>();
    // Parsing the products element

    HttpRuntime.Cache.Insert("ProductsXML", productsList);
    return productsList;
}

How is the best way i can make this function working in singleton and be thread-safe?

Fixed the saving an object into cache method (was a copy-paste mistake)

Catalin
  • 11,503
  • 19
  • 74
  • 147
  • If you call it from a page that use session, and not from any other thread then the session locks all read/write and make it thread safe. – Aristos Jul 16 '12 at 10:44

2 Answers2

12

Create a Lazy static and keep in memory for the lifetime of the application. And don't forget the "true" part, that's what makes it thread safe.

public static readonly Lazy<List<Product>> _product = new Lazy<List<Products>>(() => GetProducts(), true);

To add this to your model, just make it private and return _product.Value;

public MyModel
{
    ... bunch of methods/properties

    private static readonly Lazy<List<Product>> _products = new Lazy<List<Products>>(() => GetProducts(), true);

    private static List<Product> GetProducts()
    {
        return DsLayer.GetProducts();

    }

    public List<Product> Products { get { return _products.Value; } }
}

To create a singleton using Lazy<>, use this pattern.

public MyClass
{
    private static readonly Lazy<MyClass> _myClass = new Lazy<MyClass>(() => new MyClass(), true);

    private MyClass(){}

    public static MyClass Instance { get { return _myClass.Value; } }
}

Update/Edit:

Another lazy pattern for use within a context (i.e. Session)

Some Model that is saved in Session:

public MyModel
{
   private List<Product> _currentProducts = null;
   public List<Product> CurrentProducts 
   {
      get
      {
         return this._currentProducts ?? (_currentProducts = ProductDataLayer.GetProducts(this.CurrentCustomer));
      }
   }
}
Chris Gessler
  • 22,727
  • 7
  • 57
  • 83
  • Never thought i will ever use Lazy<> class in the application:) This looks like what i need. Only one question: can i use the Lazy pattern in a "Dictionary like" situation? For example, i have multiple XML files for multiple users, and i want to cache List for each user separately. – Catalin Jul 16 '12 at 11:08
  • Sure can, but you might not want a static at that point. You can lazy load the products per user and keep it in session. – Chris Gessler Jul 16 '12 at 11:13
2

For the record - a lazy static (Chris Gessler's answer, which gets a +1 from me) is a good solution; in this case because you always want the data in memory. This answer looks specifically at the use of Cache (addressing your somewhat confusing code) and bringing another way to initialise a website.

The traditional way to do this is in an Application_Start handler in Global.asax(.cs). However I'm going to show another nice way:

Add the WebActivator package to your website with Nuget.

Then add the following code to a new .cs file in a new folder you create in your project, called App_Start:

[assembly: WebActivator.PreApplicationStartMethod(typeof(*your_namespace*.MyInit), 
  "Start", Order = 0)]
namespace *your_namespace*
{

  public static class MyInit {
    public static void Start() {
      string xmlPath = HostingEnvironment.MapPath("~/Content/Products.xml");
      using(var reader = XmlReader.Create(xmlPath)) {
       XDocument doc = XDocument.Load(reader);  

       List<Product> productsList = new List<Product>();  
       // Parsing the products element

       HttpRuntime.Cache.Insert("ProductsXML", productsList); 
      }
    }
  }
}

Note - the correct use of Cache.Insert, and a using for the stream reader. Your original code has other strange logic and semantic errors - such trying to assign to the result of a function and returning the value from the cache if it's null.

Note also you need to bring some namespaces in the above code - and look out for *your_namespace*.

Andras Zoltan
  • 41,961
  • 13
  • 104
  • 160
  • Didn't even know WebActivator existed. Thanks! Don't forget to mention Cache Dependency, pretty cool stuff, in case the file changes mid stream, auto reloads I think... – Chris Gessler Jul 16 '12 at 10:56
  • This part of code is executed only once, first time when the Application starts? This is a very nice approach, I can find it useful in many stations! The only down side is that it will read and cache everything even if no user will access that data. – Catalin Jul 16 '12 at 11:12
  • @RaraituL - That's not a downside - the data is loaded and ready to go so the user doesn't have to wait. It's simply a trade off, load it now, or load it later. – Chris Gessler Jul 16 '12 at 11:27
  • I think what RaraituL was saying is that if you don't know for certain that you're going to need the data at some point down the road then you may be wasting effort as the data was needlessly loaded. Of course this is always a consideration with eager loading in general, and more so when the data being loaded and/or cached takes a relatively long time to load and/or consumes a lot of memory in cache. However these considerations seem rather moot as memory is cheaper than filling up a tank of gas these days. – Jagd Jul 17 '12 at 17:20