Tim Mallalieu's Blog.

Just a PM's random musings on data, models, services...

Look Mom... no XML

Look Mom... no XML

  • Comments 5

We just wrapped up our first iteration of V2.

We are shooting to get another iteration in before PDC and are still working on how we can get early bits out to customers outside of the rhythms of the CTP's and such.

One of the nifty things in our first iteration, though, was some of the work that we did around POCO. There is a lot more to be done with POCO, we need to still deliver lazy load, value objects and more. With these bits, however, it is possible to write basic POCO code now.

For a simple experiment I decided to see if I could use these bits to provide a code-first experience with none of the XML artifacts we have in a typical EF application. The support for doing this without XML artifacts on disk is all implemented via public surface from V1....

Here is the end experience:

            Northwind northwind = ContextFactory.CreateContext<Northwind>(@"Data Source=.\sqlexpress;Initial Catalog=Northwind;Integrated Security=True");

            var prods = from p in northwind.Products where p.UnitPrice > 50 select p;

            foreach (Product p in prods)
            {
                Console.WriteLine(String.Format("[{0}] : {1} - {2}", p.ProductID,p.ProductName, p.UnitPrice ) );
            }

The Context and the Product Class are as follows:

    public class Product
    {
        public int ProductID { get; set; }
        public string ProductName { get; set; }
        public Decimal UnitPrice { get; set; }
    }

    public class Northwind : ObjectContext
    {
        public Northwind(EntityConnection conn) : base(conn) { }

        public ObjectQuery<Product> Products
        {
            get
            {
                if (null == _products)
                {
                    _products = base.CreateQuery<Product>("Northwind.Products");
                }
                return _products;
            }
        }

        private ObjectQuery<Product> _products;
    }

The above code has no requirement (for instances) on the EF.

The ObjectContext is nice to have as the session of interaction with the store... providing the ObjectQuery properties yields a target for formulating the LINQ queries.

The entry point for being able to use this code without artifacts is the ContextFactory. The ContextFactory reflects over the Context type that one wrote by hand. It looks for all properties of type ObjectQuery<T> and then uses these to define an in-memory representation of the models (conceptual, store, mapping) which are then passed to the metadata infrastructure.

    public class ContextFactory
    {
        public static T CreateContext<T>(string connectionString) where T : ObjectContext
        {
            MetadataWorkspace workspace = CreateMetadataWorkspace<T>();
            SqlConnection storeConn = new SqlConnection(connectionString);
            EntityConnection entityConn = new EntityConnection(workspace, storeConn);
            ConstructorInfo contextConstructor = typeof(T).GetConstructor(new Type[] { typeof(EntityConnection)});            
            return (T)contextConstructor.Invoke(new Object[] { entityConn});            
        }


        protected static MetadataWorkspace CreateMetadataWorkspace<T>() where T : ObjectContext
        {
            MetadataWorkspace workspace = new MetadataWorkspace();
            //--- build collections
            EdmItemCollection edmCollection = CreateEdmItemCollection<T>();
            StoreItemCollection storeCollection = CreateStoreItemCollection<T>();
            MappingItemCollection mappingCollection = CreateMappingCollection<T>(edmCollection, storeCollection);
            //--- register collections            
            workspace.RegisterItemCollection(edmCollection);
            workspace.RegisterItemCollection(storeCollection);
            workspace.RegisterItemCollection(mappingCollection);
            workspace.RegisterItemCollection(new ObjectItemCollection());
            workspace.LoadFromAssembly(typeof(T).Assembly);
            //--- done
            return workspace;
        }

        protected static EdmItemCollection CreateEdmItemCollection<T>() where T : ObjectContext
        {
            CodeFirst.CSDL.TSchema schema = new CsdlBuilder().BuildSchema<T>();
            EdmItemCollection edmCollection = new EdmItemCollection(new XmlReader[]{GetXmlStream(typeof(CodeFirst.CSDL.TSchema),schema)});
            return edmCollection;
        }

        protected static StoreItemCollection CreateStoreItemCollection<T>() where T : ObjectContext
        {
            CodeFirst.SSDL.TSchema schema = new SsdlBuilder().BuildSchema<T>();
            StoreItemCollection storeCollection = new StoreItemCollection(new XmlReader[] { GetXmlStream(typeof(CodeFirst.SSDL.TSchema), schema) });
            return storeCollection;
        }

        protected static StorageMappingItemCollection CreateMappingCollection<T>(EdmItemCollection edmCollection,StoreItemCollection storeCollection) where T : ObjectContext
        {
            TMapping schema = new MslBuilder().BuildMapping<T>();
            StorageMappingItemCollection mslCollection = new StorageMappingItemCollection(edmCollection,storeCollection,new XmlReader[] { GetXmlStream(typeof(TMapping), schema) });
            return mslCollection;
        }

        protected static XmlReader GetXmlStream(Type schemaType, Object schema)
        {
            XmlSerializer schemaSerializer = new XmlSerializer(schemaType);
            MemoryStream schemaStream = new MemoryStream();            
            schemaSerializer.Serialize(schemaStream, schema);
            schemaStream.Position = 0;
            XmlReader reader = XmlReader.Create(schemaStream);           
            return reader;
        }
    }

The "builder" classes (SsdlBuilder, CsdlBuilder, MslBuilder) merely use the Type that is supplied to infer a model and return an in-memory representation which can be serialized to our XML representation. We are looking at public mutable API's and code-first surface in V2 which would allow people to do this without having to roll a separate OM.

As soon as we get the latest EF bits out in the wild I can share the sample so that people can play with it - it is hacky PM code but it illustrates the usage of the public surface to have an alternative representation of the requisite metadata. It is also a good exercise to illustrate that for the ORM developer scenario, the EDM and related artifacts can be perceived as implementation detail.

Leave a Comment
  • Please add 5 and 3 and type the answer here:
  • Post
  • PingBack from http://blog.a-foton.ru/2008/07/look-mom-no-xml/

  • Good to see the POCO implementation coming along.  One question though, you said "The above code has no requirement (for instances) on the EF" but is the reverse also true?  For instance, what would happen if I took the properties off the object and wanted EF to work via the fields?

  • @Stefan

    Should work with fields ultimately.

    The code I showed requires my code to infer the metadata from the classes and I just looked at properties.

    The production code shall support POCO in both the context of fields or properties.

  • Hi,

    How do you see the migration working from V1? For example, we have managed to abstract away via a repository pattern the dependency on the EF's object context. However, we cannoot easily avoid the dependencies on EntityObject, EntityReference and EntityCollection, which surface themselves in our WCF contracts. Any ideas/thinking around the 'Upgrade to POCO' from v1?

  • Looks great, a lot closer to what I'm looking for from something like Entity Framework. What I'm interesting in seeing is how POCO handle complex mappings such as inheritence hierachies and whether we can "inject" our own inference systems.

    Cheers

Page 1 of 1 (5 items)