Monday, 25 February 2008

This is the sixth of a series about how to go about using postcompilation in your solutions. You can read it as a tutorial on how to use PostSharp. I am very much new to that framework, but the power it provides could seriously change how you build your applications. While working on the EF contrib project, I had to dive into PostSharp, and I hope to share some of the things I learned along the way.

This post delves into using the weaver, to do some funky stuff for us!

The full table of contents:

  • Introducing Entity Framework Contrib- Easy IPoco implementation V 0.1
  • Part II, Postsharp Core versus Postsharp Laos
  • Part III, the compound aspect
  • Part IV, the PocoInterfaceSubaspect (composition aspect)
  • Part V, hooking up the weaver
  • Part VI, the EdmScalarWeaver
    Recap

    We wish to create an attribute that can be placed on top of our ordinary Poco class, that will magically transform it into a class that implements the 3 IPoco interfaces. These are needed by the Entity Framework to do it's work. We will use PostSharp to do this.
    Our previous post talked about the compound attribute and how it goes about implementing interfaces on classes for you.

    We want to put custom attributes on our type that EF needs (the EDM scalar attributes on top of properties and the EDM type attribute that connects your type to a EDM type). Laos does not seem to have a ready-to-use aspect that provides that functionality, so we are going to need to hook into the weaver ourselves! How exciting!
    Thankfully, we can derive from TypeLevelAspectWeaver to make life easy enough.

    In the previous post, we hooked up the weaver, this post we are actually going to do stuff.

    The Implement method

    Our weaver derives from TypeLevelAspectWeaver, and thus can override the Implement() method. I have to do some stuff to get to your config file, using PostSharp to get the Path to the original App.Config. When I have that, I load it, and look at the connectionstring that matches the containername you have supplied the attribute. Then, I use the EntityConnectionBuilder to create a connection string and finally load in the metadata workspace from EDM. With the metadata in hand, I can start looking at the transformation I have to do.

    Setting EDMScalarAttributes

    I recently chatted with Gael (creator of PostSharp) and he assured me that there would be a highlevel method to add attributes to code. In this version of PostSharp, that is not directly possible (hence, the weaver we are using). So, we will do it ourselves.

    First, let's loop through all the properties defined on our supplied businessEntity:

      1             foreach (PropertyDeclaration prop in typeDef.Properties)
      2             {
      3                 EdmProperty memberProperty;
      4 
      5                 // find it as a member
      6                 memberProperty = entityType.Members.SingleOrDefault(edmprop => edmprop.Name.Equals(prop.Name)) as EdmProperty;
      7 
      8                 // it can easily be something else than an edm property
      9                 if (memberProperty != null)
     10                 {
     11                     // it might be a key property. I have not yet found a better way to determine if it is a keymember or not. This seems wastefull
     12                     prop.CustomAttributes.Add(
     13                         CreatePropertyAttribute(memberProperty,
     14                         (entityType.KeyMembers.SingleOrDefault(edmprop => edmprop.Name.Equals(prop.Name)) != null)));
     15 
     16                     continue;
     17                 }
    18             }

    I use a bit of Linq to check if this a propety is a key, and call my CreatePropertyAttribute method:

      1         CustomAttributeDeclaration CreatePropertyAttribute(EdmProperty edmProperty, bool IsKeyProperty)
      2         {
      3             CustomAttributeDeclaration attr = new CustomAttributeDeclaration(edmScalarPropertyAttribute);
      4 
      5             // nullable
      6             attr.NamedArguments.Add(
      7                 new MemberValuePair(MemberKind.Property,
      8                     0,
      9                     "IsNullable",
     10                     new SerializedValue(
     11                         SerializationType.GetSerializationType(this.module.FindType(typeof(bool), BindingOptions.Default)),
     12                         edmProperty.Nullable)
     13                         ));
     14 
     15             // since we need to set the ordinal, take care to set this property last!
     16             if (IsKeyProperty)
     17             {
     18                 attr.NamedArguments.Add(
     19                     new MemberValuePair(MemberKind.Property,
     20                         1,
     21                         "EntityKeyProperty",
     22                         new SerializedValue(
     23                             SerializationType.GetSerializationType(this.module.FindType(typeof(bool), BindingOptions.Default)),
     24                             true)
     25                             ));
     26             }
     27 
     28             return attr;
    29         }

    As you can see, it get's a little bit more complicated. We need to add a custom attribute, but to get it, we need to have a constructor for the attribute. I already have it cached: at line 3 the cached IMethod is given to the PostSharp CustomAttributeDecaration class. I got to the ctor like this:

                edmScalarPropertyAttribute = module.FindMethod(typeof(EdmScalarPropertyAttribute).GetConstructor(System.Type.EmptyTypes), BindingOptions.Default);
    

    We use PostSharp to find the constructor in the module.

    With the constructor, we can create a customAttributeDeclaration and from there we can add namedArguments. Note, that here again, we use PostSharp to find types for us. Kind of confusing, but it does provide a consistent way to do things. You could use it to call your own methods as well (!).

    I do the same for the attribute that needs to be placed on the complete type, and we are ready!

    Default values

    In the EF designer, you have the ability to specify default values for properties. I needed to mimic this functionality for this project, so I got to work. It seemed quite simple, because I could get to the fields without a problem. However, fields are initialized in the ctor of your type (thank you reflector). So more work was needed.

    First, I wanted to reuse this weaver, and wanted the weaver to add IL methods in the constructor. To do that, I implemented the ITypeLevelAdvice interface and added this line to the end of the implement():

                // make sure this class is called to weave
    
                this.Task.TypeLevelAdvices.Add(this);

    Implementing the ITypeLevelAdvice gives us the opportunity to supply some  information about what we want to do exactly:

            #region ITypeLevelAdvice Members
    
            public JoinPointKinds JoinPointKinds { get { return JoinPointKinds.AfterInstanceInitialization; } }
    
            public TypeDefDeclaration Type { get { return (TypeDefDeclaration)this.TargetElement; } }
    
            #endregion
    
            #region IAdvice Members
    
            public int Priority
    
            {
    
                get { return 0; }
    
            }
    
            public bool RequiresWeave(PostSharp.CodeWeaver.WeavingContext context)
    
            {
    
                return true;
    
            }
    
            #endregion

    As you can see, I want to use the AfterInstanceInitialization joinpoint. In other words, I want to be able to weave code, at that moment.

    What to weave?? I know everything about my businessentity, but I only know which properties need default values. So I want to come up with some basic rules about which field belongs to a certain propertyname:

      1             #region set default values. not yet emitting the instruction, but waiting for the Weave method
    
      2             foreach (FieldDefDeclaration field in typeDef.Fields)
    
      3             {
    
      4                 // we have to make concessions: we do not know how to find the field with the property exactly
    
      5                 EdmProperty memberProperty;
    
      6 
    
      7                 // find it as a member
    
      8                 // the rules: the field must match the ending of the propertyname. So underscore is okay
    
      9                 memberProperty = entityType.Members.SingleOrDefault(edmprop => field.Name.EndsWith(edmprop.Name, StringComparison.OrdinalIgnoreCase) ) as EdmProperty;
    
     10 
    
     11                 // in case that didn't match, try the autogenerated fieldname
    
     12                 if (memberProperty == null)
    
     13                 {
    
     14                     memberProperty = entityType.Members.SingleOrDefault(edmprop => (field.Name.IndexOf("<" + edmprop.Name + ">") == 0)) as EdmProperty;
    
     15                 }
    
     16 
    
     17                 // if this field belongs to a edm property, we can check for it's default value
    
     18                 if (memberProperty != null)
    
     19                 {
    
     20                     FieldsNeedingDefaultValue.Add(field, memberProperty.Default);
    
     21                 }
    
     22 
    
     23             }  
    24             #endregion

    I use two rules: if the field ends with the same name as the property, then this field belongs to that property. Another rule is, to look at the naming scheme that the compiler uses when it generates auto properties: <Propertyname>_k_backingfield;. Since that is how we will most likely use this whole project, I want to also support that.
    I build up a default value dictionary that I use in a later stadium.

    The weave method will be called when our joinpoint has been reached.

      1         public void Weave(PostSharp.CodeWeaver.WeavingContext context, InstructionBlock block)
    
      2         {
    
      3             foreach (FieldDefDeclaration field in FieldsNeedingDefaultValue.Keys)
    
      4             {
    
      5                 object value = FieldsNeedingDefaultValue[field];
    
      6 
    
      7                 if (value == null)
    
      8                     continue;
    
      9 
    
     10                 // the context is the ctor because we only use the joinpoint AfterinstanceInitialization
    
     11                 InstructionSequence sequence = context.Method.MethodBody.CreateInstructionSequence();
    
     12                 block.AddInstructionSequence(sequence, NodePosition.Before, null);
    
     13                 context.InstructionWriter.AttachInstructionSequence(sequence);
    
     14                 InstructionWriter writer = context.InstructionWriter;
    
     15 
    
     16                 if(value is int)
    
     17                 {
    
     18                             writer.EmitInstruction(OpCodeNumber.Nop);
    
     19                             writer.EmitInstruction(OpCodeNumber.Ldarg_0);
    
     20                             writer.EmitInstructionInt32(OpCodeNumber.Ldc_I4, (int)value );
    
     21                             writer.EmitInstructionField(OpCodeNumber.Stfld, field);                }
    
     22                 else if (value is string)
    
     23                 {
    
     24                             writer.EmitInstruction(OpCodeNumber.Nop);
    
     25                             writer.EmitInstruction(OpCodeNumber.Ldarg_0);
    
     26                             writer.EmitInstructionString(OpCodeNumber.Ldstr, (string)value);
    
     27                             writer.EmitInstructionField(OpCodeNumber.Stfld, field);                }
    
     28                 else
    
     29                 {
    
     30                     // TODO: implement other value types
    
     31                     throw new NotImplementedException(String.Format("No IL default implemented for type {0}", value.GetType()));
    
     32                 }
    
     33 
    
     34                 writer.DetachInstructionSequence(true);
    
     35             }
    
     36 
    
    37         }

    Again, Gael has assured me that a highlevel functionality will be created to easily set default values. I do not like to work with a big IF statement to inject different IL instructions per type, but that's it for now....

    I just use reflector, in IL viewing mode, to see how I should initialize a certain type, and off we go.

     

    This is the end of this series. I hope you enjoyed it.

    The following things still have to be done:

    • PostSharp can now be installed without using the GAC. I think people feel more at ease just using an external assembly, so I will change the EFContrib project to support this.
    • Relationships and complex types need to be supported
    • Obviously, the other default values need to be supported.

    I'll keep you updated on how that progresses!
    I hope this series has given you some ideas on how to use postcompiling in your own project. Let it make your life easier and your code cleaner.

  • Saturday, 04 December 2010 03:32:42 (Romance Standard Time, UTC+01:00)
    <a href="http://www.fashioniphone.com/digital-camera">digital cameras sale</a>
    <a href="http://www.fashioniphone.com/digital-camera">cheapest digital cameras</a>

    <a href="http://www.fashioniphone.com/iphone-cases">iPhone Cases</a>
    <a href="http://www.fashioniphone.com/iphone-cases">iphone case</a>
    <a href="http://www.fashioniphone.com/iphone-cases">iPhone 3G Case</a>
    <a href="http://www.fashioniphone.com/iphone-cases">iphone 3g case </a>
    <a href="http://www.fashioniphone.com/iphone-cases">iPhone covers</a>
    <a href="http://www.fashioniphone.com/iphone-cases">iPhone plastic cases</a>
    <a href="http://www.fashioniphone.com/iphone-cases">iphone skin</a>
    <a href="http://www.fashioniphone.com/iphone-cases">iphone 3g skin</a>
    Comments are closed.