实体框架6重用数据注释

本文关键字:数据 注释 6重 框架 实体 | 更新日期: 2023-09-27 18:01:09

我已经寻找了一段时间的最终解决方案,但尚未得出结论。我想在数据模型类上只指定一次数据注释,并让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; }
}

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

我看过一个接口/继承解决方案,但由于各种原因,它并没有完全解决问题。另一个潜在的解决方案可能是在视图模型类(或属性)上设置一个Attribute,表示从…继承属性。。。但到目前为止我还找不到合适的东西。

有人有什么聪明的想法或以合适的方式实现了这一点吗?

实体框架6重用数据注释

您可以用以下内容装饰您的视图模型类:

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

假设两个类都具有相同的属性,则注释将正确地嵌入其中。

有关详细信息,请参阅MSDN中的MetadataTypeAttribute类。

注意:在MDSN注释中,它们解释了为现有模型类添加额外元数据的用途,但这将适用于您的情况。事实上,您可以一直这样做:对ViewModel进行注释,并将其应用于模型的分部类。或者甚至创建一个应用于模型中实体和视图模型的好友类。有了这些选项中的任何一个,任何类都可以"定义"自己的注释,并从buddy类"继承"其他注释。

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

我看过一个接口/继承解决方案,但由于各种原因,它并没有完全解决问题

你能告诉我们为什么遗产对你不起作用吗?

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

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; }
}

更新:

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

ViewModel

VM代表ViewModels。这些家伙就像模型,但它们不代表域,也不持久化。它们代表UI。

有关ViewModels 的更多信息

  • https://stackoverflow.com/a/11074506/1027250
  • https://stackoverflow.com/a/14468362/1027250

基于前面的示例,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

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

这正是ViewModels的用途。您可以专门为视图创建它们。

你要求的是互斥的东西。。。您希望继承元数据,但不希望以任意方式继承元数据。。。而这通常不是一个有用的场景。与维护单独的模型相比,你将花费更多的时间来尝试找到一些神奇的场景,而且你很可能最终会得到一个你不是100%满意的低于标准的解决方案。

我知道你的沮丧。。你不应该在多个地方维护相同的数据。。你违反了DRY原则。。不要重复你自己。。。

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

简单地说,最佳实践是使用单独的数据属性维护单独的视图和域模型,因为您的域和视图是单独的关注点,它们应该保持独立。如果他们没有单独的顾虑,你可以简单地对两者使用相同的模型,就没有必要跳过这些障碍。

视图模型是分开的,正是因为您的视图通常与您的域有不同的要求。把两者混为一谈只会带来痛苦。

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

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")]

例如

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);

它将首先直接查看视图模型的属性属性,但如果没有找到任何属性,它将通过调用GetInheritedAttributeOfType来查看继承的属性。

这对我来说最有效,因为。。。

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

  2. 使用MetadataType也是不灵活的,要么全有要么全无,并且不能在单个ViewModel上包含多个MetadataType属性。

  3. 在没有Properties的视图模型中封装数据模型是缺乏的,因为它也不具有灵活性。您必须包括整个封装的对象,这样就不能在多个视图中填充DataModel。