Look Mom... no XML
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.