1

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

以构成我的 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)this。我将使用此属性Question在视图级别保留输入值。

我不喜欢的是,我的 POCO 实体具有几乎不相关的属性——我只会SubmittedValue在 Web UI 的一种情况下使用,而我的 POCO 实体将在其他地方多次使用。

解决方案 2

创建与我的 POCO 具有相同结构的视图模型对象,我们称之为它们QuestionnaireModelQuestionGroupModel并且QuestionModel- 这些在我的控制器中初始化,并且属性从 POCO 复制到视图模型。在QuestionModel我添加我的SubmittedValue属性并使用自定义模型绑定器保留此值时,该绑定器查看绑定上下文并从视图中获取我的值 - 其中名称类似于 [group.question.1] (其中 1 是问题的 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排除在我的业务层之外。

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

4

2 回答 2

2

IMO 解决方案 2 是正确的前进方向,因为您经常会发现 EF POCO 和 ViewModel 在解决不同的问题时需要有所不同。

UIHints例如,一个可能的问题是使用表示层注释(等ValidationAttributes)装饰您的 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; }
    }
}
于 2012-09-07T10:59:06.433 回答
1

在使用了解决方案 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;
    }
}

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

于 2012-09-07T12:49:31.947 回答