For Visual Studio 2010, we added a number of extensibility points to the Entity Designer.
In this series of posts, I am going to focus on the first type.
Scenario:
We have a large model that will end up generating a large number of C# types and we don't want all of these types to be in the same C# namespace. We want to be able to control, on a per-entity basis, the C# namespace in which the type will be generated into.
Solution:
In this first post, we will create a new Property Annotation Extension that will add a "Code Namespace" property to an entity's property window. In the second post, we will customize the code generation template to use this new information and generate the code correctly.
NOTE: These posts are written to work with Visual Studio 2010 Beta 2.
Walk through: Creating the Property Annotation Extension
Lets get started by setting up our extension project. One tool that will make our job much easier is the ADO.NET Entity Data Model Designer Extension Starter Kit. Download this starter kit and install it on the machine where you have Visual Studio 2010 Beta 2 installed.
NOTE: If you click on the "extension.vsixmanifest" file in Solution Explorer and look at its properties (select it and hit F4), you will notice that the "Build Action" is set to "VsixContent". This build action is what tells the starter kit to include the file (or any file) in the VSIX file.
Next, we will edit the CodeNamespace.cs class so that it tracks our custom XML annotation and shows the correct UI to the user.
internal static readonly string _namespace = "http://schemas.microsoft.com/EFToolTime"; internal static XName _xnMyNamespace = XName.Get("CodeNamespace", _namespace); internal const string _category = "EF ToolTime Extensions";
[DisplayName("Code Namespace")] [Description("The code namespace to generate this entity in")] [Category(CodeNamespace._category)] [DefaultValue(false)] public string Namespace {
We need to update the getter so that it works with a string instead of a bool. The code first checks to see if the passed in "parent" has any children. If so, it loops over all of the child elements looking for our XML element. If found, that element's value is returned.
get { string propertyValue = string.Empty; if (_parent.HasElements) { foreach (XElement child in _parent.Elements()) { if (child.Name == CodeNamespace._xnMyNamespace) { // annotation element exists, so get its value. propertyValue = child.Value.Trim(); break; } } } return propertyValue; }
The setter is a little more complicated. The first thing that we do is ask the context (the second argument passed to our constructor) for a change scope. The string argument to "CreateChangeScope" will be shown to the user in the Visual Studio undo list. In order to make any changes to the user's model, you must first create a change scope. If you manipulate the XLinq tree outside of a change scope, an exception will be thrown.
The code then does one of three things. First, if the parent element has no children, we just add our new element. Second, if the parent already has one of our elements, we update it. Third, if the parent element has children but does not contain our element, we add our new element after the last child. This last part is important as the XSDs for CSDL and SSDL dictate the order of child elements in some circumstances. To be safe, you should always add your custom elements after the last child.
Finally, once all of the changes have been made we call Complete() on the change scope. If the Dispose() method is called on a change scope before a call to Complete(), then all changes are rolled back. For this reason, it is highly recommended that you always wrap your change scopes in a using statement.
set { using (EntityDesignerChangeScope scope = _context.CreateChangeScope("Set CodeNamespace")) { if (_parent.HasElements) { XElement nsElement = null; XElement lastChild = null; foreach (XElement child in _parent.Elements()) { lastChild = child; if (child.Name == CodeNamespace._xnMyNamespace) { nsElement = child; break; } }
if (nsElement == null) { // element does not exist, so create a new one as // the last child of the EntityType element. lastChild.AddAfterSelf(new XElement(_xnMyNamespace, value)); } else { // element already exists under the EntityType element, // so update its value. nsElement.SetValue(value); } } else { // element has no child elements so create a new MyNewProperty // element as its first child. _parent.Add(new XElement(_xnMyNamespace, value)); }
// commit the changes. scope.Complete(); } }
We are done with "CodeNamespace.cs". Be sure that you can compile your project. Fix any errors and close the file.
NOTE: The context class is passed to your property extension's constructor. With it, you have access to the file being edited (via the ProjectItem property), the project the user is working in (via the Project property) and the version of the EF that the user's model is targeting (via the EntityFrameworkVersion property).
We don't need to make any changes to the "CodeNamespaceFactory.cs" class but some quick notes are warranted, so let's open the factory class. The line here that you will probably find yourself changing the most as you develop extensions is the EntityDesignerExtendedProperty attribute. This attribute tells the Entity Designer the type of items that this new property extension applies to. The attribute can be a list of types OR'd together. For example, the following line would cause our extension to show up for both EntityTypes and Properties in the CSDL.
[EntityDesignerExtendedProperty(EntityDesignerSelection.ConceptualModelEntityType | EntityDesignerSelection.ConceptualModelProperty)]
For our case, we just want our extension to show up for EntityTypes, so we don't need to change any code in this file.
We are ready to build and install our extension.
Launch Visual Studio again and click on the Extension Manager. It is on the standard tool bar, but also accessible from the Tools menu. You should see your extension there, and it should be enabled. If it isn't there, go back and double-click on your VSIX file again and ensure that it reports success.
Create a new project or open up an existing project with an EDMX file, and open the Entity Model. When you click on an entity on the designer canvas, you will now see the line for "Code Namespace" in the property window. If you select this line in the property window, you will see the help text display at the bottom of the property window. If you choose the Categorized view (left most icon in the property window's tool bar), you will see it inside the custom category we defined in the Catogory attribute.
Here is what the XML looks like when I add a value in my extension.
<EntityType Name="employee"> <Key> <PropertyRef Name="emp_id" /> </Key> <Property Name="emp_id" Type="String" Nullable="false" MaxLength="9" Unicode="false" FixedLength="true" /> <Property Name="fname" Type="String" Nullable="false" MaxLength="20" Unicode="false" FixedLength="false" /> <Property Name="minit" Type="String" MaxLength="1" Unicode="false" FixedLength="true" /> <Property Name="lname" Type="String" Nullable="false" MaxLength="30" Unicode="false" FixedLength="false" /> <Property Name="job_id" Type="Int16" Nullable="false" /> <Property Name="job_lvl" Type="Byte" /> <Property Name="pub_id" Type="String" Nullable="false" MaxLength="4" Unicode="false" FixedLength="true" /> <Property Name="hire_date" Type="DateTime" Nullable="false" /> <NavigationProperty Name="job" Relationship="pubsModel.FK__employee__job_id__25869641" FromRole="employee" ToRole="jobs" /> <NavigationProperty Name="publisher" Relationship="pubsModel.FK__employee__pub_id__286302EC" FromRole="employee" ToRole="publishers" /> <a:CodeNamespace xmlns:a="http://schemas.microsoft.com/EFToolTime">Publishing</a:CodeNamespace></EntityType>
At the end we see our structured annotation correctly added to our entity. Congratulations, you have now extended the Entity Designer!
In Part 2, we will look at customizing the default T4 code generation template to use this new data and correctly place our entity classes into their namespace.
More information on Entity Designer Extensibility @ MSDN.