Wednesday, February 9, 2011

Setting default values in Entity Framework when using Silverlight

I found myself amazed at how difficult it is to set default values for a property in the Entity Framework (even version 4) when used with Silverlight. Ideally I could use the DefaultValue attribute in the metadata class for my model. The problem is that Silverlight doesn’t use it. It seems to work ok in my tests that don’t use Silverlight if I remember correctly. But that only partially helps. I explored putting the value in the constructor for the entity that has the property I want to set the default value on, but that fires every time the object is created. This means that it was created even when displaying the results of a query, which is not the event I was hoping for. I want the default value to be used when a new entity is created, but just the object instantiated.

Then I ran across this post. It talks about creating a partial class on the client-side (Silverlight) and using the partial method called OnCreated(). The forum seems to say that you can define the OnCreated() partial method in the partial entity class that is on the server-side, but that method is not defined on the server-side that I can tell. So, the partial keyword doesn’t work in that context. The server-side object has a .shared.cs instead of just .cs extension so the file will be copied to the client-side. The problem is that the code has to work on the client and the server side. As noted before, the OnCreated method only exists on the client-side.

The solution is actually quite simple. I just had to explicitly define a partial class on the client-side even though I already have one on the server-side and is shared. This allows me to leverage the OnCreated partial method on the client that will fire when the entity is created, not just instantiated, and still keep my shared partial class that is on the server and client side.

I haven’t tried it, but by doing like Kyle recommends I should be able to use the DefaultValue attribute in the metadata and have it carry over to the client-side by calling the utility method that reads the DefaultValue attribute from the model when the OnCreated method is called.

So, here is an example, of what I am trying to explain.

In this example (to keep with the example on the forum) my entity is called Entity1

In my Silverlight project, I define a file called Entity1.cs. It would contain:

public partial class Entity1
    {
        partial void OnCreated()
        {
            DefaultValueUtility.InitializeDefaultValues(this);
        }
    }

NOTE: I could also just set the property directly instead of pulling the data from the model.

I can, but don’t have to have a file called Entity1.shared.cs on my server-side that will can contain whatever I want it to. It will be copied to the client-side as well. Then it will be merged with the Entity1.cs and the generated Entity1 file from the model itself on the client side because they are all partial classes.

Here is the code for the DefaultValueUtility call above. You will want to put this code somewhere in your Silverlight project.

using System;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Reflection;

namespace DefaultSetter
{
    public static class DefaultValueUtility
    {
        public static void InitializeDefaultValues(object entity)
        {
            PropertyInfo[] properties = entity.GetType().GetProperties();
            foreach (PropertyInfo property in properties)
            {
                MethodInfo propertySetter = property.GetSetMethod();
                if (propertySetter != null)
                {
                    DefaultValueAttribute defaultValueAttribute = (DefaultValueAttribute)
                        property.GetCustomAttributes(typeof(DefaultValueAttribute), true).FirstOrDefault();

                    if (defaultValueAttribute != null)
                    {
                        object defaultValue = 
                            Convert.ChangeType(defaultValueAttribute.Value, property.PropertyType, CultureInfo.InvariantCulture);

                        propertySetter.Invoke(entity, new object[] { defaultValue });
                    }
                }
            }
        }
    }
}

2 comments:

Gustyn said...

is the OnCreated() event available for POCOs as well?

Francisco said...

this really helped me, thank you