97

I have the following class generated by entity framework:

public partial class ItemRequest
{
    public int RequestId { get; set; }
    //...

I would like to make this a required field

[Required]
public int RequestId { get;set; }

However, because this is generated code this will get wiped out. I can't imagine a way to create a partial class because the property is defined by the generated partial class. How can I define the constraint in a safe way?

davmos
  • 9,324
  • 4
  • 40
  • 43
P.Brian.Mackey
  • 43,228
  • 68
  • 238
  • 348

6 Answers6

146

The generated class ItemRequest will always be a partial class. This allows you to write a second partial class which is marked with the necessary data annotations. In your case the partial class ItemRequest would look like this:

using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

//make sure the namespace is equal to the other partial class ItemRequest
namespace MvcApplication1.Models 
{
    [MetadataType(typeof(ItemRequestMetaData))]
    public partial class ItemRequest
    {
    }

    public class ItemRequestMetaData
    {
        [Required]
        public int RequestId {get;set;}

        //...
    }
}
MUG4N
  • 19,377
  • 11
  • 56
  • 83
42

As MUG4N answered you can use partial classes but will be better use interfaces instead. In this case you will have compilation errors if EF model doesn't correspond to validation model. So you can modify your EF models without fear that validation rules are outdated.

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace YourApplication.Models
{
    public interface IEntityMetadata
    {
        [Required]
        Int32 Id { get; set; }
    }

    [MetadataType(typeof(IEntityMetadata))]
    public partial class Entity : IEntityMetadata
    {
        /* Id property has already existed in the mapped class */
    }
}

P.S. If you are using project type which is differ from ASP.NET MVC (when you perform manual data validation) don't forget to register your validators

/* Global.asax or similar */

TypeDescriptor.AddProviderTransparent(
    new AssociatedMetadataTypeTypeDescriptionProvider(typeof(Entity), typeof(IEntityMetadata)), typeof(Entity));
dimonser
  • 675
  • 6
  • 12
  • @dimonser nice solution, I tried adding xml comments like this also (for those DB fields that need a little explanation in the code - i.e. to be displayed in intellitype) but it doesn't seem to work. Any idea how to do that? – Percy Mar 21 '15 at 12:27
  • Hi @Rick, you can put a comment on an interface property, but you will see it only when you work with an interface variable. Or you can put a comment in a partial class. In this case you will see it when you work with an instance of your class. No other cases available. So you can use both of them to cover all situations, In the first case you can describe field validation rules and in the second case try to describe purposes – dimonser Mar 31 '15 at 14:14
  • Really well thought out answer, but my preference would be to see compilation errors if the validation is no longer in sync with the autogenerated entity framework class. I'm struggling to think of a situation where you might want to validate a property which is no longer present in your entity framework class. – Mike Aug 17 '16 at 11:11
  • 1
    This doesn't work for me, it says I need to implement the IEntityMetadata interface... – Worthy7 Sep 26 '17 at 06:24
14

I found a solution like MUG4N's answer, but instead, nesting the MetaData class within the entity class, thereby reducing the number of classes in your public namespace list, and eliminating the need to have a unique name for each metadata class.

using System.ComponentModel.DataAnnotations;

namespace MvcApplication1.Models 
{
    [MetadataType(typeof(MetaData))]
    public partial class ItemRequest
    {
        public class MetaData
        {
            [Required]
            public int RequestId;

            //...
        }
    }
}
Carter Medlin
  • 11,857
  • 5
  • 62
  • 68
  • I've been using this all over my project. Much easier to organize. I also add custom properties using `[NotMapped]` inside the partial class too when I need them. – Carter Medlin Dec 30 '16 at 23:26
5

This is sort of extension to @dimonser answer if you regenerate your db model you will have to manually re-add interfaces on those classes.

If you have stomach for it you can also modify your .tt templates:

Here's example of auto-generating interfaces on some classes, this is fragment from .tt just replace EntityClassOpening method in yours with following (and obviously var stringsToMatch with your entity names and interfaces).

public string EntityClassOpening(EntityType entity)
{
    var stringsToMatch = new Dictionary<string,string> { { "Answer", "IJourneyAnswer" }, { "Fee", "ILegalFee" } };
    return string.Format(
        CultureInfo.InvariantCulture,
        "{0} {1}partial class {2}{3}{4}",
        Accessibility.ForType(entity),
        _code.SpaceAfter(_code.AbstractOption(entity)),
        _code.Escape(entity),
        _code.StringBefore(" : ", _typeMapper.GetTypeName(entity.BaseType)),
        stringsToMatch.Any(o => _code.Escape(entity).Contains(o.Key)) ? " : " + stringsToMatch.Single(o => _code.Escape(entity).Contains(o.Key)).Value : string.Empty);
}

No normal person should do this to himself though, it has been proven in Bible that one goes to Hell for this.

Matas Vaitkevicius
  • 58,075
  • 31
  • 238
  • 265
2

I am not sure how to do what you are asking for but there is a way around it. Dynamic data validation by overriding the GetValidators of your custom DataAnnotationsModelValidatorProvider. In it you can read the rules for validating each field (from a database, config file, etc.) and add validators as need be. It has the added values that your validation is no longer tightly coupled to the model and can be changed without need to even restart the site. Of course it might be overkill for your case, but it was ideal for ours!

JTMon
  • 3,189
  • 22
  • 24
  • We did when we first implemented this structure. We have since switched away to NHibernate, but this has no bearing on the solution. Our validation code worked as is with no changes (only data access layer was changed). – JTMon May 24 '13 at 14:06
1

Modify the T4 template adding required annotations, this file is usually named MODELNAME.tt

find where the T4 is creating the class and methods to know where to put these.

     <#=codeStringGenerator.IgnoreJson(navigationProperty)#>


//create this method in file
public string IgnoreJson(NavigationProperty navigationProperty){
            string result = navigationProperty.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many ? "" : @"[JsonIgnore]
    [IgnoreDataMember]";

            return result;
        }

You will also need to add the namespaces;

<#=codeStringGenerator.UsingDirectives(inHeader: false)#>
using System.ComponentModel.DataAnnotations;
using Newtonsoft.Json;
using System.Runtime.Serialization;

Rebuild your classes by saving your model, all your methods should be annotated.

tswales
  • 11
  • 1