views:

1652

answers:

6

Hi 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?

+1  A: 

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
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
+1  A: 

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
Marc, I am using WPF data-grid, http://www.codeplex.com/wpf. I don't think they support TypeDescriptionProvider. :/
ArielBH
Marc, So far I've been investigating your approach, if you can spare the time to help I would appreciate it :)
ArielBH
If it supports regular binding hooks, it should work. I'll see if I can do an example...
Marc Gravell
Example added (second post)
Marc Gravell
A: 

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  A: 

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
+2  A: 

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
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
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
+12  A: 

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; }
}
JasonW
Interesting, I will want to check it out. Thanks
ArielBH
I think this is *the* solution to use.
Dmitri Nesteruk
I agree. this is the best solution.
Satumba
Yeah, this is the bomb - great answer!
Jarrod Dixon
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