Factory methods in Effort (CreateTransient vs CreatePersistent)

The Effort library provides multiple factory classes that developers can choose from. All of them can serve well in different scenarios.

  • DbConnectionFactory
  • EntityConnectionFactory
  • ObjectContextFactory

They are capable of creating different type of fake data endpoints: DbConnection, EntityConnection and ObjectContext, respectively. Fake DbConnection objects are ideal for using them in the Code First programming approach. The purpose of fake EntityConnection objects is to utilize them in the Database First and Model First techniques. Instantiate ObjectContext objects by passing them as constructor argument. This can be also done automatically by creating fake ObjectContext instances directly.

All the factory components provide two kind of factory methods.

  • CreateTransient
  • CreatePersistent

What is the difference between them? The answer lies in the lifecycle of the underlying in-memory database bound to the endpoint. Let’s examine these factory methods with an extremely simple demonstration that uses the DbConnectionFactory component.

The demo includes a single Person entity with two members:

public class Person
{
    [Key]
    public int Id { get; set; }

    public string Name { get; set; }
}

The DbContext class that provides the entity endpoint can be defined easily too. Note that it needs a constructor that can accept a DbConnection object.

public class PeopleDbContext : DbContext
{
    public PeopleDbContext(DbConnection connection)
        : base(connection, true)
    {
    }

    public IDbSet People { get; set; }
}

First, the CreateTransient method is demonstrated. The following code instantiates a DbContext object with a fake DbConnection object and adds some data to the fake database. Then creates another DbContext instance with a newly created fake DbConnection and tries to query the previously added data.

using (var ctx = new PeopleDbContext(Effort.DbConnectionFactory.CreateTransient()))
{
    ctx.People.Add(new Person() { Id = 1, Name = "John Doe" });
    ctx.SaveChanges();

    Console.WriteLine("Test 1 - First Count: {0}", ctx.People.Count());
}

using (var ctx = new PeopleDbContext(Effort.DbConnectionFactory.CreateTransient()))
{
    Console.WriteLine("Test 1 - Second Count: {0}", ctx.People.Count());
}

This code outputs the following:

Test 1 - First Count: 1
Test 1 - Second Count: 0

The second DbContext instance is not able to see the entity added by the first DbContext instance. Every time a fake connection object is created with the CreateTransient method, the new fake connection object will redirect the data operations to a completely new unique in-memory database instance. No other endpoint can use that database, it is completely isolated. In addition the database is cleaned up when the connection object is no longer in use.

Change the previous code a little bit: let the CreatePersistent method be used this time. Note that this method accepts a string argument. This string identifies the fake database instance that the connection object is bound to.

using (var ctx = new PeopleDbContext(Effort.DbConnectionFactory.CreatePersistent("1")))
{
    ctx.People.Add(new Person() { Id = 1, Name = "John Doe" });
    ctx.SaveChanges();

    Console.WriteLine("Test 2 - First Count: {0}", ctx.People.Count());
}

using (var ctx = new PeopleDbContext(Effort.DbConnectionFactory.CreatePersistent("1")))
{
    Console.WriteLine("Test 2 - Second Count: {0}", ctx.People.Count());
}

This code outputs the following:

Test 2 - First Count: 1
Test 2 - Second Count: 1

The second DbContext instance is able to see the newly added entity this time, because the connection objects are bound to the same database instance. This is possible because they were created with the same identifier.

In the last code sample the connection objects are created with the CreatePersistent method too, but with different identifiers.

using (var ctx = new PeopleDbContext(Effort.DbConnectionFactory.CreatePersistent("2")))
{
    ctx.People.Add(new Person() { Id = 1, Name = "John Doe" });
    ctx.SaveChanges();

    Console.WriteLine("Test 3 - First Count: {0}", ctx.People.Count());
}

using (var ctx = new PeopleDbContext(Effort.DbConnectionFactory.CreatePersistent("3")))
{
    Console.WriteLine("Test 3 - Second Count: {0}", ctx.People.Count());
}

This code outputs the following:

Test 3 - First Count: 1
Test 3 - Second Count: 0

The second DbContext object is not able to see the added entity, because the fake connection objects are bound to separate database instances. The reason of this, that the identifiers that were used to create them are not identical.

These two kind of factory method can be useful in different scenarios. For example, if someone wants to write automated tests for data oriented components, then CreateTransient is the way to go, because it ensures that the tests can run in completely insolated environments. They can run even in parallel. If someone wants to test the system in an interactive way without screwing up the persisted data in the real database, then CreatePersistent might worth a shot.

Use them wisely!

Advertisements

14 Responses to Factory methods in Effort (CreateTransient vs CreatePersistent)

  1. Michael says:

    Hello,
    Can you give me an idea, why persistent version doesn’t work on my machine? After creating new DbContext even with the same dbConnection saved changes are gone. I am using .NET 4.0, EntityFramework 5.

  2. RJB says:

    Does anything need to be done to dispose a CreatePersistent context?

  3. cslewis says:

    I’m trying to rewrite my Effort-based test code in such a way that it will fall in line with proper Unit testing practices by actually ensuring that I don’t need the database up and running when the Effort-based unit tests are being executed. Could you please take a look at http://stackoverflow.com/questions/27716714/how-would-i-configure-effort-testing-tool-to-mock-entity-frameworks-dbcontext-w ?

  4. Pingback: EF / Effort | Vincent

  5. Is there a way to make persistent connections with different identifiers when using the EntityConnectionFactory? I’ve tried a variety of things (including just using the Create method on that factory) but I always end up with the same database.
    I’ve looked into using the DbConnectionFactory but that seem to only work when using the code first approach.

  6. Jason Coyne says:

    I am calling create transient in several unit tests, but data appears to be shared across the tests. I have confirmed I am not reusing connections, contexts, or repositories, but somehow the data is shared across tests 😦

  7. Amy Knowles says:

    I’m attempting to use this approach

    using (var ctx = new PeopleDbContext(Effort.DbConnectionFactory.CreateTransient()))
    {
    ctx.People.Add(new Person() { Id = 1, Name = “John Doe” });
    ctx.SaveChanges();

    Console.WriteLine(“Test 1 – First Count: {0}”, ctx.People.Count());
    }

    but get the following exception when adding data to the fake context

    System.InvalidOperationException: The context cannot be used while the model is being created. This exception may be thrown if the context is used inside the OnModelCreating method or if the same context instance is accessed by multiple threads concurrently. Note that instance members of DbContext and related classes are not guaranteed to be thread safe.
    Result StackTrace:
    at System.Data.Entity.Internal.LazyInternalContext.InitializeContext()
    at System.Data.Entity.Internal.InternalContext.Initialize()
    at System.Data.Entity.Internal.InternalContext.GetEntitySetAndBaseTypeForType(Type entityType)
    at System.Data.Entity.Internal.Linq.InternalSet`1.Initialize()
    at System.Data.Entity.Internal.Linq.InternalSet`1.ToString()
    at System.Data.Entity.Infrastructure.DbQuery`1.ToString()

    I’m using a MySql database and the context does implement OnModelCreating() but I get the same error even if I change this method to do nothing.

    I downloaded the Effort.Extra.EF6 framework, if that makes a difference. This framework looks like it will provide the support I’m looking for if I can just get past this hurdle.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: