Entity Framework, IronPython and PODO’s – Can it be done? - Part 3
Before we dive into proxies when using POCO in the Entity Framework lets have a quick recap on where we left off in part 2.0.
- Entity Framework will load PODO’s (plain old DLR Objects) fine as long as:
- No primitives other than strings are used for fields and properties
- Eager Loading works when ObjectQuery[T].Include() is used.
- TSQL profiling confirms this.
- Lazy Loading does not appear to work at all
- Type names of materialized entities are not what are expected for runtime generated proxies.
Let’s talk about proxies a little. I wont delve too deep only enough that’s relevant to the problem at hand. Entity Framework creates proxies at runtime for our POCO entities, essentially so the framework can know the entities a vice versa, they maintain the glue so to speak. The glue is there to enable lazy loading and change tracking specifically. If you want to know more about proxies I suggest reading this post from the ADO.Net team blog. Figure 1.0 depicts debug data of a materialized proxy Entity in a C# library. Notice the unusual name of the proxy Entity in the watch window in figure 1.0 – the Entity Framework generates proxy types in the System.Data.Entity.DynamicProxies namespace.
Figure 1.0
At this point, we know we are not seeing proxy type names come back when we ask our PODO for it’s full name and we are not getting any love from Lazy Loading at all. Our materialized Entities are indeed the type as engineered via __clrtype__ and we saw this when querying for the namespace of the Entity from IronPython in part 2.0.
In a nutshell, the Entity Framework is querying the database and materializing our PODO’s, however given the circumstances its reasonable to argue that no proxying is taking place and hence no lazy loading. Let’s put that theory to the test just a little more shall we! When proxies are employed in the Entity Framework there are two basic usages; proxies that support either Lazy Loading only or Change Tracking with Lazy Loading, the latter option requires all mapped properties be declared virtual and be public or protected. In Python class properties, functions and methods are considered to be as good as virtual, so we will just assume for the moment that this clears the way for the Entity Framework to work with our PODO’s as any other normal compliant class that is proxyable (of course there is no such word).
Since we are getting no proxy generation with our PODO’s then we would equally expect change tracking to fail would we not? Note in figure 1.0 that a (C#) proxy when examined has a reference to the Entity Tracker and other Entity Framework plumbing required to manage it, which we would expect to be missing if our theory about what’s happening to our PODO’s is correct. Let’s now put the whole proxy and change tracking question to rest.
def IsProxy(theType): ctxType = ObjectContext.GetObjectType(theType.GetType()) notNull = theType != None isCtxObj = ctxType != theType.GetType(); print “\tThis entity is a %(podo)s“ \ %{‘podo‘:theType.GetType()} print “\tIt’s type in the ObjectContext is %(ctxType)s“ \ %{‘ctxType‘:ctxType} return notNull and isCtxObj def IsChangeTracking(podo): state = ctx.ObjectStateManager.GetObjectStateEntry(podo).State print “\tIt’s state is: %(state)s“ % {‘state‘:state} return state == System.Data.EntityState.Modified ctx.ProxyCreationEnabled = True parents = ctx.ParentRecords\ .Where(“it.ParentID != ‘bob’“)\ .Include(“ChildRecords“) for parent in parents: print parent.ParentID parent.ParentName = “change tracking works nonetheless“ isProxy = IsProxy(parent).ToString() isChange = IsChangeTracking(parent).ToString() print “\tIt is %(proxy)s that this PODO is a proxy“ \ %{‘proxy‘:isProxy} print “\tIt is %(change)s that this PODO supports “ \ “proxy style change tracking“ %{‘change‘:isChange} if parent.ChildRecords != None: for child in parent.ChildRecords: print “\t\tChildID is : “ + child.ChildID ctx.SaveChanges()
We have added two methods, one to check if an Entity is a proxy and another to verify if change tracking is working via our proxy. Running this code produces the following TSQL, subsequent changes to the database and debug window output in NetBeans.
exec sp_executesql N‘update [dbo].[ParentRecords] set [ParentName] = @0 where ([ParentID] = @1) ‘,N‘@0 nvarchar(50),@1 char(5)’,@0=N‘change tracking’,@1=‘FGHIJ’
An interesting set of results. As suspected we have no proxies with Lazy Loading or Change tracking ability but we do have change tracking nonetheless when we get a diff from the ObjectContext. The call to .SaveChanges() on the ObjectContext is still persisting the change to the database regardless of the absence of the proxy creation.
To summarise, we now have established that loading data into PODO’s works despite producing no proxy. We can eager load only and we still have the rather large problem with natives that are not strings; this still leaves us with the little problem of this ugly message.
One thing that I meant to mention in one of the earlier posts is that clrtype.py does not support the use of iterables and IronPython classes in the @Accepts and @Returns decorators out of the box. Thanks to help given by Lukas Cenovsky on the IronPython mailing list who made the required changes to the validate_clr_types method in clrtype.py to get over this hurdle.
So now it’s time I thought to jump into VS 2010 and see what we can discover by checking out the dump file created by the IPY.exe crash. And that is where part 4.0 of the series will start. Until then.
1 commentEntity Framework, IronPython and PODO’s – Can it be done? - Part 2
Part 1.0 was setting up for the idea and now in part 2.0 we start to flesh out all the moving parts that would be required to work with IronPython and the Entity Framework and furthermore, do so in such a way that is parallel to how we expect to do so in a statically typed language such as C#. What we are going to aim for is POCO classes, a custom Object Context and Code Only mapping.
Before we move on a small piece of housekeeping. When you see [T] in this post it is the IronPython version of <T> in the C# world. For example these are equivalent:
ObjectQuery<T>
//and
ObjectQuery[T]
PODO / POCO Entities
I gave a bit of a sneak peak at this in Part 1.0 but it’s worth repeating here. Using Northwind (of course – it’s law after all) we could start building out our business Entities for Customers and Orders. Let’s start by keeping the members of our entities to a minimum for brevity sake:
Figure 1.0
Simple enough, a couple of classes with members types that include System.String, System.DateTime and System.Int32, a list of Orders in the Customer Entity and a single entity reference to Customer from the Order Entity. I posted a Customer Entity in part 1.0 but let’s list both Customer and Order here:
Listing 1.0
import clrtype import clr from System import * from System.Collections.Generic import * from Entities2.Order import Order class Customer(object): __metaclass__ = clrtype.ClrClass _clrnamespace = “EntityFramework.Podo“ _clrfields = { “_customerId“:str, “_companyName“:str, “_contactName“:str, “_orders“:clr.GetClrType(ICollection) .MakeGenericType(Order) } @property @clrtype.accepts() @clrtype.returns(clr.GetClrType(str)) def CustomerID(self): return self._customerId @CustomerID.setter @clrtype.accepts(clr.GetClrType(str)) @clrtype.returns() def CustomerID(self, value): self._customerId = value @property @clrtype.accepts() @clrtype.returns(str) def CompanyName(self): return self._companyName @CompanyName.setter @clrtype.accepts(str) @clrtype.returns() def CompanyName(self, value): self._companyName = value @property @clrtype.accepts() @clrtype.returns(str) def ContactName(self): return self._contactName @ContactName.setter @clrtype.accepts(str) @clrtype.returns() def ContactName(self, value): self._contactName = value @property @clrtype.accepts() @clrtype.returns(clr.GetClrType(ICollection) .MakeGenericType(Order)) def Orders(self): return self._orders @Orders.setter @clrtype.accepts(clr.GetClrType(ICollection) .MakeGenericType(Order)) @clrtype.returns() def Orders(self, value): self._orders = value
import clrtype import clr from System import * from Entities.Customer import Customer class Order(object): __metaclass__ = clrtype.ClrClass _clrnamespace = “EntityFramework.Podo“ _clrfields = { “_customerId“:str, “_orderId“:int, “_orderDate“:clr.GetClrType(DateTime), “_customer“:clr.GetClrType(Customer) } @property @clrtype.accepts() @clrtype.returns(int) def OrderID(self): return self._orderId @OrderID.setter @clrtype.accepts(int) @clrtype.returns() def OrderID(self, value): self._orderId = value @property @clrtype.accepts() @clrtype.returns(clr.GetClrType(DateTime)) def OrderDate(self): return self._orderDate @OrderDate.setter @clrtype.accepts(clr.GetClrType(DateTime)) @clrtype.returns() def OrderDate(self, value): self._orderDate = value @property @clrtype.accepts() @clrtype.returns(str) def CustomerID(self): return self._customerId @CustomerID.setter @clrtype.accepts(str) @clrtype.returns() def CustomerID(self, value): self._customerId = value @property @clrtype.accepts() @clrtype.returns(clr.GetClrType(Customer)) def Customer(self): return self._customer @Customer.setter @clrtype.accepts(clr.GetClrType(Customer)) @clrtype.returns() def Customer(self, value): self._customer = value
Notice the OrderDate property is specifying the System.DateTime data type? Let’s assume the above code will just work; if I run this ad-hoc proof of concept code below in Listing 2.0, I don’t encounter any problems and the output certainly seems to suggest that this should hang together ok when we hook our Customer and Order entities up via mapping to the Entity Framework.
Listing 2.0
import clrtype import clr from System import * class ClrNatives(object): __metaclass__ = clrtype.ClrClass _clrfields = { “_clrDate“:clr.GetClrType(DateTime), “_clrInt32“:clr.GetClrType(Int32) } @property @clrtype.accepts() @clrtype.returns(clr.GetClrType(Int32)) def ClrInt32(self): print ‘got into accessor‘ return self._clrInt32 @ClrInt32.setter @clrtype.accepts(clr.GetClrType(Int32)) @clrtype.returns() def ClrInt32(self, value): print “got into the mutator“ self._clrInt32 = value @property @clrtype.accepts() @clrtype.returns(clr.GetClrType(DateTime)) def ClrDate(self): print ‘got into accessor‘ return self._clrDate @ClrDate.setter @clrtype.accepts(clr.GetClrType(DateTime)) @clrtype.returns() def ClrDate(self, value): print “got into the mutator“ self._clrDate = value c = ClrNatives() print c.ClrDate.GetType() print c.ClrInt32.GetType() c.ClrInt32 = 10 c.ClrDate = DateTime.Now print c.ClrInt32 print c.ClrDate
Figure 2.0
Figure 2.0 below depicts the output from the code in Listing 2.0 and shows that the underlying CLR type is indeed having the correct types created for it’s backing fields and associated properties.
The next piece of the puzzle that we would require would be the Entity Framework ObjectContext. Based on the recommended C# pattern for POCO implementation, we would do the following:
Listing 3.0
class NorthwindEntities(ObjectContext): __metaclass__ = clrtype.ClrClass _clrnamespace = “EntityFramework.Podo“ _clrfields = { “_customers“:clr.GetClrType(IObjectSet) .MakeGenericType((Customer)), “_orders“:clr.GetClrType(IObjectSet) .MakeGenericType((Order)) } def __init__(self):pass def __init__(self, connectionString): self._customers = self.CreateObjectSet[Customer]() self._orders = self.CreateObjectSet[Order]() self.ContextOptions.LazyLoadingEnabled = True; @property @clrtype.accepts() @clrtype.returns(clr.GetClrType(IObjectSet) .MakeGenericType((Customer))) def Customers(self): return self._customers @Customers.setter @clrtype.accepts(clr.GetClrType(IObjectSet) .MakeGenericType((Customer))) @clrtype.returns() def Customers(self, value): self._customers = value @property @clrtype.accepts() @clrtype.returns(clr.GetClrType(IObjectSet) .MakeGenericType((Order))) def Orders(self): return self._orders @Orders.setter @clrtype.accepts(clr.GetClrType(IObjectSet) .MakeGenericType((Order))) @clrtype.returns() def Orders(self, value): self._orders = value
Creating an ObjectContext in IronPython was straightforward, inherit from ObjectContext and create IObjectSet[T] backing fields and properties for the entities managed by the context. As I said earlier, this specialised IronPython ObjectContext follows the pattern set out for POCO development where a roughly equivalent C# version would be as follows:
public class NorthwindEntities : ObjectContext { private IObjectSet<Customer> _customers; private IObjectSet<OrderLine> _orderLines; private IObjectSet<Order> _orders; public NorthwindEntities() : base("name=NorthwindEntities", "NorthwindEntities") { } public NorthwindEntities(string connectionString) : base(connectionString, "NorthwindEntities") { _customers = CreateObjectSet<Customer>(); _orders = CreateObjectSet<Order>(); _orderLines = CreateObjectSet<OrderLine>(); } public IObjectSet<Customer> Customers { get { return _customers; } } public IObjectSet<OrderLine> OrderLines { get { return _orderLines; } } public IObjectSet<Order> Orders { get { return _orders; } } }
The Mapping Problem
My original goal was to provide all the moving pieces in IronPython code, even the mapping. Entity Framework 4.0 has a code only option with mapping (no XML required) and that made sense – considering there would be no need to bring any managed C# libraries into the mix. Unfortunately however it’s not currently possible to work with Expression[Func[T]] in IronPython and so the code only mapping is just not going to happen for the moment. Hang on you say – what about all the use of Expressions in the Entity Framework itself? Good question. We are not going to use them! The Entity Framework allows the use of Query Builder methods that take strings as arguments, producing ObjectQuery[T] where T is the entity type.
Lets run It!
At this point we have all the constituent pieces required, all that’s left is to see is whether or not it actually works. We start with some simple client code (running in NetBeans Python Edition) to simply load up some Customers:
mappingPart = “metadata=../Northwind.csdl|../Northwind.ssdl|../Northwind.“ + “msl;“ providerPart = “provider=System.Data.SqlClient;provider connection “ dbPart1 = “string=\”Data Source=BOOMER\\BOOM09;Initial Catalog=Northwind;“ dbPart2 = “Integrated Security=True;MultipleActiveResultSets=True\”“ cn = mappingPart + providerPart + dbPart1 +dbPart2 ctx = NorthwindEntities(cn) custs = ctx.Customers.Where(“it.CustomerID > ‘C’“) for cust in custs: custOutput = “The Customer ID is %(id)s and Company Name is %(name)s“ custOutTuple = {‘id‘: cust.CustomerID, ‘name‘: cust.CompanyName} print custOutput % custOutTuple
Before we look at the result of running this code its worth noting one thing about this code above, the mapping portion of the connection string is pointing to a location on disk that specifies where to find the Entity Framework Mapping files. Remember that I said that we were unsuccessful in getting the code only mapping working due the issue with Expression Trees and IronPython. Entity Framework 4.0 does allow us produce mapping files with no code generated entities; this can be achieved in a C# or VB.NET library. Simply create your model in the VS designer as usual, make sure that it maps to your POCO’s in terms of structure, then build the project, specifying to export the mapping files rather than embedded them in the assembly.
Figure 3.0
In Visual Studio 2010 click on the .edmx file in Solution Explorer and remove the Custom Tool in the properties window so it becomes blank. Next, make the Copy to Output Director property either ‘Copy Always’ or ‘Copy if Newer’. Navigate to the output directory after running a build and you should see something similar to these three files (names will vary of course). These three files constitute the mapping.
Figure 4.0
Celebrations? Cigars?
So what output if any? Time to light up a cigar in celebration? What does the output window in NetBeans show us (see figure 4.0)?
Figure 5.0
Looks like we have been successful in getting the Entity Framework to speak directly to our IronPython class doesn’t it. Upon a quick check in SQL Profiler I could confirm that indeed the Customers were being loaded by our IronPython ObjectContext.
SELECT [Extent1].[CustomerID] AS [CustomerID], [Extent1].[CompanyName] AS [CompanyName], [Extent1].[ContactName] AS [ContactName] FROM [dbo].[Customers] AS [Extent1] WHERE [Extent1].[CustomerID] > ‘C’
So after some fist pumping and jig dancing, I decided I should see how deep it really went. First thing to check for my mind was loading an entity graph deeper than one entity and also doing that with lazy and eager loading.
Lazy Load the Entity Graph
By making a small change to our code that consumes our IronPython ObjectContext class and iterating over the Order collection present in each Customer record, we would hope to see the Lazy Loading kick in? Drum Roll. Great tension in the air as we press F6 (don’t shoot me it’s NetBeans remember)……..
for cust in custs: print cust.CustomerID for order in cust.Orders: print OrderID
And sadly folks it blows up. What happened exactly? Interestingly enough, all the errors I had encountered up till this point when writing the proof of concept code, were related to mapping mistakes between the PODO’s (IronPython Entities) and my .csdl / .msl and .ssdl mapping files and therefore not insurmountable. This error was more than troubling and had me thinking we had struck a real road block that might not be movable. So how did the error manifest itself?
Figure 6.0
Unfortunately something about iterating the Customer Orders collection was blowing IPY.exe up and producing a crash dump file. I’m starting to believe in destiny just about now because just a week ago I happened across Scott Hanselman’s channel 9 interview of Tess Ferranadez who was demonstrating the new dump file debugging capabilities of VS 2010. Looks like I’m about to be checking that feature out a little earlier than I originally imagined.
Let’s hold that thought for a moment and revisit the code briefly before we embark on our voyage into dump files! The most obvious three things I saw as worthy for consideration as to the failure were, the iteration of Orders, something about the nature of the Order Entity itself or lazy loading was a goner? I started to examine the differences between the Customer and Order entities to look for differences that might proffer some clues. Immediately it was clear that the Customer entity only had strings for properties, surely that cant be it I thought, but decided to remove it from the list of possibilities immediately nonetheless. So after quickly testing the Order entity on it’s own (as the aggregate root) it became clear that indeed there might be a problem with primitives translating from Entity Framework over to the type declarations made in the IronPython classes.
orders = ctx.Orders.Where(“it.CustomerID > ‘C’“) for order in Orders: print Order.CustomerID
Unfortunately where this was successful with Customer (all strings), it was not so with the Order entity which contained integers and Dates amongst them. Incredulous and unconvinced I decided to create a pure ad-hoc table with a mixture of primitives other than strings and this also confirmed my findings. Even when more exotic (tongue in cheek) types like DateTime were removed from the equation and only Int32 and String were present, the problem remained present.
A sanity check was required at this point. I recalled that I had a pre-existing proof of concept to show that specifying CLR types to the _clrfields and decorators were indeed working in other un-persisted entities, furthermore the IronPython class deriving from ObjectContext itself was example of this (see Listing 2.0 and 3.0). So what on earth was this snag? Using Python either int or clr.GetClrType(Int32) had the same negative effect, the Entity Framework didn’t seem to like either of them. This left me scratching a big whole in my head.
In an attempt to be complete, I still wanted to test lazy and eager loading. Let’s begin by moving our attention to a different Entity Model based on two tables where all the fields were typed as strings, that way I thought I could have a go at eager loading using the .Include(“Load.Path”) extension method which takes a string and returns an ObjectQuery[T] (<T> for C#). Here is the new model and query code.
parents = ctx.ParentRecords.Where(“it.ParentID != ‘bob’“).Include(“ChildRecords“) for parent in parents: print parent.ParentID for child in parent.ChildRecords: print “\t“ + child.ChildID
Success! Judging from the output from this query and iteration, eager loading does appear to work as expected:
Figure 7.0
The SQL Profiler confirms that eager loading has in fact worked as expected. I dislike the SQL that Entity Framework produces – but that’s a subject for a very different post, I just suddenly felt the need for a mini rant.
SELECT [Project1].[C1] AS [C1], [Project1].[ParentID] AS [ParentID], [Project1].[ParentName] AS [ParentName], [Project1].[C2] AS [C2], [Project1].[ChildID] AS [ChildID], [Project1].[ParentID1] AS [ParentID1], [Project1].[ChildName] AS [ChildName] FROM ( SELECT [Extent1].[ParentID] AS [ParentID], [Extent1].[ParentName] AS [ParentName], 1 AS [C1], [Extent2].[ChildID] AS [ChildID], [Extent2].[ParentID] AS [ParentID1], [Extent2].[ChildName] AS [ChildName], CASE WHEN ([Extent2].[ChildID] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C2] FROM [dbo].[ParentRecords] AS [Extent1] LEFT OUTER JOIN [dbo].[ChildRecords] AS [Extent2] ON [Extent1].[ParentID] = [Extent2].[ParentID] WHERE [Extent1].[ParentID] <> ‘bob’ ) AS [Project1] ORDER BY [Project1].[ParentID] ASC, [Project1].[C2] ASC
How about Lazy Loading? Remember we set the Lazy Loading property of the ObjectContext options in the __init__ (see Listing 3.0 above). So let’s check and leave out the .Include() this time:
parents = ctx.ParentRecords.Where(“it.ParentID != ‘bob’“) for parent in parents: print parent.GetType().FullName print parent.ParentID for child in parent.ChildRecords: print “\t“ + child.ChildID
BOOM…no joy here! Let’s have a look at the output and see what went wrong this time.
Figure 8.0
We can see here that we are able to retrieve our ParentRecords without any problem however when ask for the ChildRecords for each Parent then we get a NoneType error, but we know for a fact that record ABCDE indeed has children as exhibited by our eager loading example. I feel myself about to descend down yet another rabbit hole. Lets make a quick check and see if a null check gets us past this point.
for parent in parents: print parent.GetType().FullName print parent.ParentID if parent.ChildRecords != None: for child in parent.ChildRecords: print “\t“ + child.ChildID
Ok, so no error this time but still no lazy load, but at least are we getting closer to knowing the theory around the primitives is accurate?
How about the mapping what part is that playing in this primitives issue? I added an extra column to the ChildRecord table and use int as it’s data type. Let’s give the ChildRecord Entity an Int64 member whilst the mapping files retain the Int32 mapping. We would expect a mapping error to be reported right? As you can see below that’s exactly what we get.
Traceback (most recent call last): File “C:\Users\simon.segal\Documents\NetBeansProjects\IronPythonEntityFramework\src\main.py“, line 33, in C:\Users\simon.segal\Documents\NetBeansProjects\IronPythonEntityFramework\src\main.py File “C:\Users\simon.segal\Documents\NetBeansProjects\IronPythonEntityFramework\src\Context.py“, line 36, in __init__ SystemError: The type ‘Edm.Int32‘ of the member ‘ChildAgeInYears‘ in the conceptual side type ‘NorthwindModel.ChildRecord‘ does not match with the type ‘System.Int64‘ of the member ‘ChildAgeInYears‘ on the object side type ‘EntityFramework.Podo.ChildRecord‘.
I also made a point of including some code to get a look at the Types full name and we can see that it’s the fully qualified name of the underlying CLR type as specified – that’s good you say, exactly what we want __clrtype__ to do for us. The problem with that is that the Entity Framework 4.0 dials up proxies at runtime when we use POCO, if you debug C# code you can see that asking for the POCO entities fully qualified name returns the proxy details as shown in Figure 9.0. This is not the case however for our IronPython Entities (see figure 8.0), regardless of our implementing __clrtype__.
Figure 9.0
In Part 3.0 we will dig into proxies and what they offer, how to setup to use them for different requirements and what effect it may or may not be having on our attempts to get POCO (or rather PODO) lazy loading and change tracking entities working with Entity Framework version 4.0.
No commentsPatterns that cross borders - C# to IronPython
Recent consideration of the Template Method Pattern got me to wondering about how I might achieve a little more dynamism with respect to declaration, definition and instantiation when using the template method pattern. Without delving too deep into the “classical” Template Method Pattern it can be described here in code with the most trivial of examples.
C# Classic Template Method Pattern
abstract class FootballMatch { abstract void StartGame(); abstract void EndGame(); public virtual void PlayGame() { Console.Writeline("Players kick the ball around a field"); } public virtual void CompleteMatch() { StartGame(); PlayGame(); EndGame(); Console.WriteLine("Match completed thanks for attending!"); } } public class AustralianRulesFootballMatch : FootballMatch { public void StartGame() { Console.WriteLine("Siren blows to begin game…."); } public void EndGame() { Console.WriteLine("Siren blows to end game…."); } } public class SoccerFootballMatch : FootballMatch { public void StartGame() { Console.WriteLine("Referee blows whistle to begin game…."); } public void EndGame() { Console.WriteLine("Referee blows whistle to end game…."); } }
What about Functions? And how about runtime implementer?
As described above, the implementing classes are defined at design time as concrete derived classes. Doing this in Languages such as Python and Ruby seems a little more flexible in as much that I don’t really require the derivation through an abstract class.
An IronPython Implementation
class RestuarantEndToEndService: def __init__(self, servicename, m1, m2, m3, m4): self._serviceName = servicename self._greetAndQualifyCustomer = m1 self._takeCustomerOrder = m2 self._serveTheCustomerMeal = m3 self._takePayment = m4 def prepareTheMeal(self): print ‘Chef prepares the meal‘ def performService(self): print ‘Doing %(svc)s Service‘ % {‘svc‘ : self._serviceName} self._greetAndQualifyCustomer() self._takeCustomerOrder() self.prepareTheMeal() self._serveTheCustomerMeal() self._takePayment() def seatCustomer(): print ‘Greet and seat the customer at their table‘ def answerOrdersPhoneLine(): print ‘Answer the phone and take the customer details‘ def takeCustomerOrder(): print ‘Waiter / Waitress takes the customers order‘ def takeCustomerPhoneOrder(): print ‘Take the customers order by phone‘ def serveTheCustomer(): print ‘Waiter / Waitress serves customer their meal‘ def deliverTheCustomerPhoneOrder(): print ‘Driver delivers the customers phone order‘ def takeCustomerPaymentAtCounter(): print ‘Cashier takes customer payment‘ def takeCustomerPaymentOnDelivery(): print ‘Driver takes customer payment‘ inhouseService = RestuarantEndToEndService(‘In House‘, seatCustomer, takeCustomerOrder, serveTheCustomer, takeCustomerPaymentAtCounter) deliveryService = RestuarantEndToEndService(‘Delivered‘, answerOrdersPhoneLine, takeCustomerPhoneOrder, deliverTheCustomerPhoneOrder, takeCustomerPaymentOnDelivery) inhouseService.performService() print ‘***********************‘ deliveryService.performService()
The DLR, Interop and shifting the code around
What’s a little more interesting is using this pattern in interop scenarios when the templated class is defined in a statically typed language and the template method is supplied by a dynamic language created for the DLR. This can be demonstrated here with the given abstract class and by using my IronPython WPF interactive console control.
public abstract class TemplateClass { public abstract void Operation1(); public abstract void Operation2(); /// <summary> /// The Tempate Method /// </summary> public void TheTemplateMethod() { Operation1(); Operation2(); } }
Using the Python Help function we can examine the abstract template class and see how it’s been defined. Looking at the printed help on the class it’s clear that the two abstract methods have ‘tagged along for the ride’ as expected. You wont be able to execute them until you provide an implementation, just as you would expect given it’s an abstract class.
The other scenario involves moving some statically typed code (a method) to the IronPython runtime and using an Action / Delegate to ‘fill in’ the template algorithm in a dynamically typed class. For example in the EndToEndRestuarantService class defined in Python above, we could push the actions (delegates) over from C# to the IronPython runtime. Given a set of Functions defined in managed C#:
var actions = new Dictionary<string, object>(); actions.Add("seat", seat_customer); actions.Add("order", take_customerOrder); actions.Add("serve", serve_the_customer); actions.Add("pay", take_customer_payment_at_counter);
When you push these functions over the Script Scope running the IronPython code, then you can pass those functions around to any other code running within the same scope. With our Restaurant example we can take our named functions from the C# code at runtime and push them into the constructor of our IronPython class:
When is this pattern useful?
These patterns can be useful in scenarios where you might be considering scripting your managed C# application and providing DLR supported scripting functionality to the application as well. It can also be quite useful when you want to expose some technology that does not currently support full interop (Entity Framework for example) or perhaps you want to make our application model visible to IronPython or IronRuby. There are of course a plethora of patterns that can be implemented in code that can traverse both environments; its really a matter of what we can devise with some imagination.
2 comments







