实体框架6重用数据注释

时间:2014-04-30 11:21:27

标签: c# asp.net-mvc entity-framework data-annotations

我已经找了一段时间才能找到明确的解决方案,但还没有得出结论。我想在数据模型类上只指定一次数据注释,并且UI可以从视图模型类中看到它们,而无需再次指定它们。为了说明我的观点,假设我有一个UserAccount类......

public class UserAccount
{
    [Display(Name = "Username", Prompt = "Login As"), Required()]
    string UserName { get; set; }
    [Display(Name = "Password", Prompt = "Password"), Required(), DataType(DataType.Password), StringLength(255, MinimumLength = 7, ErrorMessage = "The password must be at least 7 characters")]
    string Password { get; set; }
}

现在我想指定一个视图模型,其中包含一个密码确认字段,该字段不会存储在数据库中,也可能存储在其他数据模型中,但我不想再次指定所有数据注释属性。这是关于尝试找到最佳实践,但也需要维护多个声明,在某些时候,一个将被修改,而其他声明将不会。

我已经查看了一个接口/继承解决方案,但由于各种原因,它并没有完全切断它。另一个可能的解决方案可能是在View Model类(或属性)上有一个Attribute来表示从...继承属性但是我找不到任何适合它的东西。

有没有人有任何明智的想法或以合适的方式实施这个想法?

4 个答案:

答案 0 :(得分:4)

您可以使用此atttribute来装饰您的viewmodel类:

[MetadataType(typeof(YourModelClass))]
public class YourViewModelClass
{
   // ...
}

假设两个类具有相同的属性,注释将被正确地包含在内。

有关详细信息,请参阅:MetadataTypeAttribute Class in MSDN

注意:在MDSN备注中,他们解释了为现有模型类添加额外元数据的用法,但这适用于您的案例。实际上,您可以一直这样做:注释您的ViewModel,并将其应用于模型的部分类。甚至可以创建一个单独的伙伴类,它应用于模型中的实体和视图模型。有了这些选项,任何一个类都可以定义"它自己的注释和"继承"来自哥们班的其他人。

重要提示:此解决方案与Hoots接受的解决方案非常不同,因为此解决方案可被EF或MVC等框架识别,用于创建模型或提供自动数据验证。必须手动使用Hoots的解决方案,因为框架不会自动使用它。

答案 1 :(得分:2)

  

我已经查看了一个接口/继承解决方案,但由于各种原因,它并没有完全切断它。

你能告诉我们为什么继承不适合你吗?

以下是继承解决方案的示例:

public class UserAccount : PasswordModel
{
    [Display(Name = "Username", Prompt = "Login As"), Required()]
    public string UserName { get; set; }

    [Display(Name = "Password", Prompt = "Password")]
    [StringLength(255, MinimumLength = 7, ErrorMessage = "The password must be at least 7 characters")]
    [Required]
    [DataType(DataType.Password)]
    public string Password { get; set; }
}

public class ResetPasswordViewModel : PasswordModel
{
    [Display(Name = "Retype Password", Prompt = "Password")]
    [Required]
    [DataType(DataType.Password)]
    [CompareAttribute("Password", ErrorMessage = "Password does not match")]
    string RetypePassword { get; set; }
}

UPDATE:

从M-V-C,您可以将设计模式扩展为M- VM -V-C

ViewModel

VM代表ViewModels。这些人就像模特,但他们不代表域而且他们没有坚持。它们代表了用户界面。

有关ViewModels的更多信息

基于前面的示例,ResetPasswordViewModel是表示UI的ViewModel。如果您担心持续RetypePassword,请不要在EF中添加ViewModel。

另一种方法

您可以使用合成替换继承。请参阅下面的示例。

public class ResetPasswordViewModel
{
    public UserAccount UserAccount { get; set; }

    [Display(Name = "Retype Password", Prompt = "Password")]
    [Required]
    [DataType(DataType.Password)]
    public string RetypePassword { get; set; }
}

使用上面的ViewModel,您可以获得Password属性的可重用性。您可以像访问它一样访问它:

@Model.UserAccount.Password
@Model.ResetPassword
  

我可能想在同一视图中组合不同的数据模型

这正是ViewModel的用途。您可以专门为View创建它们。

答案 2 :(得分:1)

您要求互相排斥的东西......您希望继承您的元数据,但您不希望以任意方式继承您的元数据......这通常不是一个有用的场景。你将花费更多的时间来寻找一些神奇的子弹场景,而不是花费你维持单独的模型,你可能仍然会得到一个你不是百分之百满意的低于标准的解决方案。

我知道你的挫败感......你不应该在多个地方保持相同的数据......你违反了DRY原则..不要重复自己......

问题是,您创建的任何解决方案都将违反其他原则,例如开放式原则或单一责任原则。通常这些原则彼此不一致,你必须在某个地方找到平衡点。

简单地说,最佳做法是使用单独的数据属性维护单独的视图和域模型,因为您的域和视图是单独的问题,应该保持独立。如果他们没有单独的顾虑,你可以简单地为两者使用相同的模型,并且没有必要跳过这些箍。

视图模型之间的区别正是因为您的视图通常与您的域有不同的要求。试图将两者混合在一起会导致痛苦。

答案 3 :(得分:1)

这是我在考虑其他可能的解决方案后提出的解决方案。它基于找到以下文章...

http://jendaperl.blogspot.co.uk/2010/10/attributeproviderattribute-rendered.html

我在这篇文章的最后部分列出了我的理由。

拥有现有的数据模型类......

public class UserAccount
{
     [Display(Name = "Username", Prompt = "Login As"), Required()]
     string UserName { get; set; }
     [Display(Name = "Password", Prompt = "Password"), Required(), DataType(DataType.Password), StringLength(255, MinimumLength = 7, ErrorMessage = "The password must be at least 7 characters")]
     string Password { get; set; }
}

如果我们可以将属性属性复制到使用它的视图中,那就太棒了,如果我们需要,可以覆盖它们,以便不需要重新审视使​​用此类的视图在一个简单的属性更改。这是我的解决方案。创建一个新属性,如下所示......

using System.ComponentModel;

namespace MyApp.ViewModels
{
    public class AttributesFromAttribute : AttributeProviderAttribute
    {
        public AttributesFromAttribute(Type type, string property)
            : base(type.AssemblyQualifiedName, property)
        {
        }

        public T GetInheritedAttributeOfType<T>() where T : System.Attribute
        {
            Dictionary<string,object> attrs = Type.GetType(this.TypeName).GetProperty(this.PropertyName).GetCustomAttributes(true).ToDictionary(a => a.GetType().Name, a => a);
            return attrs.Values.OfType<T>().FirstOrDefault();
        }

    }
}

现在您可以将以下内容添加到视图模型类中的相关属性...

[AttributesFrom(typeof(MyApp.DataModel.UserAccount), "UserName")]

... e.g

public class RegisterViewModel
{
    public UserAccount UserAccount { get; set; }
    public RegisterViewModel()
    {
        UserAccount = new UserAccount();
    }

    [AttributesFrom(typeof(MyApp.DataModel.UserAccount), "UserName")]
    string UserName { get; set; }
    [AttributesFrom(typeof(MyApp.DataModel.UserAccount), "Password")]
    string Password { get; set; }

    [AttributesFrom(typeof(MyApp.DataModel.UserAccount), "Password")]
    [Display(Name = "Confirm Password", Prompt = "Confirm Password"), Compare("Password", ErrorMessage = "Your confirmation doesn't match.")]
    public string PasswordConfirmation { get; set; }

}

这样就可以复制可以覆盖的属性(如上面的PasswordConfirmation),允许在同一个视图模型中使用多个数据模型,如果需要从代码访问继承的属性,可以使用GetInheritedAttributeOfType方法。例如......

public static class AttrHelper
{
   public static T GetAttributeOfType<T>(this ViewDataDictionary viewData) where T : System.Attribute
    {
        var metadata = viewData.ModelMetadata;
        var prop = metadata.ContainerType.GetProperty(metadata.PropertyName);
        var attrs = prop.GetCustomAttributes(false);

        // Try and get the attribute directly from the property.
        T ret = attrs.OfType<T>().FirstOrDefault();

        // If there isn't one, look at inherited attribute info if there is any.
        if(ret == default(T))
        {
            AttributesFromAttribute inheritedAttributes = attrs.OfType<AttributesFromAttribute>().FirstOrDefault();
            if (inheritedAttributes != null)
            {
                ret = inheritedAttributes.GetInheritedAttributeOfType<T>();
            }
        }

        // return what we've found.
        return ret;
    }
}

这可以从编辑模板中调用,例如......

var dataTypeAttr = AttrHelper.GetAttributeOfType<DataTypeAttribute>(ViewData);

首先会直接查看viewmodel的属性属性,但如果找不到任何内容,它会查看继承的属性,并调用GetInheritedAttributeOfType。

这最适合我,因为......

  1. 我觉得目前在视图模型和数据模型中重复DataAnnotations的做法对于可维护性或重用并不是很好。

  2. 使用MetadataType也不够灵活,它全有或全无,您无法在单个ViewModel上包含多个MetadataType属性。

  3. 缺少在没有属性的视图模型中封装数据模型,因为它也没有灵活性。您必须包含整个封装对象,因此无法在多个视图上填充DataModel。