繁体   English   中英

实体框架POCO到MVC 3中的ViewModel

[英]Entity Framework POCO to ViewModel in MVC 3

我有一个示例项目,一个动态问卷系统,任何管理员都可以创建问卷,然后向其中添加问题组,然后向每个问题组添加问题。

采用构成我的EF数据上下文实体的以下POCO组:

public class Questionnaire
{
    public virtual int Id { get; set; }
    public virtual string QuestionnaireName { get; set; }
    public virtual IList<QuestionGroup> QuestionGroups { get; set; }
}

public class QuestionGroup
{
    public virtual int Id { get; set; }
    public virtual string GroupName { get; set; }
    public virtual int QuestionnaireId { get; set; }
    public virtual IList<Question> Questions { get; set; }
}

public class Question
{
    public virtual int Id { get; set; }
    public virtual string QuestionText { get; set; }
    public virtual int QuestionGroupId { get; set; }
    public virtual QuestionGroup QuestionGroup { get; set; }
}

我通过WCF数据服务在我的Web UI中访问这些实体,并且想知道在我的视图中为这些实体处理输入的最佳实践(或者至少是更简洁的方法)。 以下是我克服这个想法的一些想法,但我很难喜欢他们中的任何一个,因为他们只是觉得一个但是很复杂。

解决方案1

向我的Question实体添加一个名为SubmittedValue的属性,并将我的EF数据上下文Ignore(m => m.SubmittedValue) 我将使用此属性在视图级别保留Question的输入值。

我不喜欢这个,是我的POCO实体膨胀与几乎无关的属性 - 我只会在Web UI的一个案例中使用SubmittedValue ,而我的POCO实体将在其他地方多次使用。

解决方案2

创建与我的POCO具有相同结构的视图模型对象,让我们称之为QuestionnaireModelQuestionGroupModelQuestionModel - 这些在我的控制器中初始化,属性从POCO复制到视图模型。 QuestionModel我添加了我的SubmittedValue属性并使用自定义模型绑定器来持久保存此值,该绑定器查看绑定上下文并从视图中获取我的值 - 其中名称类似于[group.question.1](其中1是Id的ID问题)。 对于每个问题组和每个问题,使用编辑器模板在视图中显示。

我不喜欢这个,是使用这些额外的视图模型对象膨胀我的Web UI,并且必须手动将属性值从我的POCO复制到视图模型。 我知道我可以使用像AutoMapper这样的东西为我做这个,但这只是自动化这项工作,我理想的是根本不想这样做。

解决方案3

稍微更改解决方案2,而是扩展我的POCO并使用其他视图模型对象覆盖virtual集合属性。 所以,我的视图模型看起来像这样:

public class QuestionnaireModel : Questionnaire
{
    public new IList<QuestionGroupModel> QuestionGroups { get; set; }
}

public class QuestionGroupModel : QuestionGroup
{
    public new IList<Question> Questions { get; set; }
}

public class QuestionModel : Question
{
    public string SubmittedValue { get; set; }
}

我最喜欢这个想法,但我还没有尝试过。 我在这里得到了两个世界中最好的一个1.我可以保持我的POCO不受我的意见和2.我保留我的业务层的一次性使用属性SubmittedValue

你们中有谁有更好的方法来处理这个问题吗?

IMO解决方案2是正确的前进方向,因为您经常会发现EF POCO和ViewModel需要分散,因为它们可以解决不同的问题。

例如,可能关注的是使用表示层注释( UIHintsValidationAttributes等)来装饰ViewModel。

你说的解决方案1会导致膨胀,你可能最终会引用System.Data.Annotations(可能还行),但如果你需要[HiddenInput]等你也可以引用System.Data.MVC

IMO解决方案3比新的ViewModel更省力 - 例如,尽管MetadataType允许您将属性“转移”到具有类似属性的另一个类,但这是一项非常大的工作量。

例如,使用解决方案3,您可能会结束

namespace EFPocos
{
    /// <summary>
    ///  Your EF POCO
    /// </summary>
    public class Question
    {
        public virtual int Id { get; set; }
        public virtual string QuestionText { get; set; }
        public virtual int QuestionGroupId { get; set; }
    }
}

namespace UIViewModels
{
    /// <summary>
    ///  Your ViewModel 'derivative', but sans Annotation decoration
    /// </summary>
    [MetadataType(typeof(QuestionUIMetaData))]
    public class QuestionViewModel : EFPocos.Question, IValidatableObject
    {
        public string SubmittedValue { get; set; }

        #region IValidatableObject Members

        public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
        {
            if (Id % 2 == 0)
            {
                yield return new ValidationResult("Some rule has fired");
            }
        }

        #endregion
    }

    /// <summary>
    /// Annotations go here ... and we may as well just AutoMapped a simple ViewModel
    /// </summary>
    public class QuestionUIMetaData
    {
        [HiddenInput]
        public int Id { get; set; }
        [Required()]
        public string QuestionText { get; set; }
        [Required()]
        [DisplayName("Select Group ...")]
        public int QuestionGroupId { get; set; }
        [DisplayName("Question is Here")]
        [StringLength(50, ErrorMessage = "Too Long!!")]
        public string SubmittedValue { get; set; }
    }
}

玩过解决方案3(这是我的首选解决方案)后,我终于得到了它。 对于那些偶然发现这个问题的人来说,这就是我正在做的事情。 首先,我创建了扩展我的POCO实体的视图模型。 我使用new实现覆盖集合属性,以使我的集合成为视图模型类型。 然后我将表单持久性属性添加到我的Question视图模型中(因此我可以将其保留在业务层之外)。

public class QuestionnaireModel : Questionnaire
{
    public new IList<QuestionGroupModel> QuestionGroups { get; set; }
}

public class QuestionGroupModel : QuestionGroup
{
    public new IList<QuestionModel> Questions { get; set; }
}

public class QuestionModel : Question
{
    public string SubmittedValue { get; set; }
}

使用AutoMapper我在我的POCO和视图模型之间创建了这样的映射(使用.AfterMap()来确保我的持久性属性不是null而是空字符串):

Mapper.CreateMap<Questionnaire, QuestionnaireModel>();
Mapper.CreateMap<QuestionGroup, QuestionGroupModel>();
Mapper.CreateMap<Question, QuestionModel>().AfterMap((s, d) => d.SubmittedValue = "");

接下来,每个Question都有一个编辑器模板,它有一个输入元素:

@Html.Raw(string.Format("<input type=\"text\" name=\"group.question.{0}\" value=\"{1}\" />", Model.Id.ToString(), Model.SubmittedValue)

最后,我使用自定义模型绑定器拾取(并持久化)这些值,如下所示:

int id = Int32.Parse(controllerContext.RouteData.Values["id"].ToString());

var questionnaire = _proxy.Questionnaires
    .Expand("QuestionGroups")
    .Expand("QuestionGroups/Questions")
    .Where(q => q.Id == id)
    .FirstOrDefault();

var model = Mapper.Map<Questionnaire, QuestionnaireModel>(questionnaire);

foreach (var group in model.QuestionGroups)
{
    foreach (var question in group.Questions)
    {
        string inputValueId = "group.question." + question.Id.ToString();
        string value = bindingContext.ValueProvider.GetValue(inputValueId).AttemptedValue;

        question.SubmittedValue = value;
    }
}

虽然我对自定义模型绑定器不太满意(我不认为我正在设置我的编辑器模板,所以使用自定义绑定器)对我来说这是首选的解决方案。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM