CTICoder

A random spillage of programming (and other) thoughts

Windows 7 Slow. Constant Hard Drive Activity.

Posted by Shaun McDonnell on May 6, 2009

This one had me stumped for about an hour today. 

I have been running Microsoft Windows Vista Ultimate 64-bit for a couple years now on my Dell Precision with 0 problems.  So, naturally I decided it was time to upgrade to Windows 7 Ultimate.

Much to my dismay, after performing a fresh installation of Windows 7 Ultimate 64-bit Edition I found that it was acting very strange.  Sometimes it would sit for a few seconds when I did simple things like close a window or open the Start Menu.  It was very slow.  Furthermore, the hard drive light was always on and the hard drive was always making sounds.

I knew this had to be a driver issue because the machine had worked flawlessly under the heavy pressures of Vista.  So I started by flashing the BIOS.  That didn’t work.  I tried updating my display drivers and that did nothing.

I decided to see if I needed to update the Intel Chipset driver and it turns out that solved everything.  Microsoft has done a good job of making sure Windows 7 gets all of the correct drivers during installation as all of my other machines have worked flawlessly.

Unfortunately, Intel doesn’t have any Windows 7 x64 specific drivers:

http://downloadcenter.intel.com/filter_results.aspx?strTypes=all&ProductID=2261&OSFullName=Windows+Vista*+64&lang=eng&strOSs=150&submit=Go%21

However, using the Vista x64 drivers did the trick and everything works great now.

 

-Shaun

Advertisements

Posted in Uncategorized | 16 Comments »

Installing the Cisco VPN Client on Windows 7

Posted by Shaun McDonnell on March 3, 2009

In case you were wondering…

  • Download the Citrix DNE Update and Install it.
  • Reboot
  • Install Cisco VPN Client version 5 or newer.
  • Reboot

Good to go.

-Shaun

Posted in Uncategorized | Leave a Comment »

A Well Placed Advertisement

Posted by Shaun McDonnell on February 6, 2009

image

– Shaun

Posted in Uncategorized | Leave a Comment »

Fixing 404 Errors under MVC w/ IIS6

Posted by Michael Bray on December 30, 2008

I’ve run into this three times now, and you would think that one of the first two times I would have figured out exactly what the issue was.  Robert helped me fix this the first time, then I was able to duplicate it last night on another box, but I hit it again when I tried to make some configuration changes on the first box.  I have a rule that any issue that bite me three times gets extra attention, thus this post.  Writing about it forces my brain to push it into long-term memory, and hopefully if anyone else runs into the same problems, this will help. 

The symptoms are this: you’re running MVC (preview 5 as of this writing) on IIS6.  Opening the root of your application may work, but trying to navigate to a controller fails with a 404 error.  So you did some research, and you probably found some links…  Omar’s blog talks about several different issues, the first of which is most relevant to this issue.  He links to several forum posts, several of which contain a wonderful and easy solution (even if not the best solution), specifically, adding a wildcard application map in the Application Configuration.  Wildcard application maps may not be the best solution, but they are easy.  (Here’s a better way to do it.)

So you go an add your wildcard application map by clicking the Insert button to the right of the Wildcard application mappings (don’t confuse yourself and click ‘Add…’ under Application Extensions (see the image below).

image

So you think you fixed it….   you go back to your page and you STILL get the 404 errors.  You do everything from restarting IIS to rebooting the server to checking every permission and configuration setting you can find, but nothing works.

Well it turns out there is a bit of a misleading option that is critical to this working.  When you add the wildcard application map, you MUST uncheck the option “Verify that file exists.”  As I mentioned, there are several posts (here’s another) that talk about this, but it’s easy to overlook, especially if you are a ‘read every other word’ type of person like I am.  Worse, several other Google search results tell you to add the wildcard mapping but don’t say anything about the “Verify” option. 

Let me repeat this for those of you that are like me:

YOU MUST UNCHECK THE OPTION “VERIFY THAT FILE EXISTS”

image

This option does NOT mean “Make sure that ASPNET_ISAPI.DLL exists”…    it means “make sure the file that the user is requesting exists”.  Thus, if you do not uncheck this option, the server will see that the MVC page that the user is requesting doesn’t really exist as a file on disk, and will report a 404 error.

There.  I think that should be enough self-flogging for now.

Posted in Uncategorized | 1 Comment »

Anonymous Workflow Execution in Sharepoint 2007

Posted by Shaun McDonnell on December 24, 2008

A colleague of mine had a simple task:

Create an InfoPath form that will be uploaded to Sharepoint 2007 and then allow anonymous internet users to submit that form.  Upon submission, send the respective anonymous user an email detailing out the form they filled out.

Not so simple.

Well, that’s not entirely true.  Allowing an anonymous user to submit is easy.  It was the automated Sharepoint Workflow that executed after the form was submitted that was a problem.  Why?  Workflows, by default and by design, cannot be executed anonymously.

Enter .NET, C# and me.

The requirement:

Add some custom code to Sharepoint 2007 that will allow the anonymous execution of Sharepoint Workflows.  Furthermore, create a WebPart that will give an administrative user the ability to assign anonymous workflow execution to a Sharepoint List.

Begin interesting learning process.

If you create a workflow in Sharepoint 2007 and then assign the workflow to be executed upon the ‘ItemAdded’ event of the Document Library the workflow will not fire.  What is even better is that you will get no immediate error from Sharepoint.  You’ll just come to the realization that your workflow never executed.  But never fear!  The Microsoft Windows Event Log is here!  Here’s a snapshot of what the event log will give you:

image

Yes, do not adjust your monitor.  The text actually reads “Attempted to read or write protected memory.  This is often an indication that other memory is corrupt.”

Before you decide that your server needs new memory because a stick has gone bad, realize that what this means is a process tried to execute a workflow but that process did not have the rights to do so.  Duh.

What has to be done at this point is the creation of a custom Sharepoint EventReceiver.  An EventReceiver can be assigned to any type of Sharepoint List and when the appropriate event is executed the EventReceiver kicks in.  To start this process of developing a custom EventReceiver, you create a class (i.e. ‘WorkflowReceiver’) that inherits from Microsoft.Sharepoint.SPItemEventReceiver:

public class WorkflowReceiver : SPItemEventReceiver

When inheriting from this class there are a number of virtual methods you can override and take advantage of.  For this article, we’re just going to look at the ItemAdded method.  This method executes after a new document has been added to the document library that we will eventually assign this custom EventReceiver to.  Here is an example:

public override void ItemAdded(SPItemEventProperties properties)
{
   base.ItemAdded(properties);
   StartWorkflowAnon(properties);
}

The reason I call the respective base method is so that any additional custom processing that might be assigned to this document library can continue before I do my custom work.  This is not required but recommended for lifecycle maintainability.

As the code shows, I then call a method named ‘StartWorkflowAnon’.  This is a custom method that looks like this:

void StartWorkflowAnon(SPItemEventProperties properties)
{
    SPSecurity.RunWithElevatedPrivileges(delegate()                         {
          using (SPSite site = new SPSite(properties.WebUrl))
          using (SPWeb web = site.OpenWeb())
          {
              web.AllowUnsafeUpdates = true;

              SPList list = web.Lists[properties.ListId];
              Guid wfId = new Guid(web.Properties[list.ID.ToString() + ";" + properties.EventType.ToString()]);
              SPWorkflowAssociation assoc = list.WorkflowAssociations[wfId];

              site.WorkflowManager.StartWorkflow(properties.ListItem, assoc, assoc.AssociationData);
              web.AllowUnsafeUpdates = false;
          }
    });
}

The critical code here is SPSecurity.RunWithElevatedPrivileges as this allows whatever code executing within its delegate to have the same execution rights as an authenticated user – exactly what we need to execute a workflow as an anonymous user.

A few items that we gather from the SPItemEventProperties help us determine what Sharepoint List executed the event, it’s url, the event executed and all workflows associated with the list.  Notice that when creating the wfId (workflowId) Guid we are pulling some custom properties from the current Sharepoint Website.  These properties are created by the WebPart that we’ll look into next.  Using the wfId we are able to pull the Sharepoint Workflow that we want to execute from the current Sharepoint List.  Then, we execute that respective workflow with elevated rights. 

Boom!  We now have anonymous workflow execution.  But how did it get assigned to the ItemAdded event in this list?

The Administrative WebPart

I found this to be the most difficult task to complete because creating just a basic WebPart is not easy within Sharepoint.  All html must be hardcoded and rendered via C# which can be a timely task.  Nevertheless, it has to be done.

Custom WebParts can be created using the Microsoft.SharePoint.WebPartPages.WebPart as a base object for your custom WebPart class.  Let’s call this WebPart ‘ReceiverManagement’ and thus our class definition would look like this:

public class ReceiverManagementWebPart : Microsoft.SharePoint.WebPartPages.WebPart

For an administrative user to have the ability to assign anonymous workflow execution to the event of a Sharepoint List as well as the ability to remove workflow execution from lists we are going to need to the following controls in the custom WebPart:

  • DropDownList of Sharepoint Lists
  • DropDownList of Sharepoint List Events
  • DropDownList of Sharepoint Workflows
  • Add Button
  • DataGrid of current assignments

You need to override the ‘CreateChildControls’ event of the base class to create the UI.  Here is what I have:

protected DropDownList lstSiteLists = null;
protected DropDownList lstWorkflows = null;
protected DropDownList lstListEvents = null;
protected Button btnAddReceiver = null;
protected Label lblEventName = null;
protected Label lblWorkflowName = null;
protected DataGrid gvCurrentReceivers = null;
protected Table tblReceiverManagement = null;protected override void CreateChildControls()
{
    base.CreateChildControls();

    lstSiteLists = new DropDownList();
    lstSiteLists.ID = Strings.SiteListsControlID;
    lstSiteLists.AutoPostBack = true;
    lstSiteLists.DataTextField = Strings.SiteListsTitle;
    lstSiteLists.DataValueField = Strings.SiteListsID;
    lstSiteLists.DataSource = CurrentWeb.Lists;
    lstSiteLists.SelectedIndexChanged += new EventHandler(lstSiteLists_SelectedIndexChanged);

    lstListEvents = new DropDownList();
    lstListEvents.ID = Strings.ListEventControlID;

    lstWorkflows = new DropDownList();
    lstWorkflows.ID = Strings.WorkflowsControlID;
    lstWorkflows.DataTextField = Strings.WorkflowsListDataTextField;
    lstWorkflows.DataValueField = Strings.WorkflowsListDataValueField;

    gvCurrentReceivers = new DataGrid();
    gvCurrentReceivers.ID = Strings.CurrentReceiversControlID;
    gvCurrentReceivers.CellPadding = 1;
    gvCurrentReceivers.CellSpacing = 0;
    gvCurrentReceivers.AutoGenerateColumns = false;
    gvCurrentReceivers.DeleteCommand += new DataGridCommandEventHandler(gvCurrentReceivers_DeleteCommand);
    
    ButtonColumn btnRemoveColumn = new ButtonColumn();
    btnRemoveColumn.ButtonType = ButtonColumnType.PushButton;
    btnRemoveColumn.CommandName = Strings.DeleteCommandName;
    btnRemoveColumn.Text = Strings.DeleteCommandName;
    
    BoundColumn bcListId = new BoundColumn();
    bcListId.DataField = Strings.CurrentReceiversListIdDataField;
    bcListId.Visible = false;
    
    BoundColumn bcWorkflowId = new BoundColumn();
    bcWorkflowId.DataField = Strings.CurrentReceiversWorkflowIdDataField;
    bcWorkflowId.Visible = false;

    BoundColumn bcReceiverId = new BoundColumn();
    bcReceiverId.DataField = Strings.CurrentReceiversReceiverIdDataField;
    bcReceiverId.Visible = false;
    
    BoundColumn bcListName = new BoundColumn();
    bcListName.DataField = Strings.CurrentReceiversListTitleDataField;
    bcListName.HeaderText = Strings.CurrentReceiversListTitleHeaderText;
    bcListName.HeaderStyle.HorizontalAlign = HorizontalAlign.Center;
    bcListName.HeaderStyle.VerticalAlign = VerticalAlign.Middle;
    bcListName.HeaderStyle.Font.Bold = true;
    bcListName.ItemStyle.HorizontalAlign = HorizontalAlign.Center;
    bcListName.ItemStyle.VerticalAlign = VerticalAlign.Middle;
    
    BoundColumn bcReceiverType = new BoundColumn();
    bcReceiverType.DataField = Strings.CurrentReceiversEventTypeDataField;
    bcReceiverType.HeaderText = Strings.CurrentReceiversEventTypeHeaderText;
    bcReceiverType.HeaderStyle.HorizontalAlign = HorizontalAlign.Center;
    bcReceiverType.HeaderStyle.Font.Bold = true;
    bcReceiverType.HeaderStyle.VerticalAlign = VerticalAlign.Middle;
    bcReceiverType.ItemStyle.HorizontalAlign = HorizontalAlign.Center;
    bcReceiverType.ItemStyle.VerticalAlign = VerticalAlign.Middle;

    BoundColumn bcWorkflowName = new BoundColumn();
    bcWorkflowName.DataField = Strings.CurrentReceiversWorkflowNameDataField;
    bcWorkflowName.HeaderText = Strings.CurrentReceiversWorkflowNameHeaderText;
    bcWorkflowName.HeaderStyle.HorizontalAlign = HorizontalAlign.Center;
    bcWorkflowName.HeaderStyle.VerticalAlign = VerticalAlign.Middle;
    bcWorkflowName.HeaderStyle.Font.Bold = true;
    bcWorkflowName.ItemStyle.HorizontalAlign = HorizontalAlign.Center;
    bcWorkflowName.ItemStyle.VerticalAlign = VerticalAlign.Middle;

    gvCurrentReceivers.Columns.Add(btnRemoveColumn);
    gvCurrentReceivers.Columns.Add(bcListId);
    gvCurrentReceivers.Columns.Add(bcWorkflowId);
    gvCurrentReceivers.Columns.Add(bcReceiverId);
    gvCurrentReceivers.Columns.Add(bcListName);
    gvCurrentReceivers.Columns.Add(bcReceiverType);
    gvCurrentReceivers.Columns.Add(bcWorkflowName);

    tblReceiverManagement = new Table();
    tblReceiverManagement.ID = Strings.ReceiverManagementTableControlID;
    tblReceiverManagement.CellPadding = 0;
    tblReceiverManagement.CellSpacing = 0;
    tblReceiverManagement.BorderWidth = new Unit(0);

    TableCell tdListLabel = new TableCell();
    tdListLabel.Text = Strings.ListsLabelText;

    TableCell tdLists = new TableCell();
    tdLists.Controls.Add(lstSiteLists);

    TableCell tdEventsLabel = new TableCell();
    tdEventsLabel.Text = Strings.EventsLabelText;

    TableCell tdEvents = new TableCell();
    tdEvents.Controls.Add(lstListEvents);

    TableCell tdWorkflowsLabel = new TableCell();
    tdWorkflowsLabel.Text = Strings.WorkflowsLabelText;

    TableCell tdWorkflows = new TableCell();
    tdWorkflows.Controls.Add(lstWorkflows);

    TableCell tdReceivers = new TableCell();
    tdReceivers.RowSpan = 2;
    tdReceivers.Controls.Add(gvCurrentReceivers);

    btnAddReceiver = new Button();
    btnAddReceiver.Text = "Add";
    btnAddReceiver.Click += new EventHandler(btnAddReceiver_Click);

    TableCell tdButton = new TableCell();
    tdButton.RowSpan = 2;
    tdButton.Controls.Add(btnAddReceiver);

    TableRow trListsRow = new TableRow();
    trListsRow.Cells.Add(tdListLabel);
    trListsRow.Cells.Add(tdLists);

    TableRow trEventsRow = new TableRow();
    trEventsRow.Cells.Add(tdEventsLabel);
    trEventsRow.Cells.Add(tdEvents);

    TableRow trWorkflowsRow = new TableRow();
    trWorkflowsRow.Cells.Add(tdWorkflowsLabel);
    trWorkflowsRow.Cells.Add(tdWorkflows);

    TableRow trButtonRow = new TableRow();
    trButtonRow.Cells.Add(tdButton);

    TableRow trReceiversRow = new TableRow();
    trReceiversRow.Cells.Add(tdReceivers);

    tblReceiverManagement.Rows.Add(trListsRow);
    tblReceiverManagement.Rows.Add(trEventsRow);
    tblReceiverManagement.Rows.Add(trWorkflowsRow);
    tblReceiverManagement.Rows.Add(trButtonRow);
    tblReceiverManagement.Rows.Add(trReceiversRow);

    base.Controls.Add(tblReceiverManagement);

    BindData();

}

 

All of this code successfully creates the UI of the WebPart.  Here is the BindData method:

protected void BindData()
{
    LogHelper.WriteDebug("BindData() called.");
    lstSiteLists.DataBind();
    BindEvents();
    BindWorkflows();
    BindCurrentSubscriptions();
}

Within the BindData method I am binding the respective lists, events, workflows and existing assignments to their controls.  You can download that code using the link at the end of this article.  For now, let’s take a look at what happens when the ‘Add’ button is clicked:

protected void btnAddReceiver_Click(object sender, EventArgs e)
{
    SPList list = CurrentWeb.Lists[new Guid(lstSiteLists.SelectedValue)];

    string key = String.Format("{0};{1}", lstSiteLists.SelectedValue, lstListEvents.SelectedValue);
    if (CurrentWeb.Properties.ContainsKey(key))
    {
        CurrentWeb.Properties.Remove(key);
    }

    CurrentWeb.Properties.Add(key, lstWorkflows.SelectedValue);
    CurrentWeb.Properties.Update();

    SPEventReceiverType receiverType = (SPEventReceiverType)Enum.Parse(typeof(SPEventReceiverType), lstListEvents.SelectedValue);
    list.EventReceivers.Add(receiverType, Assembly.GetExecutingAssembly().FullName, CustomEventReceiverClassName);
}

Is is here where we create the information needed by the WorkflowReceiver (see class above) to execute the anonymous workflow.  Let’s step through the above method.

The CurrentWeb object is a local property that just retrieves the current SPWeb object where the WebPart resides.  So, first we are retrieving the list the user selected in the DropDownList that they wish to assign the workflow to.  Then, I create a unique key that is formed by the unique Guid of the list they selected, a semicolon delimiter and then the name of the event they chose. 

I then check the custom properties of the CurrentWeb to see if that key already exists.  If so, I remove that property from the web because this is a duplicate event assignment and then I proceed to re-add the same property to the CurrentWeb.  I could have chosen to inform the user that the assignment they are creating already exists but this was easier.

Using the enumerator named SPEventReceiverType I am able to cast the text-based name of the event the user chose from the dropdown to this enumerator.  Then, I add a new receiver to the user-selected list and pass it the type of event, the FQ name of the assembly that contains our custom EventReceiver (in this case it is the same assembly where the current code is executing) and finally I pass it a constant string variable that contains the full name of the class (or type) to be executed when the event is tripped.

Our custom EventReceiver (WorkflowReceiver) is now wired up correctly and ready for production.

Deploying this code to a Sharepoint 2007 Server is a little tricky.  First, all of your assemblies need to be strongly signed and uniquely versioned.  These assemblies then need to installed into the GAC (Global Assembly Cache) on the same server from which Sharepoint 2007 is running.  Finally, you need to edit the main web.config for Sharepoint and the following SafeControl lines:

<SafeControl Assembly="Cti.SharepointServices.ReceiverManagement, Version=2.0.0.12, Culture=neutral, PublicKeyToken=e59deda35424cfa7" 
Namespace="Cti.SharepointServices.ReceiverManagement"
TypeName="*"
Safe="True" />

Adding your new assembly as SafeControl ensures that Sharepoint can trust this assembly and therefore allow for its execution.

Lastly, you need to create a dwp file so you can import your new WebPart into Sharepoint.  Your dwp file should contain something like this:

<?xml version="1.0" encoding="utf-8"?>
<WebPart xmlns="http://schemas.microsoft.com/WebPart/v2">
    <Assembly>Cti.SharepointServices.ReceiverManagement, Version=2.0.0.12, Culture=neutral, PublicKeyToken=e59deda35424cfa7</Assembly>
    <TypeName>Cti.SharepointServices.ReceiverManagement.ManageReceiversWebPart</TypeName>
    <Title>Workflow Customization</Title>
</WebPart>

You can then import this DWP file from the Sharepoint interface and then drag your WebPart onto the page.  It should look something like this:

image

Once assigned, your workflows will now execute when submitted by anonymous users.

To download the code that relates to this post, go here.

-Shaun


dp.SyntaxHighlighter.ClipboardSwf = ‘http://s1.wordpress.com/wp-content/plugins/highlight/clipboard.swf&#8217;;
dp.SyntaxHighlighter.HighlightAll(‘code’);

Posted in .NET, Sharepoint, Sharepoint Workflows, Snippets, Tips | 13 Comments »

Tools

Posted by Shaun McDonnell on December 9, 2008

I am in the process of re-installing my operating system here at work and so I decided to post a list of all the different tools I always install on every computer that I use.  Here it is:

That’s it for now, I will post more if I can remember them.

-Shaun

Posted in .NET, Tips | 1 Comment »

Setting a Silverlight canvas to full width/height of the browser in Silverlight 2

Posted by Michael Bray on October 19, 2008

This article shows how to set the canvas width and height for the page, but apparently some things broke since the page was written and Silverlight 2 came out.  In order to do this properly in Silverlight 2, use Application.Current.Host.Content in place of BrowserHost as described in the article. 

Strangely, you can achieve the same thing by removing the Width and Height tags on the main UserControl.  In the designer, this will make it appear as if the control is AutoSizing down to the contents of the subcontrols, but in the actual application it will take the full size of the browser if you have the height style set to 100% on body, form, and div of the containing element of the asp:Silverlight control as well as the Width and Height parameters set to 100% on the control itself.

Posted in Uncategorized | 3 Comments »

Entity Framework Error 3002 / Error 3003

Posted by Michael Bray on October 14, 2008

Fun problem I had today figuring out this error….  I suppose it is common, and in fact I think I recall reading posts on Usenet about this, without many solutions.  The problem comes when you have a mapping from one table to another. 

The error in this case comes in the form of two errors during compile:

Error 3002: Problem in Mapping Fragment starting at line 498: Potential runtime violation of table QuotePricedLineItem’s keys (QuotePricedLineItem.ObjectId): Columns (QuotePricedLineItem.ObjectId) are mapped to EntitySet FK_QuoteLineItem_QuoteVendorLineSet’s properties (FK_QuoteLineItem_QuoteVendorLineSet.QuoteVendorLineSet.ObjectId) on the conceptual side but they do not form the EntitySet’s key properties (FK_QuoteLineItem_QuoteVendorLineSet.QuotePricedLineItem.ObjectId, FK_QuoteLineItem_QuoteVendorLineSet.QuoteVendorLineSet.ObjectId).

Error 3003: Problem in Mapping Fragment starting at line 498: At least one of the key properties of AssociationSet FK_QuoteLineItem_QuoteVendorLineSet must be mapped to all the key properties (QuotePricedLineItem.ObjectId) of table QuotePricedLineItem.

This clearly sounds like there is a problem in the association between two tables.  Looking at the designer diagram, you see the relation:

image

and looking at the mappings of the association, at first glance, everything looks fine:

image

The problem lies in which column is mapped to which property.  In this case, and I believe generally in the case of this particular error, the columns are interchanged.  The mappings should be QuoteVendorLineSet.ObjectId = fkQuoteVendorLineSet, and QuotePricedLineItem.ObjectId = ObjectId.  This is obvious since the map is for QuotePricedLineItem, so ObjectId should map to itself.

The odd part is that this association was generated by the EF Designer wizard, so it is the one that screwed up.  At first I thought maybe I had the relationship backwards in the database, but that wasn’t the case.  Thus I’m not sure why this occurred, but tracking it down hopefully will be easier next time.

Posted in Uncategorized | 2 Comments »

Using Enums with Entity Framework

Posted by Michael Bray on August 16, 2008

I was a bit disappointed (but not surprised) to see that EF didn’t seem to support Enum types on the entities it generates.  Even if you try to manually change the type to an enum type defined in your project, it will complain saying that it can’t find the type.  (Maybe I’m just not doing it right??)

Anyway, there is a seemingly elegant solution… utilize the fact that the entity classes are partial.

For example, if you have an enum:

public enum AccountType
{
    Internal,
    Public,
    External
}

that you want to store in the database (say as part of an ‘Account’ object) then you can just extend the partial ‘Account’ class and change a few properties in the .edmx file… Personally, I would rather have the C# class maintain the naming conventions than the database fields, so in this example I name the field in the database with a ‘t’, to indicate that it is a ‘type’. You could use anything – ‘e’ for ‘enum’, or even ‘enum’ all the way. So for example:

image

Then in the properties of the ‘tAccountType’ field, set the Getter and Setter to ‘private’:

image

And finally, add a class to your code that extends the partial class:

public partial class Account
{
    public AccountType? AccountType
    {
        get { return (AccountType)this.tAccountType; }
        set { this.tAccountType = (int?)value; }
    }
}

…and voila… the class now provides the enum the way it should, and still manages the underlying entity.

Posted in Uncategorized | 9 Comments »

Object Inheritance in Entity Framework

Posted by Michael Bray on August 16, 2008

I started playing with the Entity Framework that was officially released a few days ago along with VS2008 SP1 and .NET 3.5 SP1.  After playing with Linq to SQL, there was one particular feature that I was interested in – the multi-table object inheritance capabilities.  Linq to SQL had the ability to provide inherited types, but the underlying data source stored all of the different types in the same table, so-called Table-Per-Hierarchy (TPH) storage.  In this mechanism, a field in the database is used to distinguish the type that the row represents and it is generated accordingly.  This would lead to artifacts such as a lot of null fields in the database – not such a bad thing as NULL types don’t take up much (if any) space but the table structure is very flat, and not so accommodating if your object hierarchy is more than a few levels deep.

Linq to Entities provides a new type of storage, Table-Per-Type (TPT) that provides a much higher fidelity mapping to the underlying tables as compared to the business classes that are being worked with.  In this mode, there is a separate table for each level of the class hierarchy.  For deep class hierarchies this seems to make more sense.  The down side is that at the very root of the class hierarchy you end up with a table that is gi-normous.

One of the things that I plan to do to mitigate the size of that base table is to link the objects with Guids (uniqueidentifier) instead of ints.  By doing this, I have a root object that has a Guid, and two other properties that I want to be common on all objects – DateCreated and DateModified.  To maximize efficiency, the Guid column is the primary key, and is also the RowGUID.  The derived tables have the same characteristic Guid RowGuid column, and this is the value that I use to link the tables together in a 1-to-1 relationship.  This way I really have a true hierarchy without any additional data storage overhead for the key field. 

Another nice benefit that this provides is that any object in any table can make reference to any other object, and I know that I can find that object.  I also plan to control some of the portions of the Guid, and I’ll use that to identify the type of object (eg Person, Account, Order, etc) so I can know exactly what type of object it is and can load it accordingly.

Here’s an example hierarchy:

image

Note that DbObject is the root of the hierarchy, and assuming that I maintain this structure for all objects in the database, it will grow VERY large.  Is this a problem?   Possibly, but I expect SQL can deal with it.  The main concern is that any time I load one of these objects using Linq to Entities, the SQL query will include a JOIN to the DbObject table, which could be a killer, but possibly not as bad as you might think.  Initial testing indicates that I can load the entire Cisco parts database (which is more than 330,000 items and includes about 15 properties per item) in about 23 seconds.  Not great if the user is waiting, but how often am I going to load the entire database?  Not too often, I hope.  But it isn’t too bad either…  I suspect the use of the ObjectId as the joining field (and the fact that it is the primary key and RowGuid) are significantly helping that result time.

The other idea that I have to help mitigate that huge JOIN is that I may generate two nearly identical models – one with the DbObject and one without it.  This of course doesn’t affect the underlying database – it just affects how Linq to Entities interacts with it.  In that model, it will never attempt to JOIN the DbObject table.  If I’m only looking at the data (not adding a new object or updating it) then I have no need for the data contained in DbObject.  Of course, it also means that I’ll need to have two different models for every object (eg Person and PersonNRO (NRO = NoRootObject)) but it might be worth the gain. This will of course double the work to maintain the structure when changes are made, but I think that I could use a tool or write one myself that would generate the .edmx file for both objects based on a common definition. That’s a project for a rainy day, though.

Anyway, the one odd thing I’ve found as I’ve started working with this stuff is that in the normal model (with the DbObject), the Entity Container object ONLY provides access to root objects (in this case, a member called DbObjectSet).  So you don’t get a direct reference to any of the derived types at all:

image

Note that there is no ‘PersonObjectSet’, ‘EmployeeObjectSet’, ‘ContactObjectSet’ or ‘AccountObjectSet’.  So how do you load those types of entities?  You have to use a function that is provided on DbObjectSet:

Model1Container c = new Model1Container(); 
var people = from p in c.DbObjectSet.OfType<Person>() 
             select p; 

This will return a list of all the objects in the People table (along with the relevant data from DbObject) as one single class ‘Person’.  Amazing!

Posted in .NET | Tagged: , | 2 Comments »