Multi-blog Obsession


TwoBlogsThe multi-blog data provider for BlogEngine.Net has been taking up a lot of my brain space lately—to the point that I’m able to announce that it is installed and working “in the wild” on a hosted site (though not in anything like a heavy-load situation). I now have a copy of both my dev site and my personal site up and running from the same directory (and the same database). Frankly, I didn’t think it’d be as easy as it was. This success prompted me to create a 2.0 release (that is now up on the CodePlex site).

Getting Static

My main fear was with the heavy use of static variables in BlogEngine.Net. You see, BE.Net loads all the data into memory using static List variables. I found this out when I went looking for the best way to store a BlogId (so that it didn’t have to parse against an Url every time a request came through).

While there are pros and cons to keeping your entire blog in memory (pro: speed and ease, con: memory bloat and a large delay on any request that triggers a data load), my concern was how an application would react when it had to serve two sets of data. Fortunately, it seems that even when two sites share an application pool on IIS, they still keep their static spaces separate. I’m not sure what I was going to do if it didn’t but I was spared the tragedy.

Configuration

Installing the blog provider mainly involves copying the binary into the /bin directory and then updating the web.config to point to the right driver. There are three providers in your web.config that are affected.

Blog Provider

The blog provider handles the blog data. Settings, posts, categories and suchlike. Add the provider and update the “defaultProvider” tag and you’re ready to go.

 

<BlogEngine>
  <blogProvider defaultProvider="SQLBlogProvider">
    <providers>
      <add name="SQLBlogProvider" type="BlogEngine.SQLServer.SqlBlogProvider, BlogEngine.SQLServer" connectionStringName="BE"/>
    </providers>
  </blogProvider>
</BlogEngine>

 

Membership Provider

The membership provider handles user authentication and management (stuff like changing passwords and such). Technically, you don’t need to change this, but if you don’t the users will be the same across blogs (not a problem if you aren’t multi-blogging). I frankly haven’t tested if a mixed-configuration actually works but it should. Again, add the provider and update the “defaultProvider” tag and you’re ready to go.

 

<membership defaultProvider="LinqMembershipProvider">
  <providers>
    <clear/>
    <add name="LinqMembershipProvider" type="BlogEngine.SQLServer.LinqMembershipProvider, BlogEngine.SQLServer" passwordFormat="Hashed" connectionStringName="BE"/>
  </providers>
</membership>

 

Role Provider

The role provider handles authorization and what users are assigned to which roles. Again, you don’t technically have to change this if you don’t need it. Also again, it’s simply a matter of adding the provider and changing the “defaultProvider” tag.

<roleManager defaultProvider="LinqRoleProvider" enabled="true" cacheRolesInCookie="true" cookieName=".BLOGENGINEROLES">
  <providers>
    <clear/>
    <add name="LinqRoleProvider" type="BlogEngine.SQLServer.LinqRoleProvider, BlogEngine.SQLServer" connectionStringName="BE"/>
  </providers>
</roleManager>

 

Multiple-blog Configuration

To set stuff up for multiple blogs, you’ll need to run a script or two in your database and add a tag to all the providers. There are two script files (included in both the binary and source files), one for setting up the initial database changes (DatabaseSchemaChanges.sql—mostly adds tables) and another for adding the base values for a new blog (AddNewBlog.sql).

I wanted to make this easier by having the driver do the updates for you. That may still happen in the future, but since BlogEngine.Net itself requires manually running a script if you want to use the database provider I decided not to sweat it too hard. Presumably, anyone running in a database has to be running scripts manually anyway so this isn’t going to be a show stopper.

The provider will run just fine after running either script, even if you aren’t using multiple blogs. In other words, just because the database changed doesn’t mean that the single-blog installation is hosed. The exception to this is the “be_Settings” table. If you’re going to run for a while with a single-blog after running the first script, you’ll want to add a default to the BlogId column so it doesn’t choke when you insert and update settings.DefaultBlogId

Both scripts are “templated” so you can change key factors (a table prefix on the first and a couple of blog values in the second). Filling in the template is a matter of hitting ctrl-shift-M in Query Analyzer or SQL Server Management Studio. That’ll bring up a prompt for what values you want those template variables to have.TemplatePrompt

The final thing to setup is to add a multiblog attribute on the providers. That’ll make your providers look something like this.

 

<add name="SQLBlogProvider" type="BlogEngine.SQLServer.SqlBlogProvider, BlogEngine.SQLServer" connectionStringName="BE" multiblog="true"/>

 

 

The provider selects the blog it wants to deliver based on three configured values.

  • Host is the base address. The provider matches the Host value against the end of the host (so rabidpaladin.com will match “rabidpaladin.com”, “www.rabidpaladin.com” and “blog.rabidpaladin.com”).
  • Path is the rest of the Url. The provider matches the Path value against the start of the requested path.
  • Port is the port (if any) in the Url. Honestly, I threw this one in there as much for my testing as for any real-world use I expect it to see.

Tags

One thing I added (at the provider level) is that when a post comes in without any tags, the provider takes a moment to scan for tags in the post body. This is a feature I did the initial work for in Subtext so porting it over was a matter of a couple minutes. Any time a post is inserted into the database, the provider checks if it has tags yet. If no tags are present, it will scan the content for appropriate anchor markup (like those produced for Technorati tags). That means that on import, my posts all had their tags correctly populated—saving me a lot of extra work (or face losing tags on imported posts). That I was able to avoid the brain-damaged tag handling of BlogEngine.Net is just a bonus (they lower-case tags on creation and then re-capitalize them when serving them up).

Other Stuff

As I said, this should get you set up. Since I used this blog provider from the start on both my blogs, I can verify that the import tool works just fine in a multi-blog configuration. As far as BlogEngine.Net is aware, it’s doing the same stuff it always has. Indeed, the only change I made from BlogEngine.Net’s standard v1.4.5 release was in UrlRewrite.cs to allow links produced by Subtext to still work (so I don’t throw errors on old links).

 

else if (url.Contains("/POST/") || url.Contains("/ARCHIVE/"))

 

I submitted a patch at one time to have this hit the base source code but apparently it wasn’t deemed worthy.

Also, I found that running the provider in IIS7 is a bit tricky. Since BlogEngine.Net loads extensions from the database on application start you’ll get errors if you are configured for “Integrated” mode. That’s because “Integrated” mode (quite properly) fires the application start event before the HttpContext.Request is populated (which is what I’m using to determine what blog is being requested). Setting the application pool to “Classic” mode will solve this “problem”.IIS7ClassicMode

Looking Forward

My blogs are still running Subtext at their base addresses. I’m still not quite ready to take the plunge on BlogEngine.Net.  I am, however, undoubtedly one step closer.

author: Jacob | posted @ Thursday, April 02, 2009 5:26 PM | Feedback (11)

Multiple Blog Data


PartialSchema So I have a working LINQ to SQL provider for BlogEngine.Net. Now what? Given a little spare time, how about I see if I can’t use it to support running multiple blogs from the same installation? More importantly, see if I can use it to support running multiple blogs from the same database?

Doing just that turns out not to be all that difficult.

Scheming

The current architecture for BlogEngine.Net’s data already has a bit more cohesion than it technically needs. All the objects have their own individual Ids and those Ids are used to relate objects to each other (though there is one exception). Since every object already has its own Id (usually a Guid), splitting objects into separate blogs isn’t the chore it might otherwise have been.

There are two options when it comes to dividing items up into multiple blogs. First, each object can have a column added to its table to indicate which blog it is associated with. Second, you can create a cross-reference table that associates a blog Id with the object Id for the blog.

Columns
My initial impulse in most cases would be to add a BlogId column to the tables where it is needed. The reason is simple: objects belonging to the blog are in a true parent-child relationship and that relationship is generally best expressed as a field on the child indicating its parent. The relationship can (and really should) be enforced with a foreign key constraint on the column to ensure that the relationship is intact.

Cross-references
Having cross-reference tables is a bit more problematic and carries with it some maintenance and performance concerns. Not only does it force a join when you want to read the objects for a specific blog, but it means that insert, update, and delete commands now have to involve two tables instead of just one. One advantage of cross-reference tables is that they’re easier to extract back out if you need to devolve your data. Additionally, foreign key constraint integrity is triggered when the cross-reference entry is created instead of on your blog objects themselves—making your touch a bit lighter if you have other actors in the system.

Complicating Things 
No decision is best for every occasion, and when it came time to design how I wanted multiple blogs to work, I was really reluctant to mess with the native tables of BlogEngine.Net. I’m not sure if my hesitation is a matter of respect for a project I’m not involved in or if I’m just being unreasonably squeamish, but I eventually chose to go the cross-reference route. My main reasoning is that I wanted my intrusions to remain light and easily devolved.

I ♥ Linq

Now, normally, adding a super-structure on top of an existing infrastructure is a real pain. Editing all your SQL statements manually becomes an exercise in precision string manipulation and if you’re working through stored procedures… ugh. Linq made this really easy.

Here’s an example from the FillProfiles method of the blog provider.

var profileData = from p in context.Profiles
                  select p;
if (isMultiBlog)
{
    profileData = from p in profileData
                  join bp in context.BlogProfiles on p.ProfileID equals bp.ProfileId
                  where bp.BlogId == Utils.GetBlogId()
                  select p;
}

The initial select is good for the general case. It pulls all the objects from the Profiles table. Adding a filter when we have multiple blogs is added in the if clause. Note that the second select references the first (“from p in profileData”). Linq knows that the second “from” is a refinement of the first and puts them together logically. Since Linq defers execution of the query until it’s actually used, the query sent to the server includes the full constraint (i.e. filtering happens on the database). Here’s the statement that’s actually sent.

SELECT [t0].[ProfileID], [t0].[UserName], [t0].[SettingName], [t0].[SettingValue]
FROM [dbo].[be_Profiles] AS [t0]
INNER JOIN [dbo].[be_BlogProfiles] AS [t1] ON [t0].[ProfileID] = [t1].[ProfileId]
WHERE [t1].[BlogId] = @p0

This method ensures that you only take the hit of the join if you are in a multi-blog setup. And without pulling everything to the client.

Settings

I had some fun with the Settings table because it is an exception to BlogEngine.Net’s Id rigor. It has interesting impact on the Linq situation, but I think I’ll give it its own (short) post later.

Beta Available

So I tested this in my own home-grown environment and it seems to work as expected. In consequence, I’ve created a new release at the project homepage. I’m calling it a beta, though it barely warrants the label. I worry that it has only been tested in a single environment. If you’re a hearty soul and a BE.Net user, please give it a go. I’ll be spending some time getting it set up and tested in an actual public setting with my personal blogs here shortly. As always, I welcome feedback either at codeplex or comments or via email.

author: Jacob | posted @ Friday, March 27, 2009 5:00 PM | Feedback (0)

WCF With GP Web Services


I’m at Convergence this week in New Orleans. If you’re unfamiliar with the conference (and don’t want to follow the nifty link), all you really need to know is that it’s Microsoft’s convention for their business solutions products. For me, that means Dynamics Great Plains.

I bring this up because in the last session I attended yesterday, Louis Maresca mentioned a problem I remembered having with GP Web Services. GP WS has a serious problem when you first instantiate the proxy object: it can take seconds (over 30 on our older systems—I put a timer in just to verify) to instantiate the web service proxy. The reason for this is that .Net queries the service to pull down the available methods and objects on instantiation. Since there are very many of them available in GP Web Services, the query and xml serialization adds up to quite a lot.

Now, his solution was very clever, but involved creating a new web service to slim down the contract retrieval. My solution was to saddle up and use WCF. You see, WCF doesn’t do silly things like query for contracts it already knows full well about. I cracked how to use WCF with GP Web Services about a year ago and I haven’t looked back since.

In that session last night I realized that others might want to know what it took to get it working (and thus a blog post was born…) I’m not going to go though creating the WCF bit. It’s pretty straight forward and explained all over.

Crap, I find I can’t actually proceed without at least giving an overview.

  1. Right-click your project.
  2. Select “Add Service Reference”.
  3. Fill out the dialog:

GPWSServiceReference

Okay, now that I got that out of my system there are two things that prevent WCF and GP Web Services from playing nice together.

Security

Since GP WS uses your windows identity to validate things like roles and permissions, your client needs to send the correct identity or “bad things can happen”™. In VS 2005 web services, this was a simple matter of setting .UseDefaultCredentials to true. In WCF it’s a good bit more complicated. It’s a mirror of remotely printing Reporting Services using WCF, though, so techniques used there are applicable (though slightly different).

First, you have to let the binding know the correct security mode and transport. I did this in a basicHttpBinding in the <security> section:

<security mode="TransportCredentialOnly">
  <transport clientCredentialType="Ntlm" />
</security>

I came at this setting obliquely and after much trial and error. I’m not sure why clientCredentialType=“Windows” didn’t work against GP WS when it worked with Reporting Services. Probably something quirky in our environment.

This alone is not enough, however. The binding setting is just the contract. To actually use the correct credentials, your proxy has to be told what to do. Not difficult, but easy to overlook when you’re coming from a 2005 web services background. Here’s all it takes:

DynamicsGPSoapClient service = new DynamicsGPSoapClient();
service.ClientCredentials.Windows.AllowedImpersonationLevel 
    = TokenImpersonationLevel.Impersonation;

Once that’s all taken care of, you’re set to go. Those two lines of code are processed pretty much instantaneously on even our slowest clients, so problem solved. Almost.

Errors

Error handling hung me up for a while and was the final hurdle to being able to truly implement WCF with GP WS. I was so excited when I finally figured it out that I blogged it at the time. The key point is that GP WS wants to check a user’s authorization to view errors before giving up the details of what happened so you have to hit the web service again for details. Thus, while the status message is informative, you only get a GUID for detail in the initial error. This is not a bad thing, but it leads to difficulties when putting together your excuses to the user—particularly when WCF doesn’t make it easy to get at the details of an untyped FaultException.

Simple as That

From here, everything is pretty much the same. You have your objects in the domain you specified in the “Add Service Reference” dialog given above (GPService in my screenshot). Your proxy object has the methods you can use.

author: Jacob | posted @ Wednesday, March 11, 2009 9:20 PM | Feedback (0)

Gratuitous Use of Linq


PowerShovel Every now and then I get to doing something just because... well, because I can. These projects usually atrophy before becoming anything usable and serve more as a way to explore and practice than anything else. Usually. My latest tangent actually got to a state where I can let it loose in the wild and it’ll probably actual do what it is supposed to do.

BlogEngine.Net

Let me be perfectly clear up front: I don’t actually use BlogEngine.Net at all. Anywhere. I’m still a Subtext guy when it comes to blogging software. BlogEngine.Net still lacks critical features and that prevents me from using it as yet (primarily running multiple blogs from a single installation/database).

That said, BlogEngine.Net is a lovely little product with a lot to like about it. The extensibility model is extremely easy to use and flexible. The theming doesn’t suck. And its architecture is easy to navigate/understand even while it makes investments in areas I consider likely to payoff.

Data Access

While I like the product, the data access bugs me more than a little. It uses a provider model and includes built-in XML and database providers. These are good things. For flexibility, the database provider uses System.Data.Common and the DbProviderFactory with string-built commands. This structure allows BlogEngine.Net to be able to use any database that has a .Net data provider (including things like MySQL, SQLite, or VistaDB). Incidentally, they downplay (unintentionally?) this feature on their website saying in their FAQ that they support XML and “the SQL Server provider”.

At any rate, here’s their SelectPage implementation as an example

 

public override Page SelectPage(Guid id)
        {
            Page page = new Page();
 
            string connString = ConfigurationManager.ConnectionStrings[connStringName].ConnectionString;
            string providerName = ConfigurationManager.ConnectionStrings[connStringName].ProviderName;
            DbProviderFactory provider = DbProviderFactories.GetFactory(providerName);
 
            using (DbConnection conn = provider.CreateConnection())
            {
                conn.ConnectionString = connString;
 
                using (DbCommand cmd = conn.CreateCommand())
                {
                    string sqlQuery = "SELECT PageID, Title, Description, PageContent, DateCreated, " +
                                        "   DateModified, Keywords, IsPublished, IsFrontPage, Parent, ShowInList " +
                                        "FROM " + tablePrefix + "Pages " +
                                        "WHERE PageID = " + parmPrefix + "id";
 
                    cmd.CommandText = sqlQuery;
                    cmd.CommandType = CommandType.Text;
 
                    DbParameter dpID = provider.CreateParameter();
                    dpID.ParameterName = parmPrefix + "id";
                    dpID.Value = id.ToString();
                    cmd.Parameters.Add(dpID);
 
                    conn.Open();
                    using (DbDataReader rdr = cmd.ExecuteReader())
                    {
                        if (rdr.HasRows)
                        {
                            rdr.Read();
 
                            page.Id = rdr.GetGuid(0);
                            page.Title = rdr.GetString(1);
                            page.Content = rdr.GetString(3);
                            if (!rdr.IsDBNull(2))
                                page.Description = rdr.GetString(2);
                            if (!rdr.IsDBNull(4))
                                page.DateCreated = rdr.GetDateTime(4);
                            if (!rdr.IsDBNull(5))
                                page.DateModified = rdr.GetDateTime(5);
                            if (!rdr.IsDBNull(6))
                                page.Keywords = rdr.GetString(6);
                            if (!rdr.IsDBNull(7))
                                page.IsPublished = rdr.GetBoolean(7);
                            if (!rdr.IsDBNull(8))
                                page.IsFrontPage = rdr.GetBoolean(8);
                            if (!rdr.IsDBNull(9))
                                page.Parent = rdr.GetGuid(9);
                            if (!rdr.IsDBNull(10))
                                page.ShowInList = rdr.GetBoolean(10);
                        }
                    }
                }
            }
 
            return page;
        }

 

I want to reiterate that I’m not ripping on their choice to use this data access methodology. If you want the flexibility to use any .Net supported data provider without a third-party dependency, this is how you get it. You could optimize some of the stringiness, but with trade-offs.

Linq, Linq, Baby

That said, I don’t mind being tied to SQL Server and if I’m going to muck with the data layer (like, say, if I’m going to attempt multiple blogs from a single application instance) I want something simple that I can use without all that extra goo. I looked for any hint that this might have been done already, but I couldn’t find anything. It looks like I’m the only one with this particular manifestation of brain damage.

Since configurable table prefixes are a desirable feature and since that’s easier to do in Linq to SQL I figured it’d be best to go that route. It was good to get the table prefix stuff into a working project and have it work about as I expected it to.

Implementing the BlogEngine.Net blog provider turns out to be pretty easy in Linq. Ditto the Role and Membership providers. I tried to stay as close as possible to the DbBlogProvider. Even so, I found that some of the admin components are picky enough that even little things could bite me (the category editing page blows up if you leave category descriptions null, for example).

For compare and contrast, here’s my SelectPage method using Linq to SQL

 

public override Page SelectPage(Guid id)
{
    Page page = null;
    using (Data.BlogDataContext context = getNewContext())
    {
        Data.Page pageData = (from p in context.Pages
                              where p.PageID == id
                              select p).FirstOrDefault();
        if (pageData != null)
        {
            page = new Page()
            {
                Id = pageData.PageID,
                Title = pageData.Title,
                Description = pageData.Description,
                Content = pageData.PageContent,
                Keywords = pageData.Keywords,
                DateCreated = pageData.DateCreated.HasValue ? pageData.DateCreated.Value : DateTime.MinValue,
                DateModified = pageData.DateModified.HasValue ? pageData.DateModified.Value : DateTime.MinValue,
                IsPublished = pageData.IsPublished.HasValue ? pageData.IsPublished.Value : false,
                IsFrontPage = pageData.IsFrontPage.HasValue ? pageData.IsFrontPage.Value : false,
                Parent = pageData.Parent.HasValue ? pageData.Parent.Value : Guid.Empty,
                ShowInList = pageData.ShowInList.HasValue ? pageData.ShowInList.Value : false
            };
        }
    }
    return page;
}

BlogEngine.Net for SQL Server

So I got the thing working and thought I’d open it for “the community”. Since BlogEngine.Net is on CodePlex, that was a natural choice. Anyone sharing my peculiar proclivity is invited to head on over, take a poke and let me know what can change for the better. Or better yet, submit a patch. Or better, better yet, join the project. (also: CodePlex’s recent transparent svn compatibility is awesome! When did that happen?)

If you do poke at the project, some forewarning. First, I don’t have unit tests for this and don’t plan on any (I’m not against using them, I just don’t want the headache of creating them myself). Data access is more in the realm of “integration testing”, so I’m not sure there’s much you can really do that’s actually useful. It might be different if BlogEngine.Net had a suite of tests I could use to validate my providers against, but...

Second, I haven’t gone out of my way to do a lot of commenting. This is deliberate. These methods are short, should be self-explanatory, and since they should be hidden behind a provider I didn’t even bother with the typical XDoc stuff. Anything directly accessing them such that intellisense comes to bear is doing something wrong...

Room to Improve

In the end, this is the ground-work to remove an (admittedly trivial) barrier in working with BlogEngine.Net. I hope to help move as much as possible into the database in order to support things like multi-blog configurations. BlogEngine.Net hasn’t been very disciplined in its storage layer and XML really is the default medium. Things like referrer tracking don’t use a provider at all so you really can’t (yet) get away from XML files in your App_Data directory (and hence, read/write permission configuration). Since I want that to change, I’m going to see if I can’t get that going a bit.

Things to do
  • Decide on a database update methodology (so far, I’m using the already-existing tables and hence piggy-backing on the BlogEngine.Net scripts)
  • Replace the built-in ReferrerModule (use/create provider model access for referrers?)
  • Comb the project for any other errant XML dependencies
  • Work on multi-blogging configuration

author: Jacob | posted @ Wednesday, March 04, 2009 1:11 PM | Feedback (6)

So You Think You're An Admin?


Access Button I had an interesting problem crop up trying to run my own application this week. We have a routine that uses an excel spreadsheet to import orders into Dynamics GP that includes some twists that aren’t handled well by Integration Manager. Since the application runs from the network (using ClickOnce) and because these orders can be substantial and represent a commitment of corporate resources, we want some control over who can run them. Specifically, we use Active Directory group membership with hard-coded/defined groups.

One of the groups I want to allow is Domain Admins. And yes, this is a kludge. All three members of our small IT shop are Domain Admins—mainly so that we can act as backup when the others are unavailable. It’s a handy kludge, though, so lump it. Unfortunately, when running from my machine (running Vista), the user token being used to check Identity.IsInRole() wasn’t admitting that I am, in fact, in the Domain Admins domain group

This is, by the way, the first I’ve run into an inversion of Works on My Machine™.
 works-on-my-machine-starburst-not

The Problem

It wasn’t terribly difficult to figure out what was wrong. The key to the problem is that Vista UAC (which I actually rather like because I want to know when programs undertake certain activities) creates a “split token” when you login using an account with admin privileges. The user actually runs using the filtered token that removes the dangerous things and only elevates (with user notification and approval) when those privileges are actually needed.

So when I asked WindowsIdentity.IsInRole("COMPANY\Domain Admins") it told me that it’s never heard of that role and certainly I’m not a member of it. This was disconcerting.

Now the problem goes away if you start Visual Studio with “Run as Administrator” or start an application with a shortcut with that setting. Which works fine (not great) while developing (if you remember to start VS as administrator) but eventually I got tired of it and sometimes I want to run the deployed app from my box. There’s just one small hitch. Remember that I mentioned that we deploy the app to the network using ClickOnce? It turns out that there’s no good way to start a ClickOnce app with elevated privileges. Googling around (and even checking Stack Overflow) I found some people who wrote what were essentially batch files or ran services that could then be used to elevate processes either to run ClickOnce apps or to allow ClickOnce apps to do stuff that requires elevation. But really, that’s a lot of hassle for something I just knew had to be simpler.

The Solution

After beating my head on the problem for a bit, I eventually took a step back and asked myself that crucial dev question: “What am I actually trying to accomplish here?” I need to remind myself to do that sooner when I find myself “brought to Point Non Plus” (as Georgette Heyer’s characters might say). It turns out to be a good question and one that led to the “Duh” moment I share with you now.

Since I’m not actually doing anything that requires admin privileges, going for process elevation is a complete waste of time. All I really want to know is if the current user is part of a specific Active Directory group. Didn’t I see something about .Net Framework 3.5 and managed domain objects? Why yes! Yes I did!

The nifty little buggers are in System.DirectoryServices.AccountManagement and if you do anything with Active Directory domains you owe it to yourself to give this namespace a once over. Here’s what I ended up with:

bool isAllowed = false;
WindowsIdentity wi = WindowsIdentity.GetCurrent();
using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, "COMPANY"))
{
    UserPrincipal up = UserPrincipal.FindByIdentity(pc, wi.Name);
    GroupPrincipal gp = GroupPrincipal.FindByIdentity(pc, "Domain Admins");
    if (up.IsMemberOf(gp))
        isAllowed = true;
}

This worked right out of the chute. Well, getting the right AD group membership didn’t want to work when using the Principal.IsMemberOf(PrincipalContext, IdentityType, string) overload, but pulling down the actual GroupPrincipal looks cleaner anyway, I find.

author: Jacob | posted @ Friday, February 13, 2009 1:56 PM | Feedback (2)

Professional Values


Dome Scratcher One of the things I am seeing less of lately is the understanding that reasonable people can and will disagree with one another—without either of them being any the less reasonable or intelligent for doing so. It seems to me that people become so invested with the “rightness” of their ideas that they deny the possibility that those who disagree with them may be equally intelligent and well-informed. You see it a lot in politics, but I think that this attitude has crept into development discussions as well.

I saw a manifestation of this in action after a recent Stack Overflow podcast wherein Joel had the temerity to question Robert Martin’s SOLID principles (SOLID was the topic of a recent podcast with Scott Hanselman that Joel had apparently heard). I highly recommend both podcasts. It didn’t take long for some bright stars in the Alt.Net universe to talk about Joel jumping the shark or the state of his imperial wardrobe. I’m not sure why the impulse to denigrate those who disagree with you is so strong, but it appears to be nearly universal. We go from the belief that somebody is wrong to the conclusion that they are incompetent or ignorant without taking time to draw breath, really.

I think this impulse is not only wrong, but damaging. It represents a voluntary limitation of our ability to engage in important dialog and stretch our understanding.

Education

One crutch of those who participate in this hidden hubris is the belief that people who disagree must be missing data. You can see this most commonly when someone tells you to “educate yourself” or its milder form “try it and you’ll see”. The underlying message in those statements is that if you knew what they know, you’d do what they do.

And that may be correct. It really could be the case that someone is missing data and if they had that data they might agree. Since software development is so change-driven, much of development blogging is motivated by the desire to teach others things they might not have heard about before. You have to honor the often unrewarded efforts of those who take the time to put information out there for the benefit of those seeking education.

The problem is that telling someone who questions your tools or methods to educate themselves is arrogant, even if you are completely sincere and honestly well-meaning. You see, there are really only two possibilities for someone questioning you: they lack data or they have arrived at a different conclusion after weighing the data themselves.

In the first case, telling them to educate themselves notifies them of your superiority (you are in possession of data they lack) at the same time you deny access to yourself as a resource (you are directing them elsewhere for acquiring that data). It’s a dismissive brush off. In the second case, telling them to educate themselves is a judgement of their experience and conclusion without the benefit of explanation or debate. In both cases you are saying that you are better than they are and that they shouldn’t be allowed to participate in the discussion as a result.

If you honestly believe that someone disagrees with you due to lack of information, a better response would be a series of questions or even challenges geared towards examining their point better. “Have you considered” questions or “I disagree because” statements are more helpful; they give others the chance to respond and have the courtesy of taking someone seriously enough to invest in the discussion.

Weighty Matters

What if I’m already educated and I still disagree? What if I’ve done the recon, seen the scene, danced the dance, bought the souvenir and I still beg to differ with your Great Truth? How can two intelligent people look at the same data, having access to the same information, and arrive at two legitimately different conclusions?

Allow me to educate you. (heh)

You see, most decisions are complex and involve competing principles. Do I take the time to add an abstraction layer that will be useful later or do I YAGNI my way through with the simplest solution that works right now? Do I hassle with System.Data.Common to support multiple database providers or allow a strong dependency on SQL Server? Do I use test first to drive out my design or am I confident that my planned design is decoupled enough and tests ex-post-hackto will be adequate? All of these decisions break down into factors that we weight according to our experience and expectations.

I’ll illustrate what I mean. Lets take, for example, deciding if Georgette Heyer is better than Meg Cabot. It can be argued (and I would, indeed, so argue) that they have comparable ability with characterization, prose, story, and plot. However, Meg Cabot sets her characters in modern settings and Georgette Heyer’s books are chiefly set in Regency England. Two people could easily disagree over which author is best if one has a strong weight on “historical regency setting” without either being any the less intelligent or in need of education.

Back to software development. Consider some of the factors that might go into deciding to use TDD.

  • Force strongly decoupled design
  • Ensure a minimum level of test coverage
  • Early API exploration and (pseudo) documentation
  • Commit project to a test framework
  • Commit project to a mock framework
  • Commit project to automated testing infrastructure
  • Writing tests without the benefit of intellisense

How you weight each factor will determine your judgement of TDD. Someone who is not worried about forcing decoupled design (either because decoupling doesn’t pay off in their environment or because they feel they can achieve decoupling without force) will be less likely to chose TDD for their project.

And it gets even more complicated when you realize that each factor can, in turn, be comprised of additional factors with their own weight and trade offs (you could easily break “decoupled design” down further, for example).

Learning to look for underlying principles and the weights that others may apply can be interesting as well as useful. More importantly, it opens up the gray areas and opportunities for honest disagreement (without denigration) that are vital when working with IRealWorld.

Educating Yourself

As software professionals, we owe it to ourselves, our employers, and our clients, to be educated in our craft. That’s a not inconsiderable burden in a field that grows by leaps and bounds year after year with no sign of slowing any time soon. It is hard enough learning all the concepts, patterns, and practices (not to mention tools, environments, and platforms) that it is often tempting to find a core of experts that you rely on to make decisions for you (and there is some benefit in doing so initially as you come up to speed on the intricacies of our field).

But to be truly educated, you have to go beyond what your experts say and learn about the principles that exist underneath. That’s extra work but more importantly it is extra responsibility. It may be that when you break a practice down into its constituent principles that you will find yourself in a situation where “Best Practices” aren’t, in fact, best. To me, it is the ability to not only make that determination but to then act on it that truly makes a “professional”.

author: Jacob | posted @ Friday, February 06, 2009 7:17 PM | Feedback (5)

Appreciating Alt.Net


Appreciation I’m on record as dissenting from some of the planks of the Alt.Net bandwagon. I question design for testability and have extended that to questioning what I consider to be the over-use of Dependency Injection (though I’ve also talked about using it successfully in a project that I believe warranted its use). Further, in my last post, I asked if the Alt.Net folks couldn’t expand their treatments of design principles to include contra-indications or fault points. I also decried those who actively stifle alternative viewpoints, though I left it vague about who I think might do so.

Given all of that, it’s easy to see how some Alt.Net folks might see me as an antagonist and read my posts from an adversarial perspective.

For the Record

Realizing this, I thought I’d take a post to set the record straight. I’m glad that the Alt.Net movement exists and that there are so many quality .Net developers putting so much effort into that community. Software development is a non-trivial task with a huge range of concerns and diversity of environments. Having a group of people with an eye on broader software developments who then take the time to translate advances in other spheres into .Net is an invaluable service. Many folks in the Alt.Net crowd not only teach and explain software development advances, they work with awesome intensity to bring tools to the .Net environment that enable and promote these methods and practices.

Their passion and energy have advanced .Net development immensely and I would do nothing to take away from or diminish their achievements. We (as a .Net community) benefit from their work even if we don’t implement the practices, tools or patterns they espouse. Not only do we gain from the visibility they give our platform, but we enjoy improvements that make it back into our development environment (whether through Microsoft or through tools vendors who incorporate and enable better designs).

Elitist?

So is the Alt.Net community elitist as they have been accused by some? I’d be careful about making such an accusation. Any group is going to be “elitist” if only from the simple fact that for a group designation to be useful you have to have people both inside and outside that group. Attributing a personality to Alt.Net is complicated, though, by it’s amorphous definition. There’s no official designation, no tribunal, no excommunication procedure—there’s not even an official, endorsed doctrine. Alt.Net is a convenient designation to describe a general group of practices (and/or developers who favor or promote those practices) the details of which are in a more or less constant state of flux.

Generally speaking, those who identify themselves as a part of Alt.Net are some of the best and brightest in the .Net development community. They’re smart and they have strong opinions (however loosely held) and they are willing, even eager, to express those opinions when given half a chance. Smart people expressing strong opinion can come across to others as elitist but I don’t think that is an accurate description of Alt.Net as a rule. At worst, you can describe them as having high standards and the guts to promote the advantages of adhering to those high standards.

That’s not to say that there aren’t those in their ranks who sometimes behave arrogantly. As I described in my last post, I was stung by the public remarks of one prominent Alt.Netter and his casual disregard for my post and for me personally. I wouldn’t, however, paint that incident as either representative of Alt.Net as a group, or even as representative of that specific person.

Painting the Alt.Net community as “elitist” can be simply an excuse to disregard them and their practices and I think doing so is a mistake. Even if they were a bunch of elitist jerkwads (which, again, I do not consider them to be), the fact remains that their ideas and principles are valuable and ignoring them will only hurt you. I may disagree with the broad applicability of Dependency Injection, for example, but I’m glad I learned the concept from them so that I was able to use it when I ran into a situation that was pretty much tailor-made for that pattern.

Transparency

One of the reasons Alt.Net suffers the slings and arrows of their detractors is that they take pains to be transparent. Their email list is open (and public), many of them are prolific bloggers, and they aren’t afraid to speak up when they disagree with each other. This transparency can lead to unfavorable comparisons to organizations that are less open (like, say, the Patterns and Practices group at Microsoft). Such comparisons are misleading at best.

Laying your disputes out for public observation can make you look weaker than you really are because people will use those disputes as an excuse to ignore your strengths.  It’s easy to miss the reverse side of that coin—being open allows your ideas to be broadly tested and honed. It takes a certain degree if intellectual fortitude to operate transparently, but being able to do so leads inevitably to stronger solutions, principles, and practices.

Those who are willing to put their ideas out there with warts and all deserve respect. I, for one, am happy to give them the respect they’ve earned (even as I remain willing to question individual applicability in my own development).

author: Jacob | posted @ Wednesday, January 21, 2009 5:38 PM | Feedback (0)

Professional Development


Baby Developer Many of the interesting .Net bloggers are part of the Alt.Net crowd; evangelizing Dependency Injection, Design for Testability, Test Driven Development, SOLID design and other development practices that they find useful in their work. It doesn’t take long reading these blogs to pick up on what looks like an unforgiving attitude towards those who don’t use the latest acronyms in their software development.

This acrimony is unfortunate because most often what is at the heart of those who question the standard Alt.Net toolset isn’t so much principle as it is context.

A Fundamental Assumption

Unfortunately, discussing software development at all imposes an initial assumption that too often goes unexamined—the assumption that the software development you are doing is substantially similar. I’m afraid that this assumption is violated more often than we realize.

Most prominent development bloggers are consultants, conference speakers, tool vendors, or framework providers of one sort or another. As such, they are used to considering the problems of very large applications with many cross-cutting concerns. Is it any wonder, then, that they are strident about the importance of segregation and testing? For the majority of their hands-on development, everything that increases separation and independence is a benefit because isolating fail points and mitigating the cost of touching individual elements carries a heavy multiplying effect on overall complexity. No wonder they want to use a DI framework on every project and insist that other developers practice TDD. It’s all up-side in such projects because the costs disappear in the noise and the benefits stand out.

Not all of us work in those kinds of environments and/or applications, however. Many of us work on projects that are wildly dissimilar. Our applications are narrowly focused, single use, and with limited distribution. For us, implementing solution-wide DI eats up half the project configuration overhead and delivers functionally zero chance of eventual benefit.

I’m unsure how many find themselves in similar (smaller) development shops. I suspect quite a few. We might even be a majority of actual, working developers. We’re pretty much absent from the development blogger population, however.

A Problem

What that means for us small-shop developers is that we end up having to do a lot of on-the-fly translation. Up-scale bloggers have shown themselves averse to “thinking down” so we end up having to analyze the methods and techniques they discuss to see where the fail points are for our projects. If your QA team is the folks in the warehouse and your architecture a straight-on table insert, is TDD really going to pay off? What about bug tracking? Or continuous integration?

These translations are interesting and part of why I like smaller development. Our problem space is largely undiscovered country. We can’t really trust anything labeled “best practice” and we have to do our own experiments to find what works and what doesn’t and at what point it starts to make sense to engage different methodologies.

It’d be nice, though, if some of the gee-whiz bloggers would discuss the fail points of their methods and tools. Or point out the trade offs that various techniques are likely to entail. No technique or method is so useful that it is universally applicable, so where are the borders?

Worse, though, are those who actively stifle discussion of such fail points. Mention that you’d rather not use Dependency Injection, for example, and you can find your professionalism and/or intelligence questioned. Indeed, for many the very fact that you don’t <insert favorite practice here> indicates that you must never have tried it. Or if you have you must have been doing it wrong.

This friction fosters resentment and shuts down discussion. Perhaps one reason we don’t see more developers willing to discuss alternatives to the popular band wagons of today is a reluctance to enter what amounts to a hostile arena. As the troubles on Wall Street filter down to Main Street (where us small-shop developers live) the last thing we need is other developers calling us unprofessional in public. Particularly when those other developers are well-known and respected in their community.

I know this from personal experience. I’m pretty secure in my current position. My company is healthy and has a marked competitive advantage that I am key to delivering. My boss respects me as do the owners of the company. Even so, my gut clenched tight and my heart sank to my knees when I came across a twitter message from a luminary in our field (someone any informed .Net developer would recognize) dismissing me as unprofessional and my points as unworthy of consideration.

Seeing something like that will make you wonder if expressing your doubts is worth the risk, even when it isn’t directed at you.

author: Jacob | posted @ Wednesday, January 14, 2009 6:21 PM | Feedback (26)

I'm a Lumberjack


Lumberjack And I’m okay. Indeed, last week saw a payoff for one of those hygiene things you do because you know that you “should”.

A Logging Story

Allow me to share what happened (feel free to skip this section).

We receive orders from a number of different sources. In addition to EDI, we have spreadsheets, flat-files, and we even originate a few ourselves under Vendor Managed Inventory (VMI) agreements. One of the things we’ve done recently is create a single “process pipeline” such that once orders enter the pipeline, their processing from that point forward is uniform. Thus, a new client can send us whatever they want to (or can), and all we have to do is get the order details into a set of order import tables and away they go. i.e. we create a single customer-specific import routine to start their orders off instead of an entire order-processing process (yes, that was the previous solution when adding new customers—don’t ask, it wasn’t me).

So yeah. One entry point into the pipeline (it isn’t fancy enough for the appellation “workflow”) is a set of order import tables. A simple console application is called by the import routine that created the entries. All it does is take the orders identified and create them in Dynamics GP. It is generally called by nightly import processes that run unattended.

This routine has been running for months now and frankly, I don’t think about it much. That is, until last Thursday when I received an email with the subject “ImportOrders Log: Error”.

The cause of the error isn’t really important (one of our customer records hadn’t been updated with the new shipping method and GP Web Services choked). In a batch of 146 orders, the last 5 were skipped. It took me fifteen minutes to determine what had happened and why and another fifteen for our data guy to put together a fix—all before the rest of that order had been processed for the day.

Our internal order processors would never have caught this error so logging and notification saved us a couple of angry phone calls and pissed off store managers (and hence account managers and executives).

Chainsaws

So here’s the rule:

Any process that will run unattended must have robust logging that will not only proactively notify you of any errors, but that also allows you to rebuild the circumstances and conditions surrounding errors encountered.

There are a number of good logging toolsets out there. I highly recommend finding and using one rather than “rolling your own”. A logging toolset will give you more flexibility and stability than you could give yourself and frankly, who needs the headache of slaying a dragon that has already been taken out so very thoroughly?

For this project I used the Enterprise Library Logging application block. Subtext uses Apache’s log4net. I’m willing to bet that there are others of similar quality. Key features include being able to refine (through configuration) logging levels and logging details like where the log is kept and what form it will take.

Deforestation

As with any good general rule, there is a real temptation for over-application. A little logging saved a ton of aggravation so imagine the benefit I’d get if I logged everything! Yeah, well, don’t forget your friend YAGNI when determining if and what to log. Pile up enough of these things and before you know it every little project can be so encrusted with “must haves” that you end up with huge unwieldy project frameworks.

Not that there is anything wrong with a good framework for enterprisey applications. But that’s not what I had here. All I have is a simple console app that runs when needed to import orders into Dynamics GP. All the potentially enterprisey stuff happens later. I needed logging that kept track of events and notified me of errors. I didn’t need much more than that.

author: Jacob | posted @ Wednesday, November 12, 2008 6:13 PM | Feedback (2)

Changing Table Names in an OR/M


Empty Sign I spent some quality time googling this and even went and asked the nascent Stack Overflow community and didn’t come up with a satisfactory answer. Being the intrepid sort, I opened up a test project and started poking around, compiling information from a number of sources and playing until I got something that worked. For your amusement and/or edification, I’ll document what I found.

What I Want to Do

The basic scenario is that many typical “commodity” web applications use databases to store their information. Since most web hosting services come with a single database but charge extra for additional databases, it is common for web-type products to add identifier text to their table and stored procedure names*. The .Net blog software I sometimes contribute to, Subtext, is an example. Take the table I added to hold tags associated to posts a while back, “subtext_Tag”. Using the “subtext_” prefix means that we won’t run into naming collisions on our tables if someone has a wiki or forum application that also contains a table for tag entities named “Tag”.

* And yes, as one user on Stack Overflow suggested, you could use schema as a differentiator so we could as easily have used a “subtext” schema and our tables would be “subtext.Tag” instead of “dbo.Tag”. That solution is much trickier to setup than a simple table naming convention, though, so I think I prefer the table prefix for now.

The .Net Way

While I was initial open to any .Net OR/M the fact of the matter is that the only one I know anything about is SubSonic. Now I like SubSonic a lot, but their tools are geared towards code generation and I couldn’t find a handle into runtime manipulation of table names (such might exist, but I was unable to find it).

Since I’m even less familiar with the other third party .Net OR/M data tools (like LLBLGen or NHibernate) and nobody on Stack Overflow (who tend to be knowledgeable about these things) spoke up, I decided to check out what it would take to monkey with the table names at runtime using stuff I have actually used. Namely the Entity Framework and LINQ to SQL. It turns out to be possible in either, though I have to admit to being surprised that it is easier in LINQ to SQL than in EF.

My Test Database

To keep things simple, I created a test database. Since I am a highly creative professional, I named it “Test” and created a table there named “TestTable”.

Test Schema 

LINQ to SQL

Instantiating a new DataContext object in LINQ to SQL includes a constructor that allows you to feed in a MappingSource derivative. By default, L2S uses an attribute mapping object that pulls the metadata from attributes on your classes—in this case the “Name” property of a TableAttribute.

[Table(Name="dbo.TestTable")]
public partial class TestTable : INotifyPropertyChanging, INotifyPropertyChanged

Since attributes are immutable at runtime, using the default isn’t an option here. Fortunately there’s an XmlMappingSource object that can use an XML file (or fragment) to do what I need. Unfortunately, generating an initial mapping file is a touch cumbersome and requires use of the SQLMetal.exe tool provided with Visual Studio.

Here’s how (after adding a TestLINQ.dbml file and dragging the table onto it—pre-name-change of course):

  1. I opened the VS Command Prompt (it’s in a Visual Studio Tools folder in the start menu).
  2. Changed the directory to my project.
  3. Entered the command “sqlmetal /map:TestLINQ.map /code TestLINQ.dbml”. This generates a TestLINQ.map file.
  4. Right-clicked on the generated file in my project and selected “Include in Project”.
  5. Set the file’s “Copy to Output Directory” property to “Copy Always”.

The mapping file is pretty simple. The relevant bit is the Name attribute of the Table element:

<Table Name="dbo.test_TestTable" Member="TestTables">
  <Type Name="LINQ.TestTable">
    <Column Name="TestId" Member="TestId" Storage="_TestId" DbType="Int NOT NULL IDENTITY" IsPrimaryKey="true" IsDbGenerated="true" AutoSync="OnInsert" />
    <Column Name="TestOne" Member="TestOne" Storage="_TestOne" DbType="VarChar(50)" />
    <Column Name="TestTwo" Member="TestTwo" Storage="_TestTwo" DbType="VarChar(50)" />
  </Type>
</Table>

Make sure the name is what you want it to be and you’re golden. Here’s the code I used to test it out after the name change.

XmlMappingSource source = XmlMappingSource.FromUrl("TestLINQ.map");
using (LINQ.TestLINQDataContext context = new LINQ.TestLINQDataContext(Properties.Settings.Default.TestConnectionString, source))
{
    LINQ.TestTable table = new LINQ.TestTable()
    {
        TestOne = "firstLINQ",
        TestTwo = "secondLINQ"
    };
    context.TestTables.InsertOnSubmit(table);
    context.SubmitChanges();
 
    table.TestOne = "thirdLINQ";
    context.SubmitChanges();
 
    context.TestTables.DeleteOnSubmit(table);
    context.SubmitChanges();
}
 

XmlMappingSource even has a .FromXml() method that will create your map from an Xml fragment string.

Entity Framework

As I mentioned before this one is harder. This is a surprise because EF is ostensibly created to make it easier to keep your object definitions separate from your storage definitions. The reason it isn’t easier is understandable once you realize that EF is made to be highly configurable and thus its definition files are much more complex than the L2S mapping.

The first problem with EF, though, is that the documentation is schizophrenic. Also confusing. That’s because MS rolled the original three configuration files into the .edmx definition file so references on the web imply that those files are easily seen and edited. Even more confusing is that EF actually still uses the .csdl, .ssdl, and .msl files at runtime—it just generates those files from the .edmx and either packs them in the assembly as a resource (by default) or as files in your output directory.

Well, to monkey with the tables at runtime, you have to have access to the configuration files. To do so you need to change the default in the “Metadata Artifact Processing” property of your ConceptualEntityModel and rebuild the project. That’ll put perfectly good .csdl, .ssdl, and .msl files in your output bin directory (You don’t have to leave it at "Copy to Output Directory" once you have saved these files off for your own use and abuse.)

EF did another funky thing by deviating from the norm in what it pulls from the connection string that you feed it. If you look at your EF connection string in app.config (or web.config) you’ll see something like this:

<add name="TestEntities" 
connectionString="metadata=res://*/TestEF.csdl|res://*/TestEF.ssdl|res://*/TestEF.msl;provider=System.Data.SqlClient;provider connection string=&quot;Data Source=localhost;Initial Catalog=Test;Integrated Security=True;MultipleActiveResultSets=True&quot;"
providerName="System.Data.EntityClient" />
 

Notice that there’s a “provider connection string” embedded in the connectionString attribute—that’s the “normal” connection string that tells EF what database to use for storage. Also notice that the metadata property tells EF where to go for those configuration files (in this case, it’s telling EF to look in the assembly for resources TestEF.csdl, TestEF.ssdl and Test.msl).

Armed with this information, I was able to add a set of generated config files to the project stolen from those generated in the output directory. Once there, you have to edit the storage file and the mapping file to use the altered names. The table name used by EF is taken from the Name attribute of the EntitySet element in the .ssdl file. This is unfortunate because the Name attribute is a reference used by things like associations. Which means that you have to make sure you alter all the references to that EntitySet as well (fortunately, these are generally referenced using an EntitySet attribute on the relevant association and thus are relatively easy to find).

<EntitySet Name="test_TestTable" EntityType="TestModel.Store.TestTable" store:Type="Tables" Schema="dbo" />
 

It’s not necessary to alter the EntityType, though I found that if you alter it consistently across the file it will work if you do.

The mapping configuration in the .msl file just has to be updated so that the objects use the correct EntitySet for storage.

<EntitySetMapping Name="TestTable">
  <EntityTypeMapping TypeName="IsTypeOf(TestEF.TestTable)">
    <MappingFragment StoreEntitySet="test_TestTable">
      <ScalarProperty Name="TestId" ColumnName="TestId" />
      <ScalarProperty Name="TestOne" ColumnName="TestOne" />
      <ScalarProperty Name="TestTwo" ColumnName="TestTwo" />
    </MappingFragment>
  </EntityTypeMapping>
</EntitySetMapping>
 

Once those changes are made your code will work with the altered table name, though you need to alter the connection string to look for the correct configuration files. Here’s the final code I used to test it out.

string connection = "metadata=TestEF.csdl|TestEF.ssdl|TestEF.msl;provider=System.Data.SqlClient;provider connection string=\"Data Source=localhost;Initial Catalog=Test;Integrated Security=True;MultipleActiveResultSets=True\"";
using (TestEntities entities = new TestEntities(connection))
{
    TestTable table = new TestTable()
    {
        TestOne = "firstEF",
        TestTwo = "secondEF"
    };
    entities.AddToTestTable(table);
    entities.SaveChanges();
 
    table.TestOne = "thirdEF";
    entities.SaveChanges();
 
    entities.DeleteObject(table);
    entities.SaveChanges();
}

So Which is Better?

Well, that depends on what you want to accomplish but then, doesn’t it always? For me, the LINQ to SQL solution is much cleaner because I’m simply not going to use all the other goo that the Enterprise Framework includes. Plus, the LINQ to SQL solution can use an XML fragment so I can bury that mapping piece wherever I want to, including in inline code. EF requires a file reference so those files have to be either in the assembly resources or on the file system. EF also allows you to leverage Asp.Net Data Services but that’s a topic for another post entirely...

author: Jacob | posted @ Wednesday, August 27, 2008 6:30 PM | Feedback (3)