John Nye

Agile software developer

I am a software engineer from Essex, working in London. I largely work within the microsoft stack, but have wealth of experience across multiple languages and frameworks. I have built this blog from scratch using ASP.NET MVC 4, Entity Framework code first and markdowndeep.

If you have any feedback on my blog or just want to strike up a conversation I'd love to hear from you. You can get in touch with me via twitter @ninjanye or by emailing using the links in the header.

Check out my latest posts below or use my tag cloud for previous posts on a specific subjects

Migrating a dotNet project to dotnet core

A request was raised on Github to have Search Extensions support dotnet core. This post documents the steps taken as part of that migration in the hope that it may be useful to someone else.

PM> Install-Package NinjaNye.SearchExtensions

The solution I am upgrading (Search Extensions) consists of the following projects:

  • 1 class library
  • 3 test projects

NOTE: This blog assumes that you are using VS2017. You can download the Community edition free from the visual studio downloads page.

Upgrading the .csproj

First step was edit the .csproj file from the main (class library) project. You can open the .csproj in a text editor or simply, right click the project and select "Edit [Project_name].csproj". Once you have the editor open you need to clear the contents and replace it with the following snippet to make the project load on netstandard1.6:

<Project Sdk="Microsoft.NET.Sdk">
    <PropertyGroup>
        <TargetFramework>netstandard1.6</TargetFramework>
        <Description>[DESCRIPTION]</Description>
    </PropertyGroup>    
</Project>

Once this is complete, in Visual Studio 2017, reload and build the solution. This will highlight, through build errors, any incompatible code and identify which NuGet packages you need to install. At this point you have a list of errors that need fixing before you can proceed.

In order to get the project to build, you will most likely need to dive into your Assembly.cs, and potentially remove it altogether. Before you do so, remember to migrate your project meta data to your .csproj.

If you take advantage of the [assembly:InternalsVisibleTo] then your will need to keep this in your Assembly.cs

Targeting multiple frameworks

Upgrading my test projects was a little more involved, firstly because I wanted to test my code on both dotnet core and net461, and secondly because I wanted to migrate my NUnit tests to XUnit.

Fortunately, with the new xml based project files you can target multiple frameworks fairly simply:

<Project Sdk="Microsoft.NET.Sdk">
    <PropertyGroup>
        <!-- MULTIPLE FRAMEWORKS DEFINED BELOW -->
        <TargetFrameworks>netcoreapp1.1;net461</TargetFrameworks>
        <Description>Unit test project for Search Extensions</Description>
    </PropertyGroup>    
</Project>

Note that the <TargetFramework> element has been altered to <TargetFrameworks>.

Again, the above step will remove all referenced projects and packages. To reference my project under test I was able to add the following code after the closing </PropertyGroup> node (alternatively you can let VS2017 perform this for you using the UI):

<ItemGroup>
  <ProjectReference Include="..\NinjaNye.SearchExtensions\NinjaNye.SearchExtensions.csproj" />
</ItemGroup>

Once complete I could then address my second problem. Porting my NUnit tests to XUnit. This was simply a case of installing the XUnit nuget package on the project and replacing a bunch of [Test] attributes with [Fact]... well that and a bunch of assertion tweaks but you get the idea.

Which NetStandard api?

So my project had been converted and my tests were running (on multiple frameworks). The last piece in the puzzle was to decide which NetStandard framework to support. Essentially, the earlier you go the greater the amount of frameworks your code can run on. This is at the expense of the richness of the api you integrate with. In the end you don't choose which api to use... it chooses you!

To do this, you simply swap out netstandard1.6 for netstandard1.5 in the .csproj, build the code and, based on the errors, make a decision as to whether you can support the younger framework. In my case I was able to go back to netstandard1.5 but when I tried netstandard1.4 the api did not have enough functionality to perform the actions I required.

If you find that netstandard1.6 is not yet rich enough to migrate to, it might be worth waiting for NetStandard 2.0. This release is due to have a huge amount of additional integration points.

The final conversion

If the above isn't clear, please feel free to leave a comment, or alternatively you can take a look at the SearchExtensions GitHub project and take a look around.

Finally, I'd like to thank Nick Mayne for his help during this conversion. Go check out his GitHub page or follow him on twitter @nicholasmayne


If you would like to know more about this or any other feature, please get in touch by adding a comment below, contact me on twitter (@ninjanye) or you can raise an issue on the github project

Improved Levenshtein searching with SearchExtensions

NOTE: This post will not cover what Levenshtein Distance is or how it is calculated.
For more information on Levenshtein Distance, please visit the Wikipedia page

Up until now, NinjaNye.SearchExtensions has only been able to calculate the Levenshtein distance between:

  • A single property and a string search term
  • A single property and one other property

To celebrate 10,000 downloads of SearchExtensions, the latest version allows you to calculate the distance between:

  • A single property and multiple strings
  • Multiple properties and a single string
  • Multiple properties and multiple strings
  • A single property and multiple other properties
  • Multiple properties and a single property
  • Multiple properties and multiple other properties

You can install NinjaNye.SearchExtensions from nuget using the following:

PM> Install-Package NinjaNye.SearchExtensions

All of this is fairly difficult to describe, so below are some examples to help me explain.

Calculate levenshtein distance against multiple terms

The following functionality has been in SearchExtensions for a little while but it is a good starting point to describe how to get the Levenshtein distance between each users first name and the word "Jim"

var result = context.Users.LevenshteinDistanceOf(x => x.FirstName)
      .ComparedTo("Jim");

The above will return each user along with the Levenshtein distance of the FirstName compared to "Jim". In version 2.0, it is now possible to compare the FirstName to more than one term:

var result = context.Users.LevenshteinDistanceOf(x => x.FirstName)
      .ComparedTo("Jim", "Fred");

Calculate levenshtein distance against multiple properties

Calculating the distance against multiple properties is also now supported:

var result = context.Users.LevenshteinDistanceOf(x => x.FirstName, x => LastName)
      .ComparedTo("Jim", "Fred");

The above will result in 4 comparisons per record. Because the levenshtein calculation must be performed in memory, you should always reduce the record set as much as possible beforehand, e.g:

var result = context.Users.Search(x => x.CountryCode)
      .EqualTo("GB")
      .LevenshteinDistanceOf(x => x.FirstName, x => LastName)
      .ComparedTo("Jim", "Fred");

When performing a Levenshtein search it is important to always reduce your record set as much as possible, especially when comparing multiple properties to multiple terms

A new result

The result of a Levenshtein search also changed and now has additional properties to work with:

public interface ILevenshteinDistance<out T>
{
    /// <summary>
    /// The distance of the first comparison
    /// </summary>
    int Distance { get; }

    /// <summary>
    /// The queried item
    /// </summary>
    T Item { get; }

    /// <summary>
    /// A collection of all distances calculated
    /// </summary>
    int[] Distances { get; }

    /// <summary>
    /// The minimum distance of all levenshtein calculations
    /// </summary>
    int MinimumDistance { get; }

    /// <summary>
    /// The maximum distance of all levenshtein calculations
    /// </summary>
    int MaximumDistance { get; }
  }

This means you can order the results so that the record with the closest match is ordered first:

var result = context.Users.LevenshteinDistanceOf(x => x.FirstName)
      .ComparedTo("Jim", "Fred")
      .OrderBy(x => x.MinimumDistance)
      .ThenBy(x => x.MaximumDistance);

If you would like to know more about this or any other feature, please get in touch by adding a comment below, contact me on twitter (@ninjanye) or you can raise an issue on the github project

I woke up this morning to find that search extensions now has over 10,000 downloads!!!

I wanted to take this opportunity to thank all of you that have contributed to the project be it by simply using SearchExtensions, requesting new features, raising issues on GitHub or even submitting PRs.

The future

The improvements I'd like to make to SearchExtensions include:

  • Support for .Net Core
  • Split out Soundex support to its own package
  • Split Levenschtein support to its own package

The above are only what I see as a way to improve SearchExtensions. The best, and most useful ideas often come from users. With that in mind:

If you have any suggestions on how the package could be improved, please get in touch by adding a comment below, contact me on twitter (@ninjanye) or you can raise an issue on the GitHub

PM> Install-Package NinjaNye.SearchExtensions