GONeale
GONeale

Reputation: 26484

Associate attribute with code generated property in .net

I wish to set an attribute on a public property in .NET, however I do not have access to the explicit property itself, as this has been code generated in another file.

I have this field:

public virtual string Name { get; set; }

I wish to set this:

[ValidateNonEmpty("Name is required", ExecutionOrder = 1)]
public virtual string Name { get; set; }

My class is marked as partial, but you cannot have partial properties. I thought I was on to something with the MetadataType class which is a new feature of Dynamic Data and DataAnnotations, but alas I feel it can only be used with Dynamic Data, is this true?

Citations: http://blogs.oosterkamp.nl/blogs/jowen/archive/2008/10/16/metadatatype-attribute.aspx http://blogs.msdn.com/davidebb/archive/2008/06/16/dynamic-data-and-the-associated-metadata-class.aspx

Is there any way I can set this attributes (even through web.config!) without touching the code generated class?

Thanks in advance, Graham

Upvotes: 18

Views: 5144

Answers (4)

Paul Roewer
Paul Roewer

Reputation: 21

This is a great solution, but it didn't work for my problem. I'm using EF 6 with code-first generated classes from an existing database. One of the columns in a table is an IDENTITY with auto generated values. However, the generated partial class did not provide the [DatabaseGenerated(DatabaseGeneratedOption.Identity)] attribute needed to have the database generate the key. The result is the error "Cannot insert explicit value for identity column in table 'mytable' when IDENTITY_INSERT is set to OFF.". I tried your solution but it didn't work. But if I add the attribute to the original generated class, it does work. So I'm still trying to find a solution that does not require the modifying of the auto generated file.

Here is the code I tried using your solution:

public interface IMyTable
{
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    int ID { get; set; }
}

public partial class MyTable : IMyTable
{
}

original generated code:

[Table("MyTable")]
public partial class MyTable
{
    [Key]
    [Column(Order = 1)]
    public int ID { get; set; }
}

Upvotes: 2

Benaiah
Benaiah

Reputation: 150

Since the generated class is a partial class, the following should work:

  1. Create an interface that has this property declared in it, and decorate it with ValidateNonEmpty attribute.
  2. Create your own partial class with same name as the AutoGenerated class, and make this implement the interface that you just created.
  3. The property should now be decorated with that attribute

For example:

// Decorate the properties with attributes as required
public interface IMyInterface
{
    [ValidateNonEmpty("Name is required")]
    string Name { get; set; }
}

// This is the partial class I created, that implements the interface
public partial class MyGeneratedClass : IMyInterface
{    
}

// This is the auto-generated class
public partial class MyGeneratedClass
{
    public virtual string Name { get; set; }
}

I got this idea from geekswithblogs.

Upvotes: 6

Sten Petrov
Sten Petrov

Reputation: 11040

Another option is to wrap the properties inside non-generated properties in the same class. Not ideal because you may end up having double properties but if you can make your generator make protected properties it'd be a pretty good approach.

Just had to deal with this problem: Entity Framework generates classes, I want to serialize them to JSON with simpler names.

// GENERATED BY EF
public partial class ti_Users
{
    public ti_Users()
    {
        this.ti_CardData = new HashSet<ti_CardData>();
        this.ti_Orders = new HashSet<ti_Orders>();
    }

    protected int userId { get; set; }
    protected string userName { get; set; }
    protected string userEmail { get; set; }
    protected string userPassHash { get; set; }
    protected Nullable<System.DateTime> userLastLogin { get; set; }
    protected string userLastIP { get; set; } 

    public virtual ICollection<ti_CardData> ti_CardData { get; set; }
    public virtual ICollection<ti_Orders> ti_Orders { get; set; }
}

and the add-on class:

[JsonObject(memberSerialization: MemberSerialization.OptIn)]
public partial class ti_Users
{
    [JsonProperty]
    public int UserId
    {
        get { return this.userId; }
        set { this.userId = value; }
    }

    [JsonProperty]
    public string Name
    {
        get { return this.userName; }
        set { this.userName = value; }
    }

    [JsonProperty]
    public string Email
    {
        get { return this.userEmail; }
        set { this.userEmail = value; }
    }

    [JsonProperty]
    public string PassHash
    {
        get { return this.userPassHash; }
        set { this.userPassHash = value; }
    }
} 

Upvotes: 1

Marc Gravell
Marc Gravell

Reputation: 1064044

This is a known nuisance; you simply can't add metadata to the generated members.

There are 6 options here (in order of increasing effort):

  • if you own the attribute, make it possible to declare it against the class, for example: [ValidateNonEmpty("Name", "Name is required", ExecutionOrder = 1)] - then add multiple attributes to the partial class definition
  • use a virtual / interface / etc method to query this, rather than via attributes
  • sublass the generated type; override or re-declare the member, adding the metadata (really messy)
  • use a custom TypeDescriptionProvider to provide dynamic metadata (lots and lots of work) - assuming that the consumer respects TypeDescriptor; most binding-related consumers do, but for example, Expression (used by many LINQ providers) doesn't
  • change the code-generator / write your own
  • try to extend something like PostSharp to do the work (I haven't found a way to do this, but I've love to hear if you find a way!)

I usually have success with the first option, unless it is a system-defined attribute ([DisplayName], etc). If [ValidateNonEmpty] is defined by dynamic data, then you might not be able to do this.

Upvotes: 23

Related Questions