روش View Model و ارتباط چند به چند در MVC 5 و Entity Framework 6

شنبه 15 اسفند 1394

در این مقاله قصد داریم علاوه بر توضیح ارتباط چند به چند در Entity Framework این امکان را با روش ViewModel در یک پروژه عملی پیاده سازی کنیم

روش View Model و ارتباط چند به چند در MVC 5  و Entity Framework 6

برای ایجاد رابطه چند به چند کافی است که در هر دو موجودیت که قرار است با هم رابطه چند به چند داشته باشند یک Navigation Property که یک مجموعه از نوع Icollection یا IEnumerable و یا List است از موجودیت دیگر در داخل  موجودیت جاری تعریف کرد .همان طور که می دانید این رابطه رابطه ای است که در آن یک نوع از موجودیت اول با چند نوع از موجودیت دوم در ارتباط است و به صورت بر عکس .

برای درک موضوع به صورت عملی به ادامه مقاله توجه فرمائید .

در داخل دیتابیس جداولی به شرح شکل زیر داریم .فیلدهای این جداول و همچنین ارتباط بین آنها در شکل مشخص شده است .

ارتباط بین دو جدول Emplotyer و jobPost به صورت یک به چند است .ارتباط بین دو جدول JobPost و JobTag به صورت چند به چند است ولی این ارتباط در داخل دیتابیس به دو ارتباط یک به چند شکسته خواهد شد .

در ادامه یک پروژه mvc از نوع Empty ایجاد می کنیم

بعد از افزودن entityFramework به داخل برنامه خود باید جداول خود را وارد برنامه نماییم .همان طور که میدانید به کمک Entity Framework که یک ORM است این کار به راحتی امکان پذیر است و کار تبدیل این جداول به اشیا و Entity ها به صورت خودکار توسط EF انجام می شود .

بعد از افزودن جداول به برنامه شکل مدل ما به صورت زیر خواهد بود

در داخل شکل ارتباط بین دو جدول JobTag و JobPost به صورت چند به چند نمایش داده شده است در حالی که این نمایش د رداخل دیتابیس به گونه دیگری است .در داخل دیتابیس ارتباط چند به چند به صورت دو ارتباط یک به چند نمایش داده خواهد شد .

ساختار این دو جدول در داخل برنامه به شکل یک کلاس است که کدهای آن را در زیر می بینید.

public partial class JobPost
{
    public JobPost()
    {
        this.JobTags = new HashSet<JobTag>();
    }

    public int Id { get; set; }
    public string Title { get; set; }
    public int EmployerID { get; set; }

    public virtual Employer Employer { get; set; }
    public virtual ICollection<JobTag> JobTags { get; set; }
}
public partial class JobTag
{
    public JobTag()
    {
        this.JobPosts = new HashSet<JobPost>();
    }

    public int Id { get; set; }
    public string Tag { get; set; }

    public virtual ICollection<JobPost> JobPosts { get; set; }
}

در ادامه برای انجام پروژه خود نیاز به ViewModel داریم .مدل به خودی خود قسمتی از برنامه است که آن بخش از دنیای واقعی که قصد مدل کردن آن را داریم نمایش می دهد .به مدل Domain Logic هم گفته می شود .این قسمت داده هایی را بین بخش UI و دیتابیس مدیریت می کند .اما ViewModel این امکان را به ما میدهد که از چندین موجودیت یک شی واحد بسازیم .بنابراین اگر قصد داشته باشید که چندین مدل را به view پاس دهید viewModel گزینه مناسبی است .

قصد داریم در اینجا از Scaffolding هم استفاده کنیم تا خود MVC زحمت ساخت صفحات view و اکشن های مربوطه را بکشد .در ضمن همان طورکه میدانید Model Binding امکان نگاشت درخواست HTTP را به مدل ما فراهم می کند .این کار توسط MVC  و در پشت صحنه انجام می شود و کار را برای شما بسیار ساده کرده است .وقتی ما ارتباط چند به چند داریم این امکان را برای کاربر فراهم می کنیم که امکان انتخاب چند نوع از موجودیت را در حالتی که ارتباط بین آنها Many To Many است داشته باشد .

بر روی کنترلر کلیک راست کرده و add=>controller را بزنید .بعد از آن در پنجره ای که باز می شود گزینه MVC5 Controller with views, using Entity Framework را بزنید .

نوع مدل را JobPost در نظر بگیرید

بعد از زدن گزینه Add  خود mvc یک کنترلر به همراه تمام اکشن های مربوط به مدل انتخابی ما را ایجاد خواهد کرد .حال قبل از اجرای برنامه تغییراتی را در Route ایجاد می کنیم تا صفحه پیش فرض ما تغییر کند

public class RouteConfig
{
    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
 
        routes.MapRoute(
            name: "Default",
            url: "{controller}/{action}/{id}",
            defaults: new { controller = "JobPost", action = "Index", id = UrlParameter.Optional }
        );
    }
}

اگر برنامه را اجرا کنیم درست است که View ها ساخته شده اند ولی اثری از رابطه چند به چندی که مد نظر ما بود وجود ندارد !!!

قصد داریم یک ViewModel ایجاد کنیم که دیتاهای مربوط به JobTag و  JobPost را همزمان داشته باشد .در ادامه صفحه مربوط به View ایندکس را ویرایش می کنیم .

// This row to the header table
 <th>
    @Html.DisplayNameFor(model => model.JobTags)
 </th>
// This row to the body table
 <td>
    @Html.ListBox("Id", new SelectList(item.JobTags,"Id","Tag"))
 </td>

حال اگر برنامه را اجرا کنید در یک لیست باکسی کلیه اطلاعات مربوط به تگ ها را خواهید دید.

مر حله بعد تغییر دادن صفحه ویرایش است .ابتدا یک ViewModel ایجاد می کنیم .

public class JobPostViewModel
{
    public JobPost JobPost { get; set; }
    public IEnumerable<SelectListItem> AllJobTags { get; set; }
 
    private List<int> _selectedJobTags;
    public List<int> SelectedJobTags
    {
        get
        {
           if (_selectedJobTags == null)
           {
              _selectedJobTags = JobPost.JobTags.Select(m => m.Id).ToList();
           }
           return _selectedJobTags;
        }
        set { _selectedJobTags = value; }
    }
}

حال در داخل Action ویرایش خود تغییرات زیر را اعمال می کنیم .

public ActionResult Edit(int? id)
{
     if (id == null)
     {
       return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
     }

     var jobPostViewModel = new JobPostViewModel            {
            JobPost = _db.JobPosts.Include(i => i.JobTags).First(i => i.Id == id),
         };

     if (jobPostViewModel.JobPost == null)
        return HttpNotFound();

     var allJobTagsList = _db.JobTags.ToList();       
     jobPostViewModel.AllJobTags = allJobTagsList.Select(o => new SelectListItem
     {
                Text = o.Tag,
                Value = o.Id.ToString()
     });

     ViewBag.EmployerID =
             new SelectList(db.Employers, "Id", "FullName", jobpostViewModel.JobPost.EmployerID);
  
     return View(jobpostViewModel);
} 

همچنین فرم Edit.cshtml خود را به صورت زیر تغییر می دهیم

@model ManyToManyMVC5.ViewModels.JobPostViewModel
@{
    ViewBag.Title = "Edit";
}

<h2>Edit</h2>

@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()
    
    <div class="form-horizontal">
        <h4>JobPost</h4>
        <hr />
        @Html.ValidationSummary(true)
        @Html.HiddenFor(model => model.JobPost.Id)

        <div class="form-group">
            @Html.LabelFor(model => model.JobPost.Title, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.JobPost.Title)
                @Html.ValidationMessageFor(model => model.JobPost.Title)
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.JobPost.EmployerID, "EmployerID", new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.DropDownListFor(m => m.JobPost.EmployerID,
                        (SelectList)ViewBag.EmployerID,
                        Model.JobPost.Employer.Id);
                @Html.ValidationMessageFor(model => model.JobPost.EmployerID)
            </div>
        </div>
        <div class="form-group">
            @Html.LabelFor(model=>model.AllJobTags,"JobTag",new {@class="control-label col-md-2"})
            <div class="col-md-10">
                @Html.ListBoxFor(m => m.SelectedJobTags, Model.AllJobTags)
            </div>
        </div>
        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Save" class="btn btn-default" />
            </div>
        </div>
    </div>
}

<div>
    @Html.ActionLink("Back to List", "Index")
</div>

<script src="~/Scripts/jquery-2.1.1.min.js"></script>
<script src="~/Scripts/jquery.validate.min.js"></script>
<script src="~/Scripts/jquery.validate.unobtrusive.min.js"></script>

حال اگر برنامه را اجرا کنید شکل زیر را خواهید دید

اتفاقی که در پشت صحنه رخ می دهد این است که وقتی چیزی را ویرایش می کنیم یک Trigger مانند زیر هم شروع به اجرا می کند .

برای Post هم شکل Action به صورت زیر خواهد بود

  [HttpPost]
		[ValidateAntiForgeryToken]//[Bind(Include = "Title,Id,EmployerID,SelectedJobTags")]
		public ActionResult Edit(JobPostViewModel jobpostView)
        {
	    
			if (jobpostView == null) return new HttpStatusCodeResult(HttpStatusCode.BadRequest);

			

            if (ModelState.IsValid)
            {
				var jobToUpdate = _db.JobPosts
					.Include(i => i.JobTags).First(i => i.Id == jobpostView.JobPost.Id);

	            if (TryUpdateModel(jobToUpdate,"JobPost",new string[]{"Title","EmployerID"} ))
	            {
		            var newJobTags = _db.JobTags.Where(
						m => jobpostView.SelectedJobTags.Contains(m.Id)).ToList();
					var updatedJobTags = new HashSet<int>(jobpostView.SelectedJobTags);
					foreach (JobTag jobTag in _db.JobTags)
					{
						if (!updatedJobTags.Contains(jobTag.Id))
						{
							jobToUpdate.JobTags.Remove(jobTag);
						}
						else
						{
							jobToUpdate.JobTags.Add((jobTag));
						}
					}

					_db.Entry(jobToUpdate).State = System.Data.Entity.EntityState.Modified;
					_db.SaveChanges();
	            }
			     
                return RedirectToAction("Index");
            }
            ViewBag.EmployerID = new SelectList(_db.Employers, "Id", "FullName", jobpostView.JobPost.EmployerID);
            return View(jobpostView);
        }

نتیجه ای که می توان گرفت این است که با وجود اینکه Scaffolding بسیار کمک کننده است ولی در دنیای واقعی باید برای اینکه نیاز ما را بر آورده کند کمی در آن دستکاری کنیم .

فایل های ضمیمه

برنامه نویسان

نویسنده 3355 مقاله در برنامه نویسان

کاربرانی که از نویسنده این مقاله تشکر کرده اند

در صورتی که در رابطه با این مقاله سوالی دارید، در تاپیک های انجمن مطرح کنید