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?

ArielBH
  • 1,991
  • 3
  • 22
  • 38

6 Answers6

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; }
}
Jason Wicker
  • 3,416
  • 2
  • 25
  • 32
  • Interesting, I will want to check it out. Thanks – ArielBH Mar 21 '09 at 14:03
  • 3
    I think this is *the* solution to use. – Dmitri Nesteruk Mar 30 '09 at 12:44
  • 1
    I agree. this is the best solution. – Satumba May 18 '09 at 11:52
  • 2
    Yeah, this is the bomb - great answer! – Jarrod Dixon Oct 16 '09 at 08:50
  • 5
    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. – shaunmartin Dec 18 '09 at 19:50
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; } }
}
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • 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? – ArielBH Dec 28 '08 at 20:30
  • 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. – Marc Gravell Dec 28 '08 at 20:55
  • 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()); – lorddarkangel Jun 26 '13 at 10:51
  • The variable descriptor is always null. Why? – lorddarkangel Jun 26 '13 at 11:03
  • @lorddarkangel 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 48 – Marc Gravell Jun 26 '13 at 11:05
  • Please, could you type the mentioned line 48? – lorddarkangel Jun 26 '13 at 11:11
  • @lorddarkangel `descriptor = new FooTypeDescriptor(desc);` – Marc Gravell Jun 26 '13 at 11:12
  • @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. – lorddarkangel Jun 26 '13 at 11:27
  • @lorddarkangel which doesn't happen in the code I've posted, hence why an example would go a long way – Marc Gravell Jun 26 '13 at 11:28
  • @MarcGravell I'm so sorry I was forgetting the static constructor. Solved! – lorddarkangel Jun 26 '13 at 12:22
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!

Brad Leach
  • 16,857
  • 17
  • 72
  • 88
  • 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. – ArielBH Dec 26 '08 at 14:34
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...

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • Marc, I am using WPF data-grid, http://www.codeplex.com/wpf. I don't think they support TypeDescriptionProvider. :/ – ArielBH Dec 26 '08 at 13:21
  • Marc, So far I've been investigating your approach, if you can spare the time to help I would appreciate it :) – ArielBH Dec 26 '08 at 15:45
  • If it supports regular binding hooks, it should work. I'll see if I can do an example... – Marc Gravell Dec 27 '08 at 16:13
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
{
//...
}
Rafael Romão
  • 1,788
  • 3
  • 20
  • 35
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.

Rafael Romão
  • 1,788
  • 3
  • 20
  • 35