24

Preface Feb 2015 If you are still using Entity Framework EDMX, please do yourself a favor and checkout using Entity Framework Code First instead. The difference is that your tables are created from your model classes, rather than in EDMX where your model classes are created with your tables. It's an all around easier solution, and the problem in this question doesn't even exist!

Getting Started with Entity Framework 6 Code First using MVC 5

I have an existing SQL database, and I am using ADO.NET Enity Data Model for the model. I am trying to build some CRUD features into my MVC application.

In all the tutorials I have found on the subject, they build the model from scratch and add the attributes to the model class. For example:

    [Required]
    [StringLength(10)]
    public string Name { get; set; }

However, the model classes are auto-generated, so I think changing them is a bad idea (and will get written over anyway if the the database model is refreshed).

How would I add validation attributes?

Mason240
  • 2,924
  • 3
  • 30
  • 46

5 Answers5

36

You can create a partial class, separate from the EF generated class, to store the metadata in.

//Contact.cs - The original auto-generated file 
[System.ComponentModel.DataAnnotations.MetadataType(typeof(ContactMetadata))]
public partial class Contact
{
    public int ContactID { get; set; }
    public string ContactName { get; set; }
    public string ContactCell { get; set; }
}

//ContactMetadata.cs - New, seperate class

using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
internal sealed class ContactMetadata
{
    [Required(ErrorMessage = "Name is required.")]
    [StringLength(5)]  
    public string ContactName;
}
Mason240
  • 2,924
  • 3
  • 30
  • 46
  • 14
    Actually you should not try to modify the original auto-generated file , just create another file as the **partial** keyword will make it work like a charm. – Simon Wang Feb 28 '13 at 02:28
  • Oh yes, I agree. I have each class in its own file in a separate Metatdata folder. – Mason240 Feb 28 '13 at 15:22
  • 1
    The compiler produces warnings that say that ContactName "is never assigned to, and will always have its default value null"; is it safe to default properties like these? – Brad Jun 08 '13 at 22:32
  • 4
    It's giving me "This member is defined more than once" :( – SteveCav Jul 26 '13 at 03:42
  • 1
    I have noticed that once you have a Metadata class for an EF model, e.g. to add extra validation, you seem to lose any existing validation the EF model already provided. Is it correct that the Metadata acts as a replacement for attributes rather than extend the existing set of attributes? – iCollect.it Ltd Nov 14 '13 at 09:28
  • @TrueBlueAussie did you confirm that the EF generated validation is lost? Thanks! – Rachael May 21 '14 at 19:42
  • 3
    @SteveCav I also got that error. You have probably have figured it out by now, but if you haven't, you need to have the metadata class in the same assembly as the model class with the same namespace. – John Verco Feb 04 '15 at 17:24
19

Mason240 answer works well, I will try to improve it : you could create a new ContactDataAnnotations.cs class with :

//ContactDataAnnotations.cs - A new file 
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

[MetadataType(typeof(ContactMetadata))]
public partial class Contact
{
    // No field here
}

internal sealed class ContactMetadata
{
    [Required(ErrorMessage = "Name is required.")]
    [StringLength(5)]  
    public string ContactName {get; set; }
}

This way, you can regenerate your Contact class through EF without touching DataAnnotations - and without warning, by the way.

Christian Navelot
  • 1,174
  • 9
  • 9
4

This has been answered correctly already, but I wanted to also add that I always find nesting the metadata seemed a bit cleaner to me, IMHO.

[MetadataType(typeof(ProductDescription.Metadata))]
public partial class ProductDescription
{
    sealed class Metadata
    {
        [Key]
        public long id { get; set; }
        [Display(Name = "Title")]
        public string title { get; set; }
        // ...
    }
}

I also noticed an added benefit of keeping the Metadata private to the class. The attribute will only work on the correct class, preventing a bug that may occur if you duplicate the class (to create a similar one). The bug can occur if you forget to change the class name in the attribute when renaming the duplicated class.

James Wilkins
  • 6,836
  • 3
  • 48
  • 73
4

I know this has been marked answered but I want to clear some stuffs up.

@SteveCav said: "This member is defined more than once". I had the same exact same error. Spent hours trying to figure out.

To finally correct it, you have to create a separate file class in the same Assembly(I think this is already mentioned here). But what I want to stress is that this class should be nested with a partial class representing the Inner Class.

And then you decorate that inner class with the Annotation class. Like this:

//ContactMap.cs - Present in the same namespace as Contact.cs
[System.ComponentModel.DataAnnotations.MetadataType(typeof(ContactMap))]
partial class Contact // Present in the ContactMap class. This represent the Inner Class
{
}

//ContactMap.cs - This represent the outer class

using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
public class ContactMetadata
{
    [Required(ErrorMessage = "Name is required.")]
    [StringLength(5)]  
    public string ContactName;
}

Hope this is clearer or more understandable.

IAmGroot
  • 13,760
  • 18
  • 84
  • 154
Francky
  • 55
  • 1
  • 8
3

There is a variation of the suggested answers here that allows you to use classes in different assemblies and namespaces. I haven't actually tested it with EF, but I am using this for Swagger codegen API model classes.

In a nutshell: inherit from the model class and add metadata on the inherited class. An added benefit is that with Swagger codegen you can use the API model directly without mapping and for initial forms you can use the protected default ctor.

[MetadataType(typeof(LocalAssemblyModelMetadata))]
public class LocalAssemblyModel : IO.Swagger.Model.OtherAssemblyModel 
{
    public LocalAssemblyModel() : base ()     { }
}



public sealed class LocalAssemblyModelMetadata
{
    [Required(ErrorMessage = "BaseclassProperty is mandatory.")]
    public string BaseclassProperty { get; set; }
}
Sentinel
  • 3,582
  • 1
  • 30
  • 44