Share via



July 2015

Volume 30 Number 7

Data Points - Exploring Entity Framework Behavior at the Command Line with Scriptcs

By Julie Lerman

Julie LermanI spend a lot of time trying to educate people about how Entity Framework behaves with regard to relationships and disconnected data. EF doesn’t always follow the rules you might imagine in your head, so I always have the goal to help you be aware of what to expect, why some things work in a certain way and how to work with or avoid a particular behavior.

When I want to demonstrate something to developers, I have a handful of workflow options. For example, I can build integration tests and run them one at a time as I discuss each behavior. What I don’t like about this path is I often want to debug so I can drill into objects and show what’s going on under the covers. But debugging automated tests can be slow because all of the source Visual Studio needs to load.

Another workflow I’ve used involves building a console app and debugging through it, stopping at break points and inspecting objects. This is also not as interactive as I’d like because all of the code needs to be there in advance.

What I really want to be able to do is type some code, execute it so my audience can see its effect, then modify that code and execute it again to highlight the different response. For example, I’ve used this method as in the following code, to show the difference between this:

using (var context = new AddressContext()) {
  aRegionFromDatabase=context.Regions.FirstOrDefault();
  context.Address.Add(newAddress);
  newAddress.Region=aRegionObjectFromDatabase;
}

this:

using (var context = new AddressContext()) {
  context.Address.Add(newAddress);
  newAddress.Region=aRegionObjectFromDatabase;
}

and this:

newAddress.Region=aRegionObjectFromDatabase;
using (var context = new AddressContext()) {
  context.Address.Add(newAddress);
}

The difference, by the way, is that in the first example, where EF retrieves the Region in the same context instance, EF will understand that the region pre-exists and will not try to re-add it to the database. In the second and third cases, EF rules will infer that the Region, like its “parent” Address, is new and will add it into the database. In the long run, I recommend using a foreign key value and not an instance. It’s really helpful to be able to demonstrate cause and effect.

Sure, I could demonstrate this effect of with tests, but it’s a little convoluted. And there’s the argument that tests are not to prove theories, but to validate code. I also don’t like showing differences with a console app, because you have to run the app after each change to the code. A better alternative is to use the fantastic LinqPad application, and I’ve done that a few times. But now I’m leaning toward a fourth way to get to my happy demo place, with Scriptcs.

Scriptcs (scriptcs.net) has been around since 2013 and is completely open source. It was written on top of Roslyn to provide a command-line runtime that lets you run C# code outside of Visual Studio. Scriptcs also provides a very lightweight mechanism for creating scripts with C# in whatever text editor you want and then running those scripts from the command line.

I’d often heard of Scriptcs because one of the key people behind it is Glenn Block, for whom I have enormous respect. But I never looked too closely at it until very recently, after listening to Block talking about the bright future of Scriptcs on an episode of DotNet­Rocks (bit.ly/1AA1m4z). My first thought on seeing this tool was that it could allow me to perform interactive demos right at the command line. And by combining it with EF, I can rationalize sharing Scriptcs with readers of this data-focused column.

A Tiny Intro to Scriptcs

There are lots of great resources for Scriptcs. I am far from an expert, so I’ll just give some highlights and point you to the scriptcs.net site for more info. I also found the short video from Latish Sengal at bit.ly/1R6mF8s to be a perfect first look at Scriptcs.

First, you’ll need to install Scriptcs onto your development machine using Chocolatey, which means you’ll also need Chocolatey installed on your machine. If you don’t already have Chocolatey, it’s an incredible tool for installing software tools. Chocolatey uses NuGet to install tools (and the tools or APIs they depend on) in the same way that NuGet uses packages to deploy assemblies.

Scriptcs provides a Read, Evaluate, Play, Loop (REPL) environment, which you can think of as akin to a runtime environment. You can use Scriptcs to execute C# commands one by one at the command line, or to execute Scriptcs script files created in a text editor. There are even Scriptcs IntelliSense extensions for some editors. The best way to see Scriptcs in action is to start with line-by-line execution. Let’s start with that.

Once Scriptcs is installed, navigate to the folder where you want any saved code to be stored, then execute the scriptcs command. This will start the REPL environment and inform you that Scriptcs is running. It also returns a > prompt.

At the prompt, you can type any C# command that’s found in a few of the most common .NET namespaces. These are all available right off the bat and are from the same assemblies that a typical .NET console application project has by default. Figure 1 shows Scriptcs starting at the command prompt and then executing a line of C# code, as well as the results that are displayed.

Running C# Code at the Command Line in the Scriptcs REPL Environment
Figure 1 Running C# Code at the Command Line in the Scriptcs REPL Environment

Scriptcs has a handful of directives that let you do things like reference an assembly (#r) or load an existing script file (#load).

Another cool feature is that Scriptcs lets you easily install NuGet packages. You do this at the command prompt using the -install parameter of the Scriptcs command. For example, Figure 2 shows what happens when I install Entity Framework.

Installing NuGet Packages into Scriptcs
Figure 2 Installing NuGet Packages into Scriptcs

There’s more to package installs, though, as you can see from the screenshot of my folder after I’ve installed Entity Framework (see Figure 3)—Scriptcs has created a scriptcs_packages folder along with the contents of the relevant package.

The Scriptcs NuGet Package Installer Creates Familiar Package Folders and Config Files
Figure 3 The Scriptcs NuGet Package Installer Creates Familiar Package Folders and Config Files

Creating a Model and Some Common Setup Code

I’ll be doing my experiments against a particular model I already built in Visual Studio using code first. I have one assembly with my classes—Monkey, Banana and Country—and another assembly that contains a Monkeys­Context that inherits from the EF DbContext and exposes DbSets of Monkeys, Bananas and Countries. I verified that my model was set up correctly using the View Entity Data Model feature of the Entity Framework Power Tools extension for Visual Studio (bit.ly/1K8qhkO). This way, I know I can depend on the compiled assemblies I’ll be using in my command-line tests.

In order to do these experiments, there’s some common setup I have to perform. I need references to my custom assemblies, as well as to the EF assembly, and I need code that instantiates new monkey, country and MonkeysContext objects.

Rather than repeat this setup continually at the command prompt, I’ll create a script file for Scriptcs. Script files, which have a .csx extention, are the more common way to take advantage of Scriptcs. I first used Notepad++ with the Scriptcs plug-in, but was encouraged to try out Sublime Text 3 (sublimetext.com/3). Doing so allowed me to use not only the Scriptcs plug-in, but also OmniSharp, a C# IDE plug-in for Sublime Text 3, which provides an amazing coding experience considering you’re in a text editor that supports OSX and Linux, as well as Windows.

OmniSharp lets you build Scriptcs, as well as build for many other systems. With Sublime set up, I’m ready to create a .csx file that encapsulates the common setup code I want for testing out my model.

In the text editor, I create a new file, SetupForMonkeyTests.csx, add some Scriptcs commands for referencing needed assemblies, and then add the C# code to create some objects, as shown here:

#r "..\DataModel Assemblies\DataModel.dll"
#r "..\DataModel Assemblies\DomainClasses.dll"
#r "..\scriptcs_packages\EntityFramework.6.1.3\lib\net45\EntityFramework.dll"
using DomainClasses;
using DataModel;
using System.Data.Entity;
var country=new Country{Id=1,Name="Indonesia"};
var monkey=Monkey.Create("scripting gal", 1);
Database.SetInitializer(new NullDatabaseInitializer<MonkeysContext>());
var ctx=new MonkeysContext();

Remember, the #r commands are Scriptcs commands to add references to needed assemblies. A number of oft-used System assemblies are referenced by default in order to provide an out-of-the-box experience similar to the one you get when you create a new console project in Visual Studio. So I don’t need to specify those, but I do need to reference my custom assemblies, as well as the EntityFramework assembly installed by NuGet. The rest is just C# code. Notice that I’m not defining classes here. This is a script, so Scriptcs will just read and execute line by line whatever is in the file. With the OmniSharp Sublime Text combo, I can even build to ensure my syntax is correct.

With this file in hand, I’ll return to the command line and check out some EF behavior using my model and classes.

On to My Command-Line Experiments with EF

Back at the command prompt, I’ll start the Scriptcs REPL with just the scriptcs command and no parameters.

Once the REPL is active, the first thing I want it to do is load the .csx file. Then I’ll use the :references and :vars commands to verify the REPL correctly ran the .csx file. (Scriptcs has a number of REPL commands that start with a colon. You can see a list by typing :help.) Figure 4 shows my session so far; you can see all of the APIs that are referenced, as well as the objects I created.

Figure 4 Starting Scriptcs; Loading a .csx File;  Checking References and Existing Variables

D:\ScriptCS Demo>scriptcs
scriptcs (ctrl-c to exit or :help for help)
> #load "SetupForMonkeyTests.csx"
> :references
[
  "System",
  "System.Core",
  "System.Data",
  "System.Data.DataSetExtensions",
  "System.Xml",
  "System.Xml.Linq",
  "System.Net.Http",
  "C:\\Chocolatey\\lib\\scriptcs.0.14.1\\tools\\ScriptCs.Core.dll",
  "C:\\Chocolatey\\lib\\scriptcs.0.14.1\\tools\\ScriptCs.Contracts.dll",
  "D:\\ScriptCS Demo\\scriptcs_packages\\EntityFramework.6.1.3\\lib\\   
    net45\\EntityFramework.dll",
  "D:\\ScriptCS Demo\\scriptcs_packages\\EntityFramework.6.1.3\\lib\\
    net45\\EntityFramework.SqlServer.dll",
  "D:\\ScriptCS Demo\\DataModel Assemblies\\DataModel.dll",
  "D:\\ScriptCS Demo\\DataModel Assemblies\\DomainClasses.dll"
]
> :vars
[
  "DomainClasses.Country country = DomainClasses.Country",
  "DomainClasses.Monkey monkey = DomainClasses.Monkey",
  "DataModel.MonkeysContext ctx = DataModel.MonkeysContext"
]
>

I can also inspect the objects by just typing the variable name at the prompt. Here, for example, is my monkey object:

> monkey
{
  "Id": 0,
  "Name": "scripting gal",
  "Bananas": [],
  "CountryOfOrigin": null,
  "CountryOfOriginId": 1,
  "State": 1
}
>

Now I’m ready to start exploring some EF behavior. Returning to my earlier examples, I’ll check how EF responds to attaching the country object to the monkey when the context is or isn’t tracking the monkey and when it is or isn’t tracking the country.

In the first test, I’ll attach the pre-existing country to the monkey’s CountryOfOrigin property, then Add the monkey to the context. Finally, I’ll use the DbContext.Entry().State property to examine how EF understands the objects’ state. It makes sense that the monkey is Added, but notice that EF thinks the country is Added, as well. That’s how EF treats a graph. Because I used the Add method on the root of the graph (monkey), EF is marking everything in the graph as Added. When I call SaveChanges, the country will get inserted into the database table and, as you can see in Figure 5, I’ll have two rows for Indonesia. The fact that country already had a key value (Id) will be ignored.

Using Scriptcs to See Immediately How EF Responds to the DbSet.Add Method with a Graph
Figure 5 Using Scriptcs to See Immediately How EF Responds to the DbSet.Add Method with a Graph

Next, I’ll use the :reset command to clear the REPL history and then  #load the .csx again. After that, I’ll try a new workflow—add the monkey to the context and then attach the country to the monkey that’s already being tracked. Note that I’m not including all of the responses in the following listing:

> :reset
> #load "SetupForMonkeyTests.csx"
> ctx.Monkeys.Add(monkey);
{...response...}
> monkey.CountryOfOrigin=country;
{...response...}
> ctx.Entry(monkey).State.ToString()
Added
> ctx.Entry(country).State.ToString()
Added

Again, the context assigns the Added state to the country object because I attached it to another Added entity.

If you really want to use the navigation property, the pattern that will give you success is to ensure that the context already is aware of the country. This can happen either because you’ve retrieved the country using a query in the same context instance, resulting in the context assigning the Unchanged state to the object, or because you manually assigned the Unchanged state yourself.

Figure 6 shows an example of the latter, which allows me to do this testing without working with a database.

Figure 6 Manually Assigning the Unchanged State

> :reset
> #load "SetupForMonkeyTests.csx"
> ctx.Entry(country).State=EntityState.Unchanged;
2
> ctx.Monkeys.Add(monkey);
{...response...}
> monkey.CountryOfOrigin=country;
{...response...}
> ctx.Entry(monkey).State.ToString()
Added
> ctx.Entry(country).State.ToString()
Unchanged
>

Because the context was already tracking the country, the country object’s state won’t be changed. EF only changes the state of the related object when its state is unknown.

Scriptcs Will Be More Than Just a Great Teaching Tool for Me

Personally, I prefer to avoid the confusion around these rules completely and simply set the foreign key value (CountryOf­OriginId=country.Id), without attaching a reference using a navigation property. In fact, with that pattern, I can then look more closely at the CountryOfOrigin navigation property and consider if I even want it there. Demonstrating all of the variations and how EF responds to each scenario is an eye-opening lesson for many who have seen this.

When trying to show developers these behaviors, I like the immediate and obvious response I can get from an interactive window. While LINQPad can also help me achieve this, I like the command-line interaction. More important, having used these experiments as a way to introduce myself to Scriptcs, I’m now more aware of the great value it brings beyond just performing command-line tests on an API.


Julie Lerman is a Microsoft MVP, .NET mentor and consultant who lives in the hills of Vermont. You can find her presenting on data access and other .NET topics at user groups and conferences around the world. She blogs at thedatafarm.com/blog and is the author of “Programming Entity Framework” (2010), as well as a Code First edition (2011) and a DbContext edition (2012), all from O’Reilly Media. Follow her on Twitter at twitter.com/julielerman and see her Pluralsight courses at juliel.me/PS-Videos.

Thanks to the following Microsoft technical expert for reviewing this article: Justin Rusbatch