How to implement Create with child collection in Application Layer? - asp.net-mvc

I have 2 entities named News and NewsAttachment in the .Core Project. These entities have a relationship through News_Id. In other words, when I create new News and add it into database, this News may have one or two attachment media that I want to insert in the related table named NewsAttachment. So I may want to retrieve News_Id and insert attachments in the related table.
I define 2 DTOs named NewsDto and CreateNewsDto in NewsAppService and I pass CreateNewsDto to insert new News, but I have no idea how to do it for NewsAttachment.
Here is my News entity:
public class News : FullAuditedEntity<long>
{
public const int MaxTitleLength = 150;
public const int MaxContentLength = 1200;
public const int MaxMetaTagLength = 60;
[Required]
[MaxLength(MaxTitleLength)]
public string Title { get; set; }
[Required]
public string Code { get; set; }
[Required]
[MaxLength(MaxContentLength)]
public string Content { get; set; }
public DateTime PublishDate { get; set; }
[MaxLength(MaxMetaTagLength)]
public string Tags { get; set; }
public virtual NewsType Type { get; set; }
public virtual ICollection<NewsAttachment> Attachments { get; set; }
}
and NewsAttachment entity:
public class NewsAttachment: FullAuditedEntity<long>
{
public const int MaxTitleLength = 50;
[Required]
[MaxLength(MaxTitleLength)]
public string FileName { get; set; }
[Required]
public byte[] File { get; set; }
public string FileExtension { get; set; }
public int FileSize { get; set; }
[Required]
public DateTime RegisterDate { get; set; }
public virtual News News { get; set; }
}
and the DTOs:
public class NewsDto : EntityDto<long>
{
public const int MaxTitleLength = 50;
public const int MaxContentLength = 800;
public const int MaxMetaTagLength = 60;
[Required]
[MaxLength(MaxTitleLength)]
public string Title { get; set; }
[Required]
public string Code { get; set; }
[Required]
[MaxLength(MaxContentLength)]
public string Content { get; set; }
public DateTime PublishDate { get; set; }
[MaxLength(MaxMetaTagLength)]
public string Tags { get; set; }
public virtual NewsType Type { get; set; }
}
and:
public class CreateNewsDto
{
public const int MaxTitleLength = 50;
public const int MaxContentLength = 800;
public const int MaxMetaTagLength = 60;
[Required]
[MaxLength(MaxTitleLength)]
public string Title { get; set; }
[Required]
public string Code { get; set; }
[Required]
[MaxLength(MaxContentLength)]
public string Content { get; set; }
public DateTime PublishDate { get; set; }
[MaxLength(MaxMetaTagLength)]
public string Tags { get; set; }
public virtual NewsType Type { get; set; }
public virtual ICollection<NewsAttachment> Attachments { get; set; }
}
Here is my presumptive method in NewsAppService to insert new News and add related media to NewsAttachment table:
public virtual NewsDto InsertWithMedia(CreateNewsDto input, NewsAttachmentDto attach)
{
var news = ObjectMapper.Map<NewsManagement.News>(input);
var newsAttachment = ObjectMapper.Map<NewsAttachment>(attach);
newsAttachment.News.Id = news.Id;
return MapToEntityDto(news);
}

Two choices:
Add to ICollection and let EF handle the entities:
public virtual NewsDto InsertWithMedia(CreateNewsDto input, NewsAttachmentDto attach)
{
var news = ObjectMapper.Map<NewsManagement.News>(input);
news.Attachments = new List<NewsAttachment>(); // 1
var newsAttachment = ObjectMapper.Map<NewsAttachment>(attach);
news.Attachments.Add(newsAttachment); // 2
_newsRepository.Insert(news); // 3
CurrentUnitOfWork.SaveChanges(); // 4
return MapToEntityDto(news);
}
Add by Id instead of collection, with a foreign key:
public class NewsAttachment: FullAuditedEntity<long>
{
// ...
public virtual long NewsId { get; set; }
public virtual News News { get; set; }
}
public virtual NewsDto InsertWithMedia(CreateNewsDto input, NewsAttachmentDto attach)
{
var news = ObjectMapper.Map<NewsManagement.News>(input);
var newsId = _newsRepository.InsertAndGetId(news); // 1
var newsAttachment = ObjectMapper.Map<NewsAttachment>(attach);
newsAttachment.NewsId = newsId; // 2
_newsAttachmentRepository.Insert(newsAttachment); // 3
CurrentUnitOfWork.SaveChanges(); // 4
return MapToEntityDto(news);
}
The second is good for updating — when newsId is already known — but may require additional steps if NewsDto also has Attachments (which should be ICollection<AttachmentDto> type).

thank you #aaron. after all, I have a little problem. in .WebMpa project how can I retrieve News with related picture.I define NewsViewModel in model folder and Map this ViewModel to NewsDto with Auto Mapper library.and in NewsController call NewsAppService and GetAll method for get news from database.I have 2 Scenario for this situation:
1- I think that change this method in .Application Project and add some linq query to get News with related Pictures from NewsAttachment
2- all operation about News and NewsAttachment and join with two entity stay in .WebMpa project and NewsController.
I confused really.would you help me with show example?

Related

ViewModel for multiple tables without links between tables

I'm trying to write a View Model in an ASP.NET MVC5 project to show data from different tables on a form that the user can then edit and save. I'm using the following :-
VS 2017, c#, MySQL Database, Entity Framework 6.1.3, MySQL Connector 6.9.9, Code First from Database (existing database that I can't change)
To complicate matters, there are no links between the tables in the database, so I cannot work out how to create a suitable View Model that will then allow me to save the changes.
Here are the 4 table models :-
public partial class evc_bearer
{
[Key]
[Column(Order = 0]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public long evcid { get; set; }
[Key]
[Column(Order = 1]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public long bearerid { get; set; }
public int vlan { get; set; }
[Column("ref")]
public string _ref { get; set; }
[Required]
public string port { get; set; }
[Required]
public string endpoint { get; set; }
}
public partial class bearer
{
[Key]
public long id { get; set; }
[Column("ref")]
[Required]
public string _ref { get; set; }
public string name { get; set; }
public long? site { get; set; }
public long? provider { get; set; }
public long? mtu { get; set; }
public float? rental { get; set; }
public int? offsiteprovider { get; set; }
public float? offsiteproviderrental { get; set; }
public bool? aend { get; set; }
public int? equipmentport { get; set; }
public string orderref { get; set; }
public string offsiteref { get; set; }
public string notes { get; set; }
public float? bookingfactor { get; set; }
}
public partial class evc
{
[Key]
public long id { get; set; }
[Required]
public string type { get; set; }
public byte servicetype { get; set; }
public byte cos { get; set; }
public int cir { get; set; }
public int pir { get; set; }
public bool burst { get; set; }
[Column("ref")]
public string _ref { get; set; }
public string orderref { get; set; }
public byte state { get; set; }
public string notes { get; set; }
public float? rental { get; set; }
}
public partial class evc_provider
{
[Key]
public long id { get; set; }
[Required]
public string provider { get; set; }
}
This is the View Model I tried writing :-
public partial class evcBearersVM
{
[Key]
[Column(Order = 0)]
public long evcid { get; set; }
[Key]
[Column(Order = 1)]
public long id { get; set; }
[Column("ref")]
public string b_ref { get; set; }
public string b_name { get; set; }
public string ep_provider { get; set; }
public int eb_vlan { get; set; }
public string eb_port { get; set; }
public string eb_endpoint { get; set; }
}
This is the Linq query I used to populate the View Model :-
IQueryable<evcBearersVM> data = from eb in csdb.evc_bearers
join b in csdb.bearers on eb.bearerid equals b.id
join ep in csdb.evc_providers on b.provider equals ep.id
join e in csdb.evcs on eb.evcid equals e.id
where (eb.evcid == evcid && b.id == id)
select new evcBearersVM
{
evcid = eb.evcid,
id = b.id,
b_ref = b._ref,
b_name = b.name,
ep_provider = ep.provider,
eb_vlan = eb.vlan,
eb_port = eb.port,
eb_endpoint = eb._ref
};
So the query works and joins the tables to get the data I need and I can display this date in various views as needed. What I now need to do is be able to edit a row and save it back to the database. I have an Edit View that is showing the data I need but I'm not sure how to save changes given that it's a View Model and the DB Context isn't aware of it. Grateful for any help.
What you should do is, use the same view model as your HttpPost action method parameter and inside the action method, read the entities you want to udpate using the Id's (you can get this from the view model, assuming your form is submitting those) and update only those properties you need to update.
[HttpPost]
public ActionResult Edit(evcBearersVM model)
{
var b = csdb.bearers.FirstOrDefault(a=>a.id==model.id);
if(b!=null)
{
b.name = model.b_name;
b._ref = model.b_ref;
csdb.SaveChanges();
}
// Update the other entity as well.
return RedirectToAction("Index");
}

Join two Models to display in webpage

I'm building an application using ASP.NET MVC4 with code first data migrations. I have an estimates model, a clients model, a DbContext, and a view model I created. I am wanting to display the company name in a drop down, with the company name tied to an estimate. I have a ClientId in both models. I also created a DbSet<> and that didn't work either when querying against it.
I tried to create a viewmodel that I thought I could simply query against and display through my controller. I'm not having any luck in getting this to work. After a day plus of looking on here and other places, I'm out of ideas.
How can I query/join the two models, or query the viewmodel to get the company name associated with the clientId? Thanks for the help.
Models:
public class Estimates
{
[Key]
public int EstimateId { get; set; }
public int ClientId { get; set; }
public decimal EstimateAmount { get; set; }
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:MM/dd/yyyy}")]
public DateTime EstimateDate { get; set; }
public string EstimateNotes { get; set; }
}
public class Clients
{
[Key]
public int ClientId { get; set; }
public string CompanyName { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Phone { get; set; }
public string Email { get; set; }
public string Address { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Zip { get; set; }
public ICollection<Estimates> Estimates { get; set; }
public ICollection<Contracts> Contracts { get; set; }
}
public class ClientEstimateViewModel
{
public Clients Clients { get; set; }
public Estimates Estimates { get; set; }
}
public class NovaDb : DbContext
{
public NovaDb(): base("DefaultConnection")
{
}
public DbSet<Clients> Clients { get; set; }
public DbSet<Estimates> Estimates { get; set; }
public DbSet<Contracts> Contracts { get; set; }
public DbSet<Invoices> Invoices { get; set; }
public DbSet<ClientEstimateViewModel> ClientViewModels { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
}
Controller:
NovaDb _db = new NovaDb();
ClientEstimateViewModel ce = new ClientEstimateViewModel();
public ActionResult Index()
{
var model =
(from r in ce.Clients
join x in ce.Estimates
where
//var model =
// from r in _db.Clients
// orderby r.CompanyName ascending
// select r;
return View(model);
}
Because you've created the relationship between client & estimate in your models, you should be able to create a query like this:
var query = from c in _db.clients
select new ClientEstimateViewModel
{
Clients = c,
Estimates = c.Estimates
}
Although you'd have to change your model so Estimates was public List<Estimates> Estimates { get; set; }
This would give you a collection of ClientEstimateViewModel which you could then pass to your view

Getting Error: "A specified Include path is not valid. The EntityType Field' does not declare a navigation property"

Created method:
public List<Field> GetScheduleDetails()
{
var schedulefields = DBcontextFactory.Context.Set<Field>).Include("ScheduleField").ToList();
}
With the above method i am trying to fetch all joined(field.fieldid=schedulefield.fieldid) records from both tables. The field table is related with schedulefield table. Sorry if i am not familiar with technical terms.
Field Model:
public partial class Field : DOIEntity
{
public Field()
{
this.FilerResponses = new HashSet<FilerResponse>();
this.ScheduleFields = new HashSet<ScheduleField>();
}
public int FieldId { get; set; }
public string FieldDisplayName { get; set; }
public int FieldTypeId { get; set; }
public string HelpText { get; set; }
public Nullable<bool> OtherTextAllowed { get; set; }
public Nullable<int> ChoiceGroupId { get; set; }
public virtual FieldType FieldType { get; set; }
public virtual ICollection<FilerResponse> FilerResponses { get; set; }
public virtual ICollection<ScheduleField> ScheduleFields { get; set; }
}
ScheduleField Model:
public partial class ScheduleField
{
[Key]
public int ScheduleId { get; set; }
public int FieldId { get; set; }
public byte SortOrder { get; set; }
public Nullable<bool> IsMandatory { get; set; }
public Nullable<int> ParentFieldId { get; set; }
public Nullable<int> ParentChoiceId { get; set; }
public virtual Field Field { get; set; }
public virtual Schedule Schedule { get; set; }
}
When I call the method I am getting this error:
A specified Include path is not valid. The EntityType
'WorldBank.DOI.Data.Field' does not declare a navigation property with
the name 'ScheduleField'.
Why am I getting this error?
You have to use the property name of the Field class in the Include string:
public List<Field> GetScheduleDetails()
{
var schedulefields = DBcontextFactory.Context.Set<Field>).Include("ScheduleFields").ToList();
}
This will eager load the ScheduleField objects associated with the Field objects.
Note, you can also eager load many levels. For example, if you want to eager load the schedules of the ScheduleField object as well you would do this:
public List<Field> GetScheduleDetails()
{
var schedulefields = DBcontextFactory.Context.Set<Field>).Include("ScheduleFields.Schedule").ToList();
}

PagedList in MVC3 with IQueryable

I can't understand what i'm doing wrong. Every time I'm getting this error:
The entity or complex type 'BusinessLogic.CompanyWithDivisionCount' cannot be constructed in a LINQ to Entities query.
I need to get info from 'Company' table and divisions count of each company from 'Division' table, and then make PagedList. Here is my 'Company' table:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.DataAnnotations;
using BusinessLogic.Services;
using BusinessLogic.Models.ValidationAttributes;
namespace BusinessLogic.Models
{
public class Company
{
public Company()
{
Country = "US";
Status = true;
}
public int Id { get; set; }
[Required]
[UniqueCompanyName]
public string Name { get; set; }
public string Street { get; set; }
public string City { get; set; }
public string State { get; set; }
public int Zip { get; set; }
public string Country { get; set; }
public string ContactInfo { get; set; }
[Required]
public DateTime EffectiveDate { get; set; }
public DateTime TerminationDate { get; set; }
public bool Status { get; set; }
[Required]
public string URL { get; set; }
public string EAP { get; set; }
public string EAPCredentials { get; set; }
public string BrandingColors { get; set; }
public string Comments { get; set; }
}
}
Here is my domain model:
public class Company
{
public Company()
{
Country = "US";
Status = true;
}
public int Id { get; set; }
[Required]
[UniqueCompanyName]
public string Name { get; set; }
public string Street { get; set; }
public string City { get; set; }
public string State { get; set; }
public int Zip { get; set; }
public string Country { get; set; }
public string ContactInfo { get; set; }
[Required]
public DateTime EffectiveDate { get; set; }
public DateTime TerminationDate { get; set; }
public bool Status { get; set; }
[Required]
public string URL { get; set; }
public string EAP { get; set; }
public string EAPCredentials { get; set; }
public string BrandingColors { get; set; }
public string Comments { get; set; }
}
public class CompanyWithDivisionCount: Company // I'm using this
{
public int DivisionCount { get; set; }
}
Here is my controller:
public ActionResult CompaniesList(int? page)
{
var pageNumber = page ?? 1;
var companies = companyService.GetCompaniesWithDivisionsCount2();
var model = companies.ToPagedList(pageNumber, PageSize);
return View(model);
}
And here is my service part:
public IQueryable<CompanyWithDivisionCount> GetCompaniesWithDivisionsCount2()
{
return (from c in dataContext.Companies.AsQueryable()
select new CompanyWithDivisionCount
{
Id = c.Id,
Name = c.Name,
Status = c.Status,
EffectiveDate = c.EffectiveDate,
URL = c.URL,
EAP = c.EAP,
EAPCredentials = c.EAPCredentials,
Comments = c.Comments,
DivisionCount = (int)dataContext.Divisions.Where(b => b.CompanyName == c.Name).Count()
});
}
}
Thanks for help!!!
Creator of PagedList here. This has nothing to do with PagedList, but rather is an Entity Framework issue (I'm no expert on Entity Framework, so can't help you there). To confirm that this is true, write a unit test along the following lines:
[Test]
public void ShouldNotThrowAnException()
{
//arrange
var companies = companyService.GetCompaniesWithDivisionsCount2();
//act
var result = companies.ToList();
//assert
//if this line is reached, we win! no exception on call to .ToList()
}
I would consider changing you data model if possible so that instead of relating Companies to Divisions by name strings, instead use a properly maintained foreign key relationship between the two objects (Divisions should contain a CompanyID foreign key). This has a number of benefits (including performance and data integrity) and will almost certainly make your life easier moving forward if you need to make further changes to you app (or if any company ever decides that it may re-brand it's name).
If you create a proper foreign key relationship then your domain model could look like
public class Company
{
...
public virtual ICollection<Division> Divisions{ get; set; }
public int DivisionCount
{
get
{
return this.Divisions.Count()
}
}
...
}

Need to map from two objects into a single one

I have the following entity model:
public class Project
{
[Key]
public int ProjectID { get; set; }
public string Title { get; set; }
public string Slug { get; set; }
public string Content { get; set; }
public string Category { get; set; }
public string Client { get; set; }
public int Year { get; set; }
// more attributes here...
}
I would like to prepare a view model (specific for my view). Here is the view model:
public class ProjectListViewModel
{
public IEnumerable<ProjectInfos> ProjectList { get; set; }
public PagingInfo Paging { get; set; }
public class ProjectInfos
{
public string Title { get; set; }
public string Slug { get; set; }
public string Content { get; set; }
public string Category { get; set; }
public string Client { get; set; }
public int Year { get; set; }
}
public class PagingInfo
{
public int TotalItems { get; set; }
public int ItemsPerPage { get; set; }
public int CurrentPage { get; set; }
public int TotalPages { get; set; }
}
}
In my controller, I would like to prepare the view model by filling it with 2 different objects:
List of projects
Paging information
Here is my controller:
public ViewResult List(string category, int page = 1)
{
IEnumerable<Project> projectList = m_Business.GetProjects(category, page, 10);
PagingInfo pagingInfo = m_Business.GetPagingInfo(category, page, 10);
// Here I need to map !!
ProjectListViewModel viewModel = .....
return View(viewModel);
}
So how can I proceed in my controller? I know we can use automapper to map from one object to another but here I need to map from two objects into a single one.
Thanks.
You can extend AutoMapper to map multiple objects.
Here is a blog which provides some sample cope.
Then you can use code like this:
var personViewModel = EntityMapper.Map<PersonViewModel>(person, address, comment);

Resources