Secrets of DetectChanges Part 4: Binary properties and complex types

In parts 1, 2, and 3 of this series we looked at fairly normal, if occasionally advanced, uses of DetectChanges. In this post we’re going to look at some corner cases around complex types and binary properties. While these are corner cases its still worth knowing about them so they don’t catch you out if you ever run into them.

DetectChanges and binary properties

EF supports byte array properties for storing binary data such as images. For example, we could store the image for a banner on a blog by adding a byte array property to our Blog class:

public class Post
{
    // Just showing added byte array property
    public byte[] BannerImage { get; set; }
}

If you want to change this image you must do it through setting a new byte[] instance. Don’t try to change the contents of the existing byte array. DetectChanges doesn’t go down into the contents of the byte array and attempt to see if it has changed. It assumes that if the same instance is still present on the entity that was there when it took the snapshot, then the property has not changed, and so no update for the property will be sent to the database.

In other words, treat your byte arrays as if they are immutable.

Binary keys

EF also allows you to use byte array properties as keys. (This is rarely a good idea, but you might have a reason to legitimately do it.)

When a binary property is a key (primary or foreign) then DetectChanges behaves differently—it checks the contents of the byte array, not just the instance. This is because it is unlikely that a foreign key property on one entity and a primary key property on another entity will be set to the same byte array instance even when they represent matching keys. EF must still be able to detect that these two byte[] objects represent the same key in order to correctly do fixup and order changes sent to the database.

Complex types

EF complex types allow properties to be gathered into non-entity classes that do not have keys. An entity with a property that is of a complex type is said to have a complex property and the object set on that property is called a complex object.

EF allows complex objects to be mutated. That is, you can change the values of the properties inside the complex object and EF will detect these changes and send appropriate updates to the database.

For example, consider a Person entity with an Address complex property, which itself contains a PhoneNumbers complex property:

public class Person
{
    public virtual int Id { get; set; }
    public virtual string Name { get; set; }
    public virtual Address Address { get; set; }
}

public class Address
{
    public virtual string Street { get; set; }
    public virtual string City { get; set; }
    public virtual string State { get; set; }
    public virtual PhoneNumbers PhoneNumbers { get; set; }
}

public class PhoneNumbers
{
    public virtual string Home { get; set; }
    public virtual string Work { get; set; }
}

Notice that Person fulfills the requirements for being a change-tracking proxy. So you might think that DetectChanges is not needed and that this code will work:

using (var context = new PeopleContext())
{
    context.Configuration.AutoDetectChangesEnabled = false;

    var person = context.People
        .Single(p => p.Name == "Frans");

    person.Address.Street = "1 Tall Street";
    person.Address.City = "Fairbanks";
    person.Address.State = "AK";
    person.Address.PhoneNumbers.Home = "555-555-5555";
    person.Address.PhoneNumbers.Work = "555-555-5556";

    context.SaveChanges();
}

Running this code will result in no changes being sent to the database! The reason is that even though Person is proxied, the Address and PhoneNumbers types are not proxied, even though they have virtual properties. EF never creates change-tracking proxies for complex types. Instead, snapshot change tracking and DetectChanges is always used for complex types.

Treat complex objects as immutable

All is not lost. You can still avoid the need for DetectChanges when using complex types and change-tracking proxies so long as you treat the objects as immutable. In practice, this means always setting a new instance of the complex type rather than changing the properties of an existing instance. For example, this will work fine:

using (var context = new PeopleContext())
{
    context.Configuration.AutoDetectChangesEnabled = false;

    var person = context.People
        .Single(p => p.Name == "Frans");

    person.Address =
        new Address
        {
            Street = "1 Tall Street",
            City = "Fairbanks",
            State = "AK",
            PhoneNumbers =
                new PhoneNumbers
                {
                    Home = "555-555-5555",
                    Work = "555-555-5556"
                }

        };

    context.SaveChanges();
}

I prefer to always treat complex objects as immutable—they then become a good analogue for value types in DDD.

Rule 1 to the rescue

If you do want to mutate a complex object and do it in a way that won’t require DetectChanges, then Rule 1 can come to the rescue again:

using (var context = new PeopleContext())
{
    context.Configuration.AutoDetectChangesEnabled = false;

    var person = context.People
        .Single(p => p.Name == "Frans");

    var addressEntry = context.Entry(person)
        .ComplexProperty(p => p.Address);

    addressEntry
        .Property(a => a.Street)
        .CurrentValue = "1 Tall Street";

    addressEntry
        .Property(a => a.City)
        .CurrentValue = "Fairbanks";

    addressEntry
        .Property(a => a.State)
        .CurrentValue = "AK";

    addressEntry
        .ComplexProperty(a => a.PhoneNumbers)
        .Property(p => p.Home)
        .CurrentValue = "555-555-5555";

    addressEntry
        .ComplexProperty(a => a.PhoneNumbers)
        .Property(p => p.Work)
        .CurrentValue = "555-555-5556";

    context.SaveChanges();
}

This code uses calls into EF code to mutate the complex objects and hence, by Rule 1, doesn’t require DetectChanges to be called.

And that’s DetectChanges!

If you’ve read all four parts of this series then thank you! You are now a certified DetectChanges expert. So how about going off to Stack Overflow and using your knowledge to the betterment of the EF community. :-)

Advertisements

6 comments on “Secrets of DetectChanges Part 4: Binary properties and complex types

  1. Kim Tranjan says:

    Wow Arthur, I read all of the 4 parts. Very pleasant reading with excellent posts! Congratulations and thank you for the awesome content.

  2. britto augustine says:

    best article series on DetectChanges and the internal working that I have ever read. nice job. thanks.

  3. ahmet salih says:

    Happened upon this series at the tail end of a long and frustrating misadventure trying to get some solid information about change tracking in EF, and there is finally light at the end of the tunnel.

  4. Hi, great article, very helpful. One question though, what about many to many relationships in EF? Is there anyway to refresh these without needing a call to detect changes? I keep getting tripped up by them because EF abstracts the underlying database association table?
    Thanks, Ed

    • Many-to-many relationships are always independent associations (IAs) which means that it is possible to manipulate their state but it’s not easy and requires dropping down to the ObjectContext and using the ObjectStateManager to get at the low-level tracking that EF uses. If you do this you will find the relationships between pairs of entities are represented by a special kind of ObjectStateEntry called a RelationshipEntry. You can add/remove RelationshipEntry objects to manipulate the state of the relationships without the need to call DetectChanges.

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