3

I have defined an enum in my Entity Framework 5 model, which I'm using to define the type of a field on a table, e.g.

public enum PrivacyLevel : byte {
  Public = 1,
  FriendsOnly = 2,
  Private = 3,
}

And I have a table Publication that has a tinyint field PrivacyLevel, which I've mapped in the EF model to use the PrivacyLevel type defined above, using the method described here.

But I also want to be able to display a string description for each value of the enum. This I've done in the past for enums by decorating them with a Description attribute, e.g.

public enum PrivacyLevel : byte {
  [Description("Visible to everyone")]
  Public = 1,
  [Description("Only friends can view")]
  FriendsOnly = 2,
  [Description("Only I can view")]
  Private = 3,
}

I've got some code that converts enums to strings by checking if they have a Description attribute, and that works well. But here, because I had to define the enum in my model, the underlying code is auto-generated, and I don't have anywhere stable to decorate them.

Any ideas for a workaround?

Community
  • 1
  • 1
Shaul Behr
  • 36,951
  • 69
  • 249
  • 387

3 Answers3

14

Not sure if this is what you are after but from what I understand i will try to be as clear as possible, since you have a concrete database first approach, you can abstract much of your Entity models to ViewModels using a Dto Approach through AutoMapper.

Using automapper profiles you can quickly setup profiles for all sorts of environments and scenarios for flexibility and adaptability

So here is this "Enum" which is causing me a problem

here is my view model for this Enum

First here is my layout

here is a simply mapping for the Account entity to a viewmodel for Account

public class AccountProfile : Profile
{
    protected override void Configure()
    {
        // Map from Entity object to a View Model we need or use
        // AutoMapper will automatically map any names that match it's conventions, ie properties from Entity to ViewModel have exact same name properties

        Mapper.CreateMap<Account, AccountViewModel>()
              .ForMember(model => model.CurrentPrivacy, opt => opt.MapFrom(account => (PrivacyLevelViewModel)account.PrivacyLevel));

        Mapper.CreateMap<Account, EditAccountViewModel>()
            .ForMember(model => model.SelectedPrivacyLevel, opt => opt.MapFrom(account => (PrivacyLevelViewModel) account.PrivacyLevel));

        // From our View Model Changes back to our entity

        Mapper.CreateMap<EditAccountViewModel, Account>()
              .ForMember(entity => entity.Id, opt => opt.Ignore()) // We dont change id's
              .ForMember(entity => entity.PrivacyLevel, opt => opt.MapFrom(viewModel => (PrivacyLevel)viewModel.NewSelectedPrivacyLevel));

    }


}

Note that this does not have to apply to MVC, this can be used in WPF or other applications not tied to the Web, but since it's a good way of explaining, it's why I used MVC for this example.

When I first get a Http Get request for my profile, I grab the entity from the database and map anything I actually need to the view

public ActionResult Index()
{
    // Retrieve account from db
    var account = new Account() { Id = 1, Name = "Patrick", AboutMe = "I'm just another dude", ProfilePictureUrl = "", PrivacyLevel = PrivacyLevel.Private, Friends = new Collection<Account>() };
    // ViewModel abstracts the Entities and ensures behavour that only matters to the UI
    var accountViewModel = Mapper.Map<AccountViewModel>(account);

    return View(accountViewModel); // strongly typed view model
}

So my profile index view can use my enum view model

Here's the output

Now when I want to change what my privacy setting is, I can create a new EditAccountViewModel which allows me to submit a new value in a dropdown

public class EditAccountViewModel
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string AboutMe { get; set; }
    public int NewSelectedPrivacyLevel { get; set; }
    public PrivacyLevelViewModel SelectedPrivacyLevel { get; set; }

    public SelectList PrivacyLevels
    {
        get
        {
            var items = Enum.GetValues(typeof (PrivacyLevelViewModel))
                .Cast<PrivacyLevelViewModel>()
                .Select(viewModel => new PrivacyLevelSelectItemViewModel()
                {
                    Text = viewModel.DescriptionAttr(),
                    Value = (int)viewModel,
                });

            //SelectPrivacyLevel was mapped by AutoMapper in the profile from 
            //original entity value to this viewmodel
            return new SelectList(items, "Value", "Text", (int) SelectedPrivacyLevel);
        }
    }
}

Now once I send a post of my new changed value, the interesting part is how I modify the "real" entity from the db with the updated privacy setting

On submitting the form back to my edit action you can i get the original real db entity and then merge changes if the ViewModel state is valid

AutoMapper allows you to configure how ViewModels can be mapped to Entities, if some properties should change, from integer entities to string values for view models, maybe you want an enum to really be a string in the "view" and only the enum for the db, with auto mapper it allows you to configure all these scenarious, and through convention you dont need to configure "every single property" if your view models have the same property names/camel case to upper case.

Lastly, before you can use these Profiles, you must load them at the application entry point, like global.asax or Main.

AutoMapper only needs to be 'configured' once to load any sort of profiles defined in the application. With some reflection you can load all Profiles in your assembly with this code:

public class AutoMapperConfig
{
    public static void RegisterConfig()
    {
        Mapper.Initialize(config => GetConfiguration(Mapper.Configuration));
    }

    private static void GetConfiguration(IConfiguration configuration)
    {
        configuration.AllowNullDestinationValues = true;
        configuration.AllowNullCollections = true;

        IEnumerable<Type> profiles = Assembly.GetExecutingAssembly().GetTypes().Where(type => typeof(Profile).IsAssignableFrom(type));

        foreach (var profile in profiles)
        {
            configuration.AddProfile(Activator.CreateInstance(profile) as Profile);
        }
    }
}

I call the configuration in my global.asax:

protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();

    WebApiConfig.Register(GlobalConfiguration.Configuration);
    FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
    RouteConfig.RegisterRoutes(RouteTable.Routes);
    AutoMapperConfig.RegisterConfig(); // AutoMapperConfig.cs
}

More information about how to use AutoMapper and how it can benefit you can be found here:

AutoMapper Github

Patrick Magee
  • 2,951
  • 3
  • 33
  • 50
  • 1
    Wow, now that's called putting effort into an answer! +1 at least for the effort, even before I start working through it! – Shaul Behr Mar 18 '13 at 15:29
  • Hi Shaul, did this end up being what you were after? Interested to know how you got on since nobody else has answered you. – Patrick Magee Apr 05 '13 at 02:20
  • The requirement is still there, but it's been pushed down the priority list for some more urgent stuff in the meantime... so I haven't had time to try it! – Shaul Behr Apr 05 '13 at 07:34
  • I came up with a much simpler solution in the end - see my accepted answer. Thanks for your help, anyway! – Shaul Behr Oct 20 '13 at 14:39
3

In the end I came up with a much simpler solution: I just used an extension method to get the description of the enum. That also made it a lot easier for localization, so I could use a Resource string.

public static string Description(this PrivacyLevel level) {
  switch (level) {
    case PrivacyLevel.Public:
      return Resources.PrivacyPublic;
    case PrivacyLevel.FriendsOnly:
      return Resources.PrivacyFriendsOnly;
    case PrivacyLevel.Private:
      return Resources.PrivacyPrivate;
    default:
      throw new ArgumentOutOfRangeException("level");
  }
}
Shaul Behr
  • 36,951
  • 69
  • 249
  • 387
0

Some other idea: Use byte PrivacyLevelByte in your EF classes. Create additional partial class for that particular model where you define property

PrivacyLevel PrivacyLevelEnum
{
    get { return (PrivacyLevel)PrivacyLevelByte; }
    set { PrivacyLevelByte = (byte)value;}
}

and define PrivacyLevel enum in your code and not by EF designer. That allows you to handle any attributes but still gives you enum properties on EF models.

Ondra
  • 1,619
  • 13
  • 27