How can I add my attributes to Code-Generated Linq2Sql classes properties?
Asked Answered
A

6

23

I would like to add attributes to Linq 2 Sql classes properties. Such as this Column is browsable in the UI or ReadOnly in the UI and so far.

I've thought about using templates, anybody knows how to use it? or something different?

Generally speaking, would do you do to address this issue with classes being code-generated?

Actuate answered 26/12, 2008 at 11:10 Comment(0)
A
5

As requested, here's an approach using a CustomTypeDescriptor to edit the attributes at run-time; the example here is win-forms, but it should be pretty simple to swap it into WPF to see if it works...

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Forms;
// example POCO
class Foo {
    static Foo()
    {   // initializes the custom provider (the attribute-based approach doesn't allow
        // access to the original provider)
        TypeDescriptionProvider basic = TypeDescriptor.GetProvider(typeof(Foo));
        FooTypeDescriptionProvider custom = new FooTypeDescriptionProvider(basic);
        TypeDescriptor.AddProvider(custom, typeof(Foo));
    }
    public string Name { get; set; }
    public DateTime DateOfBirth { get; set; }
}
// example form
static class Program {
    [STAThread]
    static void Main() {
        Application.EnableVisualStyles();
        Application.Run( new Form {
                Controls = {
                    new DataGridView {
                        Dock = DockStyle.Fill,
                        DataSource = new BindingList<Foo> {
                            new Foo { Name = "Fred", DateOfBirth = DateTime.Today.AddYears(-20) }
                        }
                    }
                }
            });
    }
}

class FooTypeDescriptionProvider : TypeDescriptionProvider
{
    ICustomTypeDescriptor descriptor;
    public FooTypeDescriptionProvider(TypeDescriptionProvider parent) : base(parent) { }
    public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance)
    {   // swap regular descriptor for bespoke (Foo) descriptor
        if (descriptor == null)
        {
            ICustomTypeDescriptor desc = base.GetTypeDescriptor(typeof(Foo), null);
            descriptor = new FooTypeDescriptor(desc);
        }
        return descriptor;
    }
}
class FooTypeDescriptor : CustomTypeDescriptor
{
    internal FooTypeDescriptor(ICustomTypeDescriptor parent) : base(parent) { }
    public override PropertyDescriptorCollection GetProperties()
    {   // wrap the properties
        return Wrap(base.GetProperties());
    }
    public override PropertyDescriptorCollection GetProperties(Attribute[] attributes)
    {   // wrap the properties
        return Wrap(base.GetProperties(attributes));
    }

    static PropertyDescriptorCollection Wrap(PropertyDescriptorCollection properties)
    {
        // here's where we have an opportunity to swap/add/remove properties
        // at runtime; we'll swap them for pass-thru properties with
        // edited atttibutes
        List<PropertyDescriptor> list = new List<PropertyDescriptor>(properties.Count);
        foreach (PropertyDescriptor prop in properties)
        {
            // add custom attributes here...
            string displayName = prop.DisplayName;
            if (string.IsNullOrEmpty(displayName)) displayName = prop.Name;

            list.Add(new ChainedPropertyDescriptor(prop, new DisplayNameAttribute("Foo:" + displayName)));
        }
        return new PropertyDescriptorCollection(list.ToArray(), true);
    }
}


class ChainedPropertyDescriptor : PropertyDescriptor
{
    // this passes all requests through to the underlying (parent)
    // descriptor, but has custom attributes etc;
    // we could also override properties here...
    private readonly PropertyDescriptor parent;
    public ChainedPropertyDescriptor(PropertyDescriptor parent, params Attribute[] attributes)
        : base(parent, attributes)
    {
        this.parent = parent;
    }
    public override bool ShouldSerializeValue(object component) { return parent.ShouldSerializeValue(component); }
    public override void SetValue(object component, object value) { parent.SetValue(component, value); }
    public override object GetValue(object component) { return parent.GetValue(component); }
    public override void ResetValue(object component) { parent.ResetValue(component); }
    public override Type PropertyType {get { return parent.PropertyType; } }
    public override bool IsReadOnly { get { return parent.IsReadOnly; } }
    public override bool CanResetValue(object component) {return parent.CanResetValue(component);}
    public override Type ComponentType { get { return parent.ComponentType; } }
    public override void AddValueChanged(object component, EventHandler handler) {parent.AddValueChanged(component, handler);  }
    public override void RemoveValueChanged(object component, EventHandler handler) { parent.RemoveValueChanged(component, handler); }
    public override bool SupportsChangeEvents { get { return parent.SupportsChangeEvents; } }
}
Anglosaxon answered 27/12, 2008 at 16:38 Comment(10)
Marc Thanks for you help!. I'm still uncertain, sorry for me being so thick. So in this example if I wanted to make the "Name" property ReadOnly and only this column what do you need to do? do "if prop.DisplayName == "Name" ... in the Wrap method?Actuate
Well, you should be able to add a ReadOnlyAttribute(true) - however, in this case it is probably easier to override IsReadOnly. You might want to add a specific subclass of ChainedPropertyDescriptor for such.Anglosaxon
What am I doing wrong if I use your code and get a stack overflow exception? I've just replaced Foo with my class, nothing else. The overflow is caused by this chain of calls: Wrap(base.GetProperties()); -> Inside of GetTypeDescriptor(Type objectType, object instance) the line return descriptor; calls again to GetProperties() that has Wrap(base.GetProperties());Myca
The variable descriptor is always null. Why?Myca
@Myca I'd need to see an example; I've double checked, and the code as posted works fine. The field descriptor gets a non-null value on line 48Anglosaxon
Please, could you type the mentioned line 48?Myca
@Myca descriptor = new FooTypeDescriptor(desc);Anglosaxon
@MarcGravell The problem is that every time base.GetProperties() is called, calls to GetTypeDescriptor(Type objectType, object instance) and descriptor is null again. But you are right field decriptor is initialized.Myca
@Myca which doesn't happen in the code I've posted, hence why an example would go a long wayAnglosaxon
@MarcGravell I'm so sorry I was forgetting the static constructor. Solved!Myca
P
39

You can take advantage of the new Metadata functionality in the System.ComponentModel.DataAnnotations which will allow us to separate the MetaData from the existing domain model.

For example:

[MetadataType (typeof (BookingMetadata))]
public partial class Booking
{
 // This is your custom partial class     
}

public class BookingMetadata
{
 [Required] [StringLength(15)]
 public object ClientName { get; set; }

 [Range(1, 20)]
 public object NumberOfGuests { get; set; }

 [Required] [DataType(DataType.Date)]
 public object ArrivalDate { get; set; }
}
Prohibitory answered 20/3, 2009 at 17:21 Comment(5)
Interesting, I will want to check it out. ThanksActuate
I think this is the solution to use.Cerebro
I agree. this is the best solution.Git
Yeah, this is the bomb - great answer!Tannate
Note: attributes in the MetadataClass are not actually added directly to the target class. For example, if you ran MemberInfo.IsDefined() on Booking.ClientName looking for the Required attribute, you would not find it. It appears that libraries that use MetadataType-applied attributes must look for a class-level MetadataType attribute on the target class and, if found, search the meta class for any relevant attributes. I was disappointed that I couldn't define custom attributes, apply them with MetadataType, and detect them with Member.IsDefined(). For this I think you need Marc's solution.Bounty
A
5

As requested, here's an approach using a CustomTypeDescriptor to edit the attributes at run-time; the example here is win-forms, but it should be pretty simple to swap it into WPF to see if it works...

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Forms;
// example POCO
class Foo {
    static Foo()
    {   // initializes the custom provider (the attribute-based approach doesn't allow
        // access to the original provider)
        TypeDescriptionProvider basic = TypeDescriptor.GetProvider(typeof(Foo));
        FooTypeDescriptionProvider custom = new FooTypeDescriptionProvider(basic);
        TypeDescriptor.AddProvider(custom, typeof(Foo));
    }
    public string Name { get; set; }
    public DateTime DateOfBirth { get; set; }
}
// example form
static class Program {
    [STAThread]
    static void Main() {
        Application.EnableVisualStyles();
        Application.Run( new Form {
                Controls = {
                    new DataGridView {
                        Dock = DockStyle.Fill,
                        DataSource = new BindingList<Foo> {
                            new Foo { Name = "Fred", DateOfBirth = DateTime.Today.AddYears(-20) }
                        }
                    }
                }
            });
    }
}

class FooTypeDescriptionProvider : TypeDescriptionProvider
{
    ICustomTypeDescriptor descriptor;
    public FooTypeDescriptionProvider(TypeDescriptionProvider parent) : base(parent) { }
    public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance)
    {   // swap regular descriptor for bespoke (Foo) descriptor
        if (descriptor == null)
        {
            ICustomTypeDescriptor desc = base.GetTypeDescriptor(typeof(Foo), null);
            descriptor = new FooTypeDescriptor(desc);
        }
        return descriptor;
    }
}
class FooTypeDescriptor : CustomTypeDescriptor
{
    internal FooTypeDescriptor(ICustomTypeDescriptor parent) : base(parent) { }
    public override PropertyDescriptorCollection GetProperties()
    {   // wrap the properties
        return Wrap(base.GetProperties());
    }
    public override PropertyDescriptorCollection GetProperties(Attribute[] attributes)
    {   // wrap the properties
        return Wrap(base.GetProperties(attributes));
    }

    static PropertyDescriptorCollection Wrap(PropertyDescriptorCollection properties)
    {
        // here's where we have an opportunity to swap/add/remove properties
        // at runtime; we'll swap them for pass-thru properties with
        // edited atttibutes
        List<PropertyDescriptor> list = new List<PropertyDescriptor>(properties.Count);
        foreach (PropertyDescriptor prop in properties)
        {
            // add custom attributes here...
            string displayName = prop.DisplayName;
            if (string.IsNullOrEmpty(displayName)) displayName = prop.Name;

            list.Add(new ChainedPropertyDescriptor(prop, new DisplayNameAttribute("Foo:" + displayName)));
        }
        return new PropertyDescriptorCollection(list.ToArray(), true);
    }
}


class ChainedPropertyDescriptor : PropertyDescriptor
{
    // this passes all requests through to the underlying (parent)
    // descriptor, but has custom attributes etc;
    // we could also override properties here...
    private readonly PropertyDescriptor parent;
    public ChainedPropertyDescriptor(PropertyDescriptor parent, params Attribute[] attributes)
        : base(parent, attributes)
    {
        this.parent = parent;
    }
    public override bool ShouldSerializeValue(object component) { return parent.ShouldSerializeValue(component); }
    public override void SetValue(object component, object value) { parent.SetValue(component, value); }
    public override object GetValue(object component) { return parent.GetValue(component); }
    public override void ResetValue(object component) { parent.ResetValue(component); }
    public override Type PropertyType {get { return parent.PropertyType; } }
    public override bool IsReadOnly { get { return parent.IsReadOnly; } }
    public override bool CanResetValue(object component) {return parent.CanResetValue(component);}
    public override Type ComponentType { get { return parent.ComponentType; } }
    public override void AddValueChanged(object component, EventHandler handler) {parent.AddValueChanged(component, handler);  }
    public override void RemoveValueChanged(object component, EventHandler handler) { parent.RemoveValueChanged(component, handler); }
    public override bool SupportsChangeEvents { get { return parent.SupportsChangeEvents; } }
}
Anglosaxon answered 27/12, 2008 at 16:38 Comment(10)
Marc Thanks for you help!. I'm still uncertain, sorry for me being so thick. So in this example if I wanted to make the "Name" property ReadOnly and only this column what do you need to do? do "if prop.DisplayName == "Name" ... in the Wrap method?Actuate
Well, you should be able to add a ReadOnlyAttribute(true) - however, in this case it is probably easier to override IsReadOnly. You might want to add a specific subclass of ChainedPropertyDescriptor for such.Anglosaxon
What am I doing wrong if I use your code and get a stack overflow exception? I've just replaced Foo with my class, nothing else. The overflow is caused by this chain of calls: Wrap(base.GetProperties()); -> Inside of GetTypeDescriptor(Type objectType, object instance) the line return descriptor; calls again to GetProperties() that has Wrap(base.GetProperties());Myca
The variable descriptor is always null. Why?Myca
@Myca I'd need to see an example; I've double checked, and the code as posted works fine. The field descriptor gets a non-null value on line 48Anglosaxon
Please, could you type the mentioned line 48?Myca
@Myca descriptor = new FooTypeDescriptor(desc);Anglosaxon
@MarcGravell The problem is that every time base.GetProperties() is called, calls to GetTypeDescriptor(Type objectType, object instance) and descriptor is null again. But you are right field decriptor is initialized.Myca
@Myca which doesn't happen in the code I've posted, hence why an example would go a long wayAnglosaxon
@MarcGravell I'm so sorry I was forgetting the static constructor. Solved!Myca
S
1

You may want to consider using Damien Guard's T4 templates for Linq To Sql. Modifying his templates would most likely give you the results you seek.

Hope this helps!

Steelwork answered 26/12, 2008 at 11:15 Comment(1)
This is good when you want to apply the same attribute with the same parameters. If I have an Visibility attribute for the UI: for the ID column I would assign it to not be visible as a parameter but for the Name column I will assig it as visible.Actuate
A
1

This is a common problem with code-generation; while you can add members and class level attributes via an additional partial class, you can't add attributes to the generated members. To compensate, some attribute-based mechanisms allow you to specify the attributes at the class (naming the member), but not any of the ones you cite.

One hardcore option would be to write a TypeDescriptionProvider that supplies custom PropertyDescriptor definitions for the properties. This would allow you to fully control the metadata used by UI binding tools like PropertyGrid, DataGridView, etc.

However, this is possibly too much work simply to set a few UI propertiex if you can also set them by hand! But if you are interested in pursuing that option, let me know - it is a familiar area to me, but too much code to write an example if you don't want it.

Note: if you are using PropertyGrid, then you can't set the properties by hand, but you can write a TypeConverter, which is a bit less work than a full TypeDescriptionProvider; just inherit from ExpandableObjectConverter and override GetProperties(). You'll still need a shim PropertyDescriptor, so still not trivial...

Anglosaxon answered 26/12, 2008 at 11:25 Comment(3)
Marc, I am using WPF data-grid, codeplex.com/wpf. I don't think they support TypeDescriptionProvider. :/Actuate
Marc, So far I've been investigating your approach, if you can spare the time to help I would appreciate it :)Actuate
If it supports regular binding hooks, it should work. I'll see if I can do an example...Anglosaxon
G
1

You can use a partial class to make your entity implement a interface that declares the same properties of your entity and then put the attributes on the interface.

This way you can use the interface type to get the attributes from the properties.

I don't know if you will be able to use the attributes this way, but you can try something like that.

Example:


public interface IConcept {
    long Code { get; set; }
    [Unique]
    string Name { get; set; }
    bool IsDefault { get; set; }
}

public partial class Concept : IConcept { }

[Table(Name="dbo.Concepts")]
public partial class Concept
{
//...
}
Gownsman answered 26/12, 2008 at 14:25 Comment(0)
G
1

You can also write/use another code generator in place of the default MSLinqToSQLGenerator.

One option to start from is this one.

I built my own, according to my needs. I don't know if there is a way to indicate which attributes must be placed in each property using the DBML Designer, or xml file, but maybe you can find a way to use this alternative to help you with your problem.

Gownsman answered 26/12, 2008 at 14:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.