如何动态地将实体属性设置为只读?

时间:2013-11-04 11:24:24

标签: entity-framework data-annotations dbcontext

我正在使用EF 4.5和DbContext。在业务规则层级,我应该实现检查以避免在某些实体方案中更改实体值属性。示例:StartProjecteDate应该是ProjectIsStarted只读,而不是其他状态。

我遵循 DRY 原则,因此,我应该能够从上下文和UI中检查只读属性列表。

我的问题:

是否有可以将属性动态设置为只读的DataAnnotation验证器?

(如果没有,是否有针对此问题的不同/更好的解决方案?)

请注意,我使用Web Forms(和Telerik)架构时,欢迎使用干净优雅的图案。

我尝试设置并获取运行时EditableAttribute为Jesse Webb解释,但我无法从属性获取dataannotation属性,我的代码:

<EditableAttribute(False)>
<MaxLength(400, ErrorMessage:="Màxim 400 caracters")>
Public Property NomInvertebrat As String

enter image description here

2013年11月8日编辑在挖掘文档后,似乎数据注释是否为类而非对象本身。也许iReadonlyableProperties接口可能是一种方式。

3 个答案:

答案 0 :(得分:2)

我有一个包含扩展方法的类,可以让我读取这样的数据注释:

int maxRefLen = ReflectionAPI.GetProperty<Organisation, String>(x => x.Name)
                             .GetAttribute<StringLengthAttribute>()
                             .GetValueOrDefault(x => x.MaximumLength, 256);

因此,如果您使用它,您应该能够像这样获得EditableAttribute的值:

bool isEditable = ReflectionAPI.GetProperty<Foo, String>(x => x.NomInvertebrat)
                               .GetAttribute<EditableAttribute>()
                               .GetValueOrDefault(x => x.AllowEdit, true);

至于在运行时设置数据注释,我自己没有这样做,但我已经读到这里有一个解决方案:Setting data-annotations at runtime

获取特定类型的所有数据注释的列表,我认为这将需要reading the entity framework metadata。我再次尝试过这个。

如果你将它们加在一起,我个人认为它感觉笨重而不是优雅,但你已经要求使用DataAnnotation的解决方案,更优雅的东西可能意味着进入你的架构。

我倾向于这样做:

public bool StartDateIsReadOnly
{
   //use this property client-side to disable the input
   get{ return Project.IsStarted;}
}

//Implement IValidatable object to do server side validation
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext
{
   bool startdateIsChanged = // I'll leave you to work out this bit
   var results = new List<ValidationResult>();
   if(StartDateIsReadOnly && startdateIsChanged)
   results.Add(new ValidationResult("Start Date cannot be changed after project is started");
}

这是ReflectionAPI类:

请注意,该课程包括@JonSkeet发布并描述为“邪恶”的黑客攻击的一部分。我个人认为这一点并不是那么糟糕,但你应该阅读以下参考资料:

Override a generic method for value types and reference types

Evil code - overload resolution workaround

public static class ReflectionAPI
{

    public static int GetValueOrDefault<TInput>(this TInput a, Func<TInput, int> func, int defaultValue)
        where TInput : Attribute
    //Have to restrict to struct or you get the error:
    //The type 'R' must be a non-nullable value type in order to use it as parameter 'T' in the generic type or method 'System.Nullable<T>'
    {
        if (a == null)
            return defaultValue;

        return func(a);
    }

    public static Nullable<TResult> GetValueOrDefault<TInput, TResult>(this TInput a, Func<TInput, TResult> func, Nullable<TResult> defaultValue)
        where TInput : Attribute
        where TResult : struct
    //Have to restrict to struct or you get the error:
    //The type 'R' must be a non-nullable value type in order to use it as parameter 'T' in the generic type or method 'System.Nullable<T>'
    {
        if (a == null)
            return defaultValue;

        return func(a);
    }

    //In order to constrain to a class without interfering with the overload that has a generic struct constraint
    //we need to add a parameter to the signature that is a reference type restricted to a class
    public class ClassConstraintHack<T> where T : class { }

    //The hack means we have an unused parameter in the signature
    //http://msmvps.com/blogs/jon_skeet/archive/2010/11/02/evil-code-overload-resolution-workaround.aspx
    public static TResult GetValueOrDefault<TInput, TResult>(this TInput a, Func<TInput, TResult> func, TResult defaultValue, ClassConstraintHack<TResult> ignored = default(ClassConstraintHack<TResult>))
        where TInput : Attribute
        where TResult : class
    {
        if (a == null)
            return defaultValue;

        return func(a);
    }

    //I don't go so far as to use the inheritance trick decribed in the evil code overload resolution blog, 
    //just create some overloads that take nullable types - and I will just keep adding overloads for other nullable type
    public static bool? GetValueOrDefault<TInput>(this TInput a, Func<TInput, bool?> func, bool? defaultValue)
where TInput : Attribute
    {
        if (a == null)
            return defaultValue;

        return func(a);
    }

    public static int? GetValueOrDefault<TInput>(this TInput a, Func<TInput, int?> func, int? defaultValue)
where TInput : Attribute
    {
        if (a == null)
            return defaultValue;

        return func(a);
    }

    public static T GetAttribute<T>(this PropertyInfo p) where T : Attribute
    {
        if (p == null)
            return null;

        return p.GetCustomAttributes(false).OfType<T>().LastOrDefault();
    }

    public static PropertyInfo GetProperty<T, R>(Expression<Func<T, R>> expression)
    {
        if (expression == null)
            return null;

        MemberExpression memberExpression = expression.Body as MemberExpression;
        if (memberExpression == null)
            return null;

        return memberExpression.Member as PropertyInfo;
    }
}

答案 1 :(得分:1)

.NET允许您通过实现System.ComponentModel.ICustomTypeDescriptor来动态更改Class的结构。大多数序列化程序都支持此接口。

// Sample Serialization

foreach(PropertyDescriptor p in TypeDescriptor.GetProperties(obj)){
    string name = p.PropertyName;
    object value = p.GetValue(obj);
}

内部TypeDescriptor使用Reflection,但实现允许我们轻松覆盖反射属性。

以下是实施的三个步骤,

// Implement System.ComponentModel.ICustomTypeDescriptor Interface on
// your Entity

public class MyEntity: System.ComponentModel.ICustomTypeDescriptor
{
     ....
     // most methods needs only call to default implementation as shown below

    System.ComponentModel.AttributeCollection      
    System.ComponentModel.ICustomTypeDescriptor.GetAttributes()
    {
        return TypeDescriptor.GetAttributes(this, true);
    }

    string System.ComponentModel.ICustomTypeDescriptor.GetClassName()
    {
        return TypeDescriptor.GetClassName(this, true);
    }

    string System.ComponentModel.ICustomTypeDescriptor.GetComponentName()
    {
        return TypeDescriptor.GetComponentName(this, true);
    }

    System.ComponentModel.TypeConverter System.ComponentModel.ICustomTypeDescriptor.GetConverter()
    {
        return TypeDescriptor.GetConverter(this, true);
    }

    System.ComponentModel.EventDescriptor System.ComponentModel.ICustomTypeDescriptor.GetDefaultEvent()
    {
        return TypeDescriptor.GetDefaultEvent(this, true);
    }

    System.ComponentModel.PropertyDescriptor System.ComponentModel.ICustomTypeDescriptor.GetDefaultProperty()
    {
        return TypeDescriptor.GetDefaultProperty(this, true);
    }

    object System.ComponentModel.ICustomTypeDescriptor.GetEditor(Type editorBaseType)
    {
        return TypeDescriptor.GetEditor(this, editorBaseType, true);
    }

    System.ComponentModel.EventDescriptorCollection System.ComponentModel.ICustomTypeDescriptor.GetEvents(Attribute[] attributes)
    {
        return TypeDescriptor.GetEvents(this, attributes, true);
    }

    System.ComponentModel.EventDescriptorCollection System.ComponentModel.ICustomTypeDescriptor.GetEvents()
    {
        return TypeDescriptor.GetEvents(this, true);
    }

    System.ComponentModel.PropertyDescriptorCollection System.ComponentModel.ICustomTypeDescriptor.GetProperties(Attribute[] attributes)
    {
        return TypeDescriptor.GetProperties(this, attributes, true);
    }

    object System.ComponentModel.ICustomTypeDescriptor.GetPropertyOwner(System.ComponentModel.PropertyDescriptor pd)
    {
        return this;
    }

    // The Only method that needs different implementation is below
    System.ComponentModel.PropertyDescriptorCollection 
    System.ComponentModel.ICustomTypeDescriptor.GetProperties()
    {
       // ... you are supposed to create new instance of 
       // PropertyDescriptorCollection with PropertyDescriptor

       PropertyDescriptorCollection pdc = new PropertyDescriptorCollection();

       foreach(PropertyDescriptor p in TypeDescriptor.GetProperties(this,true)){
            // if readonly..

            AtomPropertyDescriptor ap = new AtomPropertyDescriptor(p, p.Name);
            // or
            AtomPropertyDescriptor ap = new AtomPropertyDescriptor(p, p.Name, 
                true,
                new XmlIgnoreAttribute(),
                new ScriptIgnoreAttribute(),
                new ReadOnlyAttribute());
            pdc.Add(ap);
       }

       return pdc;
    }
}


// And here is the AtomPropertyDescriptorClass

public class AtomPropertyDescriptor : PropertyDescriptor
{
    PropertyDescriptor desc;

    bool? readOnly = null;

    public AtomPropertyDescriptor(PropertyDescriptor pd, string name, 
        bool? readOnly, params Attribute[] attrs) :
        base(name, attrs)
    {
        desc = pd;
        this.readOnly = readOnly;
    }

    public override bool CanResetValue(object component)
    {
        return desc.CanResetValue(component);
    }

    public override Type ComponentType
    {
        get
        {
            return desc.ComponentType;
        }
    }

    public override object GetValue(object component)
    {
        return desc.GetValue(component);
    }

    public override bool IsReadOnly
    {
        get
        {
            if (readOnly.HasValue)
                return readOnly.Value;
            return desc.IsReadOnly;
        }
    }

    public override Type PropertyType
    {
        get { return desc.PropertyType; }
    }

    public override void ResetValue(object component)
    {
        desc.ResetValue(component);
    }

    public override void SetValue(object component, object value)
    {
        desc.SetValue(component, value);
    }

    public override bool ShouldSerializeValue(object component)
    {
        return desc.ShouldSerializeValue(component);
    }
}

答案 2 :(得分:1)

我认为您正在寻找的是这样的自定义注释属性:

<DisableEditAttribute(this.IsProjectStarted)>
Public Property NomInvertebrat As String

public override bool IsValid(bool value)
{
    bool result = true;
    // Add validation logic here.
    if(value)
    {
         //Compare Current Value Against DB Value.
    }
    return result;
}

请参阅MSDN:http://msdn.microsoft.com/en-us/library/cc668224(v=vs.98).aspx