Can I define properties in partial classes, then mark them with attributes in another partial class?
Asked Answered
T

5

96

Is there a way I can have a generated code file like so:

public partial class A 
{
    public string a { get; set; }
}

and then in another file:

public partial class A 
{
    [Attribute("etc")]
    public string a { get; set; }
}

So that I can have a class generated from the database and then use a non-generated file to mark it up?

Termination answered 23/9, 2010 at 20:43 Comment(5)
How much is "generated from the database"? Only property definitions, or code as well?Mump
Short answer, no. Long answer, dup of #457124.Implant
@snemarch: property definitions only, I plan on doing any other code by hand.Termination
Could you do with an interface+implementation split instead of partial class? Generate the interface from the database, implement (and add attributes) in the implementation.Mump
yes this is possible but with the use of metadata then have the other partial inherit that metadataPeristyle
S
36

I've seen something like this done in an article by Scott Guthrie (near the end of it) - didn't try it myself, though.
http://weblogs.asp.net/scottgu/archive/2010/01/15/asp-net-mvc-2-model-validation.aspx

[MetadataType(typeof(Person_Validation))]
public partial class Person
{
    // Partial class compiled with code produced by VS designer
}

[Bind(Exclude="ID")]
public class Person_Validation
{
    [Required(ErrorMessage = "First Name Required")]
    [StringLength(50, ErrorMessage = "Must be under 50 characters")]
    public string FirstName { get; set; }

    [Required(ErrorMessage = "Last Name Required")]
    [StringLength(50, ErrorMessage = "Must be under 50 characters")]
    public string LastName { get; set; }

    [Required(ErrorMessage = "Age Required")]
    [Range(0, 120, ErrorMessage = "Age must be between 0 and 120")]
    public int Age { get; set; }

    [Required(ErrorMessage = "Email Required")]
    [Email(ErrorMessage = "Not a valid email")]
    public string Email { get; set; }
}
Subzero answered 23/9, 2010 at 21:5 Comment(4)
This answer bears mentioning, but it is not a general solution to the question posed by the OP. Consumers of the attributes still need to know to look for the meta data class -- i.e. these attributes will not be returned by Attribute.GetCustomAttribute(...). (Fortunately for many use-cases, the consumers are written by MS and in certain situations this will work.)Implant
This solution does NOT solve the problem. Why do we look to decorate members in another file? Because the class is OVERWRITTEN each time the designer runs. Thus your attribute [MetaDataType ... will be cleared each time the designer runsSoftcover
@Desolator - The idea is that you don't put the MetadataType attribute in the file generated by the designer, you put it in the other file where the partial class Person is defined.Subzero
the problem with this solution is the class shouldve been partialPeristyle
A
97

Here is the solution I have been using for such cases. It is useful when you have auto-generated classes that you want to decorate with attributes. Let's say this is the auto-generated class:

public partial class UserProfile
{
    public int UserId { get; set; }
    public string UserName { get; set; }
    public string Firstname { get; set; }
    public string Lastname { get; set; }
}

And let's say, I would like to add an attribute to specify that UserId is the key. I would then create a partial class in another file like this:

[Table("UserProfile")]
[MetadataType(typeof(UserProfileMetadata))]
public partial class UserProfile
{
    internal sealed class UserProfileMetadata
    {
        [Key]
        [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
        public int UserId { get; set; }
    }
}
Ashliashlie answered 19/10, 2013 at 4:33 Comment(9)
Great solution. I knew that you could decorate the partial class with attributes in multiple files and even add interfaces and an inherited class to its declaration, but I didn't make the connection to the MetadataType attribute. Well done!Induna
What if I want to add an attribute to constructor of UserProfile?Anitraaniweta
MetadataType can only be used for Class, what if I want to do with a Struct?Diaz
MetadataType does not exist in .NET CoreJackpot
.net core uses ModelMetadataTypeShakta
Is there an option for .NET Core that isn't coupled to MVC? I'm writing a console app to mirror data from a legacy enterprise system to a SQL database and want to do something similar to the above but have to include a load of unwanted MVC packages for this one functionality.Priggish
@Priggish Support is supposed to be coming in .NET Core 3.0Chucho
Also works with virtual properties (in metadata class declare property without "virtual" keyword)Superscribe
@Shakta Does anyone know how to get this working in EF Core? I'm still getting the "The invoked method is cannot be used for the entity type 'HumanResources_ResourceMaster' because it does not have a primary key." error even when using the ModelMetadataType instead of MetadataType.Musjid
S
36

I've seen something like this done in an article by Scott Guthrie (near the end of it) - didn't try it myself, though.
http://weblogs.asp.net/scottgu/archive/2010/01/15/asp-net-mvc-2-model-validation.aspx

[MetadataType(typeof(Person_Validation))]
public partial class Person
{
    // Partial class compiled with code produced by VS designer
}

[Bind(Exclude="ID")]
public class Person_Validation
{
    [Required(ErrorMessage = "First Name Required")]
    [StringLength(50, ErrorMessage = "Must be under 50 characters")]
    public string FirstName { get; set; }

    [Required(ErrorMessage = "Last Name Required")]
    [StringLength(50, ErrorMessage = "Must be under 50 characters")]
    public string LastName { get; set; }

    [Required(ErrorMessage = "Age Required")]
    [Range(0, 120, ErrorMessage = "Age must be between 0 and 120")]
    public int Age { get; set; }

    [Required(ErrorMessage = "Email Required")]
    [Email(ErrorMessage = "Not a valid email")]
    public string Email { get; set; }
}
Subzero answered 23/9, 2010 at 21:5 Comment(4)
This answer bears mentioning, but it is not a general solution to the question posed by the OP. Consumers of the attributes still need to know to look for the meta data class -- i.e. these attributes will not be returned by Attribute.GetCustomAttribute(...). (Fortunately for many use-cases, the consumers are written by MS and in certain situations this will work.)Implant
This solution does NOT solve the problem. Why do we look to decorate members in another file? Because the class is OVERWRITTEN each time the designer runs. Thus your attribute [MetaDataType ... will be cleared each time the designer runsSoftcover
@Desolator - The idea is that you don't put the MetadataType attribute in the file generated by the designer, you put it in the other file where the partial class Person is defined.Subzero
the problem with this solution is the class shouldve been partialPeristyle
P
3

This is my answer
different class files or you can combine the metadatas in a same file but keep the namespace the same..so they can see each other obviously.

keep in mind when you update your model like add more columns you have to update the project class too.

--your model class
public partial class A {
    public string a {get; set;}
}

--your project class 
public class Ametadata {
     [Attribute("etc")]
     public string a {get; set;}
}


[MetadataType(typeof(Ametadata))]
public partial class A
{
}
Peristyle answered 24/2, 2017 at 16:34 Comment(0)
C
1

You need to define a partial class for your A class just like below example

using System.ComponentModel.DataAnnotations;

// your auto-generated partial class
public partial class A 
{
    public string MyProp { get; set; }
}

[MetadataType(typeof(AMetaData))]
public partial class A 
{

}

public class AMetaData
{
    [System.ComponentModel.DefaultValue(0)]
    public string MyProp { get; set; }
}
Crine answered 4/11, 2017 at 8:12 Comment(0)
C
0

Not as such; the compiler will complain that the member is defined in multiple parts. However, as the use of custom attributes is reflective in nature, you could define a "metadata" class and use it to contain decorators.

public class A
{
   public string MyString;
}

public class AMeta
{
   [TheAttribute("etc")]
   public object MyString;
}

...

var myA = new A();
var metaType = Type.GetType(myA.GetType().Name + "Meta");
var attributesOfMyString = metaType.GetMember("MyString").GetCustomAttributes();
Calamitous answered 23/9, 2010 at 20:58 Comment(3)
How often is it that the actor who is adding the attributes to his or her properties is also the person consuming them and will therefore know to look for the magical "Meta" classes?Implant
Quite often, in my experience. This wouldn't work for an existing aspect-oriented framework, but if you were decorating your domain with, say, custom validation attributes, you're the one looking for them and can define where. My team has done exactly this on one of our projects. The main disadvantage is not looking for the other class; it's maintaining two parallel classes while developing, one functional, the other decorative. That would be a problem in partial classes as well, if you were able to define partial fields/properties in the first place.Calamitous
@KirkWoll It's a good question IMO. I think that makes a good argument for putting the meta classes in the same file as the annotated class, potentially even at the top of the file, so that other coders will be more likely to find that code.Skipbomb

© 2022 - 2024 — McMap. All rights reserved.