Showing posts with label LINQ. Show all posts
Showing posts with label LINQ. Show all posts

Thursday, 24 July 2008

WCF In 1 Hour

As part of a product concept study, I needed to prototype a web service to be consumed by a telephony service. This is to simulate a targeted promotion campaign in the telco environment.

The web service allows the consumer to check by keyword for any related promotions. If there are any, the consumer can retrieve the promotion and use the details of the promotion information - e.g. send a SMS or email to the subscriber to notify him/her about the promotion. I had the following in mind about the promotion web service:

  1. to store the promotion data in an xml file
  2. have a boolean service to check whether there are any promotions in the database with matching keyword
  3. have another service method to return the promotion data (an object/composite type) with matching keyword
  4. the promotion information need to be short and concise, making it suitable for sending in a SMS
The XML file I chose to create looked something like this:

 
  New Years Eve Concert
  The biggest event of the year. Artists from all around
  the country.
  Domain
  2008-12-31 20:00:00
  New Year's Eve
 
 
  Batman The Dark Knight
  
   Meet stars in Batman in person - Chritian Bale, Michael Caine.
  
  Star City
  2008-7-30 10:00:00
  Batman, action
 
 
  Jazz Night
  
   Jazz lovers' do not miss this once in a lifetime opportunity.
  
  The Jazz Club
  2008-10-30 21:00:00
  Jazz
 

After a short consideration, I decided to use .NET WCF for the web service and LINQ for the XML querying. The only problem is that I knew nothing about WCF and little about LINQ (I lived in the Java land). But it did not stop me. In fact, it made it more exciting.

The first thing I did was to create a WCF project in VS.2008. VS.2008 made it extremely easy. I then modified the auto-generated service interface and the service implementation files:

The serivce interface is IPromoService

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;

namespace svdemo {
    [ServiceContract]
    public interface IPromoService {
        /// 
        /// Tests whether there are any promotions with matching keyword.
        /// 
        /// <param name="keyword">the keyword to be matched. Case in-sensitive, single word only</param>
        /// <returns>whether there are any promotions with matching keyword</returns>
        [OperationContract]
        bool HasPromo(string keyword);

        /// 
        /// Get the first promotion which matches the given keyword.
        /// 
        /// <param name="keyword">case in-sensitive, single word only.</param>
        /// <returns>The first promotion with matching keyword.</returns>
        [OperationContract]
        PromoInfo GetFirstPromo(string keyword);
    }


    // Use a data contract as illustrated in the sample below to add composite types to service operations.
    [DataContract]
    public class PromoInfo {
        string promoName;
        string promoDescription;
        string promoVenue;
        DateTime promoDateTime;

        public PromoInfo(string name, string desc, string venue, DateTime dt) {
            promoName = name;
            promoDescription = desc;
            promoVenue = venue;
            promoDateTime = dt;
        }
        [DataMember]
        public string PromoName {
            get { return promoName; }
            set { promoName = value; }
        }

        [DataMember]
        public string PromoDescription {
            get { return promoDescription; }
            set { promoDescription = value; }
        }

        [DataMember]
        public string PromoVenue {
            get { return promoVenue; }
            set { promoVenue = value; }
        }

        [DataMember]
        public DateTime PromoDateTime {
            get { return promoDateTime; }
            set { promoDateTime = value; }
        }
    }
}

The service implementation PromoService:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using System.Xml.Linq;

namespace svdemo {
    public class PromoService : IPromoService {
        XDocument doc;
        public PromoService() {
            doc = XDocument.Load(@"D:\projects\SigvalueWS\svdemo\svdemo\data.xml");
        }

        public bool HasPromo(string keyword) {
            if (keyword == null)
                return false;

            string kw = keyword.ToUpper();
            var q = from p in doc.Descendants("Promotion")
                    where ((string) p.Element("Tags")).ToUpper().Contains(kw)
                    select p.Element("Name").Value;
            return (q.Count() > 0);
        }

        public PromoInfo GetFirstPromo(string keyword) {
            if (keyword == null)
                return null;
            string kw = keyword.ToUpper();
            var q = from p in doc.Descendants("Promotion")
                    where ((string) p.Element("Tags")).ToUpper().Contains(kw)
                    select new PromoInfo(p.Element("Name").Value,
                        p.Element("Description").Value,
                        p.Element("Venue").Value,
                        DateTime.Parse( p.Element("DateTime").Value));
            PromoInfo promo = q.First();
            return promo;
        }
    }
}

Running this project (Ctrl+F5) will deploy the web service onto a test web server and the wsdl can be obtained from the url http://localhost:2144/PromoService.svc?wsdl

To test the consumption of the web service, I created a console application project in C#. Adding a service reference (by copying the above URL into it) automatically generates the required web service client entry point code, client proxy, and the DTO PromoInfo class. So consuming the service is a no-brainer:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using svdemoClient.ServiceReference;

namespace svdemoClient {
    class Program {
        static void Main(string[] args) {
            PromoServiceClient client = new PromoServiceClient();
            string keyword="Jazz";
            if (client.HasPromo(keyword)){
                PromoInfo promo=client.GetFirstPromo(keyword);
                Console.WriteLine(
                    String.Format("got {0}, {1}", 
                    promo.PromoName, promo.PromoDateTime)
                );
            }else
                Console.WriteLine("nothing");
            System.Threading.Thread.Sleep(3000);
            client.Close();
        }
    }
}

Although this is my very first attempt to use WCF and LINQ to XML, the whole thing took me an hour and a bit and majority of the time was used in googling on how to use LINQ to XML.

Note: it is better to modify the transport setting to use Basic HTTP instead of TCP to improve interoperability. See here for more details.

Tuesday, 10 June 2008

Entity Framework Bug

In a previous post I blogged about Entity Framework (EF, v1.0 Beta 3) flaws, one of which was about the inconsistency between the entity context and the actual data in the database tables. Here I demonstrate it.

I have two tables: AddressField and Address with a many-to-many relationship as illustrated below.

In the database, the many-to-many relationship is representated as the address_addressfield table. EF correctly generated the entity model, which is shown below. In the entity model, Address has a collection of AddressField and vice versa. To avoid circular loops, lazy loading is used. So far, so good.

Suppose I have 3 addresses a1, a2 and a3, all of which are located in the same country, i.e. all 3 contain the same AddressField. Conversely, the AddressField representing the country should have 3 Addresses in its collection.

If I delete any Address, the corresponding database records in Address and Address_AddressField table should get removed. I would expect the same thing happens in the entity context - i.e. the deleted address should be removed from the AddressField's Address collection.

The following code snippet from my unit test illustrates this:

addressDao.Delete(a1);
Assert.AreEqual(2, addressDao.GetAll().Length);
addressDao.Delete(a2);
addressDao.Delete(a3);
Assert.IsNull(addressDao.GetAll());
Assert.AreEqual(0, 
  afDao.CountAddressReferences(afCountry)); // test fails here
The CountAddressReferences() method implementation is shown below:
public int CountAddressReferences(Address.Domain.AddressField af) {
    int id=(int) af.GetAddressFieldId();
    AddressModel.AddressField entity = addressContext.AddressField
        .Where(t => t.addressFieldId == id).First();
    entity.Address.Load();
    return entity.Address.Count;
}
When I inspect the database tables, everything was OK - the Address table was empty and so was the Address_AddressField table. So the problem is in the entity context - it is not refreshed/updated following the association between Address and AddressField. To get around this problem, I had to detach and reattach the AddressField as shown below (lines 7 to 9):
public int CountAddressReferences(Address.Domain.AddressField af) {
    int id=(int) af.GetAddressFieldId();
    AddressModel.AddressField entity = addressContext.AddressField
        .Where(t => t.addressFieldId == id).First();
    entity.Address.Load();
    Console.WriteLine("before count=" + entity.Address.Count);
    addressContext.Detach(entity);  // extra line
    addressContext.Attach(entity);  // extra line
    entity.Address.Load();          // extra line
    Console.WriteLine("after count=" + entity.Address.Count);
    return entity.Address.Count;
}
Inspecting the console output, it yielded:
before count=3
after count=0

Sunday, 8 June 2008

Entity Framework 1.0 Beta 3

The much anticipated Microsoft ADO.NET Entity Framework (by me at least) has turned out to be a big disappointment.

Entity Framework (EF) is an improvement of the old ADO.NET and supposed to be Microsoft's answer to third-party Object-Relational Mapping (ORM) products, such as Hibernate. However, it totally missed the plot of why people want to have ORM and how these tools are used.

The purpose of ORM is to shield the database details away from the business logic so that the developer does not have to worry about the implementation details of how the data are stored on disk, how the foreign keys relationships are traversed, etc. Developers only need to deal with the domain object model and the domain objects should be modelled as Plain Old .Net Objects (PONO, borrowing from the POJO acronym). This is evident in the tried and true ORM frameworks originated in the Java world: Hibernate, JPA, etc (and to certain extents iBatis SQL Mapper).

The EF way of doing this is to have an Entity Model which can be generated from the database schema. The Entity Model classes inherit from System.Data.Objects.DataClasses.EntityObject, which means that these entity objects are not PONO. So if you want to have a loosely coupled system (between data tier and business tier), then you will need to have a mapping layer between your PONO and these entity objects. This is unnecessary additional work which should have been taken care of by the ORM framework itself.

Another problem with dealing directly with these entity objects is that the developer will have be be aware of the fact that there is a whole new caching layer called entities context which caches the database records in memory in the form of these entity objects. Very often, the developer will have to worry about the synchronisation between the entities context and the actual database (by calling Attach(), Detach() and Load() methods explicitly on the entity model objects) especially when many-to-many relationship associations need to be followed on Delete or Update operations. See my other post on an example of this. Microsoft has also been evangelising the practice of putting these entity objects directly into GUI widgets as their data source. This again creates tight coupling between the data tier and the presentation tier. All these extra data and methods about the entity object life-cycle should be made only visible in the data tier (or DAL, as Microsoft calls it) and nowhere else. That is why PONO should be used throughout the business and presentation tiers, rather than these bloated entity objects. It is disappointing to see that after witnessing all these great ORM examples in the Java world, Microsoft still could not make a decent ORM framework.