EF 4’s PluralizationService Class: A Singularly Impossible Plurality
Entity Framework’s new 4.0 designer does its best to generate correct plural and singular forms of object names. This magic is done through the PluralizationService Class found in the System.Data.Entity.Design.PluralizationServices namespace and in the System.Data.Entity.Design.dll assembly.
[Before you ask… Yes, I’ll post my example page, the service, and the project source code as soon as my ISP makes ASP.NET 4 RTM available. Stay tuned.]
Anyone who speaks English is brutally aware of the ridiculous and inconsistent rules for constructing the plural and singular forms of nouns. I’m not sure how non-native speakers ever get a handle on the bizarre and senseless exceptions.
With that in mind, I was curious as to how well Microsoft fared with applying computer logic and lookups to an illogical language. I worked up a little WCF service and a Web page to make it easy to test.
The service returns a SingularPluralInfo object that contains the essential information about a word you provide:
Public Class SingularPluralInfo
Property PluralValue As String
Property SingularValue As String
Property IsPlural As Boolean
Property ErrorMessage As String
End Class
Because the PluralizationService can determine whether a word is a plural or not, I was able to put all the code in one function called MakeSingleOrPlural(). Users pass either the singular or plural of a word and get back both forms.
Public Function MakeSingleOrPlural(ByVal word As String, _
Optional ByVal culturestring As String = "en") _
As SingularPluralInfo Implements _
Iplservice.MakeSingleOrPlural
The first lines of the function initialize the variables and verify that the user has entered some text. Notice the use of the new String.IsNullOrWhiteSpace(word) function in .NET 4:
Dim spinfo As New SingularPluralInfo
Dim pls As PluralizationService = Nothing
spinfo.PluralValue = String.Empty
spinfo.SingularValue = String.Empty
spinfo.ErrorMessage = String.Empty
spinfo.IsPlural = False
If String.IsNullOrWhiteSpace(word) Then
spinfo.ErrorMessage = "No input provided"
Return spinfo
End If
The fun starts with calling the CreateService method which wants a CultureInfo object as a parameter. I wrapped this in a Try…Catch block because, out of the box, the service throws an exception if you try to feed it a non-English culture:
Try
pls = PluralizationService.CreateService _
(New CultureInfo(culturestring))
If pls.IsPlural(word) Then
spinfo.IsPlural = True
spinfo.PluralValue = word
spinfo.SingularValue = pls.Singularize(word)
Else
spinfo.IsPlural = False
spinfo.SingularValue = word
spinfo.PluralValue = pls.Pluralize(word)
End If
Catch ex As NotImplementedException
spinfo.ErrorMessage = "Sorry, only English is supported"
Catch ex As Exception
spinfo.ErrorMessage = ex.Message
End Try
The Singularize() and Pluralize() methods in the preceding code take the string into a black box and produce a result.
The client for the web service is a simple ASP.NET 4 page. While creating the page, I used new ASP.NET features to reduce the page’s overhead. For example, the declarations turn off ViewState, EventValidation, and use static element IDs. These three settings make a huge difference in the amount of data on the wire:
<%@ Page Language="VB" ViewStateMode="Disabled"
ClientIDMode="Static" EnableEventValidation="false" %>
<%@ Import Namespace="System.Globalization" %>
For markup, I added a TextBox, Button, DropDownList and some labels:
<asp:TextBox ID="txtInput" runat="server"></asp:TextBox>
<asp:DropDownList ID="ddlCulture" runat="server">
</asp:DropDownList><br /><br />
<asp:Button ID="btnConvert" runat="server"
Text="Convert" OnClick="btnConvert_Click" /><br /><br />
Singular:
<asp:Label ID="lblSingular" runat="server"></asp:Label><br />
Plural:
<asp:Label ID="lblPlural" runat="server"></asp:Label><br />
Error:
<asp:Label ID="lblError" runat="server"></asp:Label>
The Page Load event fills the DropDownList with the names of the cultures available on the IIS machine. Rather than store the data in bloated ViewState, I decided to cache it and fetch it anew on each postback.
Protected Sub Page_Load(ByVal sender As Object, _
ByVal e As System.EventArgs)
Dim q As System.Linq.IOrderedEnumerable(Of String)
q = Cache("cultures")
If IsNothing(q) Then
q = From c In _
System.Globalization.CultureInfo.GetCultures _
(Globalization.CultureTypes.AllCultures) _
Select c.Name Order By Name
Cache("cultures") = q
End If
ddlCulture.DataSource = q
ddlCulture.DataBind()
ddlCulture.SelectedValue = "en-CA"
End Sub
The real action happens with the click of the button and the postback. The following code instantiates the WCF service, creates a SingularPluralInfo object to hold the results, and passes the user input to the MakeSingleOrPlural() method:
Protected Sub btnConvert_Click(ByVal sender As Object, _
ByVal e As System.EventArgs)
Dim plsvc As New plservice
Dim spi As New SingularPluralInfo
Dim word As String = String.Empty
word = txtInput.Text
If String.IsNullOrWhiteSpace(word) Then
lblError.Text = "Please enter a word"
Else
spi = plsvc.MakeSingleOrPlural _
(word, ddlCulture.SelectedItem.Value)
lblError.Text = spi.ErrorMessage
lblPlural.Text = spi.PluralValue
lblSingular.Text = spi.SingularValue
End If
End Sub
Okay, it’s time to test the pluralization service with some eccentricities of the English language. In the table below, I’ve put some of the wrong results at the top. However, what’s remarkable is that the singularization and pluralization was correct for some very obscure words, such as viscus.
Input | Result |
movies | movy |
money | moneys |
moose | mooses |
census | censu |
movie | movies |
aircraft | aircraft |
goose | geese |
louse | lice |
woman | women |
tooth | teeth |
matrix | matrices |
phenomenon | phenomena |
species | species |
alumnus | alumni |
censuses | census |
viscus | viscera |
child | children |
Maybe the next version of EF will take on collection naming for us? For example, a collection of geese is a gaggle, and a collection of lions is a pride. Anyone who writes documentation will tell you that a collection of technical writers is known as a quarrel. <grin>
BTW, you’re welcome to post words that you feel the PluralizationService mishandles. However, keep in mind that language is a shifting, argument-laden battleground where even experts contradict each other.
Ken
Microsoft MVP [ASP.NET]