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 DataServicesを介してWebUIでこれらのエンティティにアクセスしており、これらのエンティティのビューで入力を処理するためのベストプラクティス(または少なくともよりクリーンな方法)を知りたいと思っています。以下は私がこれを克服するために持っているアイデアのいくつかですが、それらはただ複雑に感じているだけなので、私はそれらのどれも好きになるのに苦労しています。

解決策1

Questionと呼ばれるエンティティにプロパティを追加しSubmittedValue、EFデータコンテキストにIgnore(m => m.SubmittedValue)これを設定します。Questionこのプロパティは、ビューレベルでの入力値を永続化するために使用するものです。

これについて私が気に入らないのは、POCOエンティティをほとんど無関係なプロパティで肥大化させることです-私はSubmittedValueWeb UIで1つのケースでのみ使用しますが、POCOエンティティは他の場所で何度も使用されます。

解決策2

POCOと同じ構造を持つビューモデルオブジェクトを作成し、それらを呼び出しましょう。これらQuestionnaireModelはコントローラで初期化され、プロパティがPOCOからビューモデルにコピーされます。プロパティを追加し、バインディングコンテキストを調べてビューから値を取得するカスタムモデルバインダーを使用してこの値を永続化します。名前は[group.question.1]のようになります(1は質問のIDです)。 )。これは、各質問グループおよび各質問のエディターテンプレートを使用してビューに表示されます。QuestionGroupModelQuestionModelQuestionModelSubmittedValue

これについて私が気に入らないのは、これらの追加のビューモデルオブジェクトで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。その1回限りの使用プロパティSubmittedValueをビジネスレイヤーから遠ざけることができます。

これを処理するためのより良い方法を持っている人はいますか?

4

2 に答える 2

2

IMO ソリューション 2 は、EF POCO と ViewModel が異なる問題に対処するために分岐する必要があることがよくあるため、正しい方法です。

たとえば、ViewModel をプレゼンテーション層の注釈 (など)UIHintsで装飾することが懸念される可能性があります。ValidationAttributes

あなたが言うように解決策1は肥大化につながり、おそらくSystem.Data.Annotationsを参照することになります(おそらくOK)が、必要に応じてSystem.Data.MVCを参照することもでき[HiddenInput]ます

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 に答える