EF Core 2 Not Saving Related Entities - c#

I have an implicit one to many relationship set up between Template and TemplateParameters. As far as I can tell, I am following conventions. However, when I save a Template, the children are not discovered and saved with it.
public class Template
{
#region Properties
public int TemplateId { get; set; }
public string UserId { get; set; }
public DateTime Created { get; set; }
public DateTime Published { get; set; }
public bool Deleted { get; set; }
public int Likes { get; set; }
public int Shares { get; set; }
public int Downloads { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public decimal Price { get; set; }
#endregion
#region Navigation
//navigation properties
public ApplicationUser User { get; set; }
public IEnumerable<Wave> Waves { get; set; }
public IEnumerable<TemplateParameter> TemplateParameters;
public IEnumerable<ApplicationUser> Subscribers;
#endregion
}
public class TemplateParameter
{
public int TemplateParameterId { get; set; }
public int TemplateId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public double? Value { get; set; }
public DateTime Created { get; set; }
//navigation properties
public virtual Template Template { get; set; }
}
[HttpPost]
[Route("SaveTemplate")]
public TemplateViewModel SaveTemplate([FromBody] TemplateViewModel vm)
{
//get the current userId
var user = _userManager.GetUserAsync(HttpContext.User).Result;
var token = User;
//map params
var parameters = vm.Parameters.Select(r => new TemplateParameter()
{
Name = r.Name,
Description = r.Description,
Created = DateTime.Now,
}).ToList();
//map the vm to the model
var template = new Template()
{
Name = vm.Name,
Description = vm.Description,
Created = DateTime.Now,
UserId = user.Id,
TemplateParameters = parameters,
};
//save the template
template = _templateService.SaveTemplate(template);
//map id back to the view model
vm.TemplateID = template.TemplateId;
return vm;
}
public Template SaveTemplate(Template template)
{
_context.Templates.Add(template);
_context.SaveChanges();
return template;
}
Can anyone spot what I'm doing wrong here? The template saves fine, but the child collections does not. I'm using the same pattern elsewhere in my project and it seems to work without issue. Thank you in advance for your help.

Related

I want to add 2 entities at once

I want to add 2 entities at once. When I want to add an offer, let the offer detail be added at the same time.
But let's add more than one detail to a quote.
I can't do this. I will be glad if you help.
public class Offer
{
public int Id { get; set; }
public int CompanyId { get; set; }
public int CompanyContactId { get; set; }
public int OfferNumber { get; set; }
public string Annotations { get; set; }
public string CommercialConditions { get; set; }
public string TimeInformation { get; set; }
public decimal ProfitRate { get; set; }
public DateTime Date { get; set; }
public DateTime ValidityDate { get; set; }
public Currency Currency { get; set; }
public Status Status { get; set; }
//Navigation Property
public virtual Company Company { get; set; }
public virtual CompanyContact CompanyContact { get; set; }
public virtual List<OfferDetail> OfferDetail { get; set; }
public class OfferDetail
{
//public int Id { get; set; }
public int OfferId { get; set; }
public int RowNumber { get; set; }
public int Quantity { get; set; }
public string Description { get; set; }
public decimal UnitPrice { get; set; }
public decimal TotalPrice { get; set; }
public string Definition { get; set; }
public Boolean Optional { get; set; }
public decimal UnitProfit { get; set; }
public decimal UnitCost { get; set; }
//public Currency Currency { get; set; }
//Navigation Properties
public Offer Offer { get; set; }
When I add the offer table to the database, the offer details are also added. But let another detail be added to a quote.
public async Task<Result> AddOffer(OfferInfo offerInfo)
{
try
{
var vOffer = new Offer
{
Id = offerInfo.Id,
CompanyId = offerInfo.CompanyId,
CompanyContactId = offerInfo.CompanyContactId,
OfferNumber = offerInfo.OfferNumber,
Annotations = offerInfo.Annotations,
CommercialConditions = offerInfo.CommercialConditions,
TimeInformation = offerInfo.TimeInformation,
ProfitRate = offerInfo.ProfitRate,
Date = offerInfo.Date,
ValidityDate = offerInfo.ValidityDate,
}; _context.Offers.Add(vOffer);
await _context.SaveChangesAsync();
return Result.PrepareSuccess();
}
catch (Exception vEx)
{
return Result.PrepareFailure(vEx.Message);
}
}
I can add from the model I created here.
This way I want to add both. Thanks in advance.
If you assign list of OfferDetail to Offer details entities will automatically be added to adequate table.
It should be something like this:
var vOffer = new Offer
{
// set all your properties of offer here
OfferDetails = new []
{
new OfferDetail {/*init details here*/},
// you can add more of details objects here
}
}
Chicken and egg problem, but there is a solution if you define constructors in OfferDetail that require the Offer and then add themselves to the List<OfferDetail>.
public class Offer
{
public Offer()
{
OfferDetail = new List<OfferDetail>();
}
//Navigation Property
public List<OfferDetail> OfferDetail { get; }
}
public class OfferDetail
{
public OfferDetail(Offer offer)
{
Offer = offer;
offer.OfferDetail.Add(this);
}
public int OfferId { get => Offer.Id; }
//Navigation Properties
public Offer Offer { get; }
}

How to get team entities details in other main entities in MVC 5?

I have two entities Team, MainEntry. Now I want to get all team details in dropdown while entring in MainEntry view
in MVC.
In other word in the MainEntry views I want get all team details in the dropdown.
Im using VS-2015, MVC5, EF6 with Code first migration.
public class Team
{
public int TeamId { get; set; }
public string TeamName { get; set; }
public string TeamAge { get; set; }
public string TeamLocation { get; set; }
}
public class MainEntry
{
public int MainEntryId { get; set; }
public string TeamAssignment { get; set; }
public string Role { get; set; }
public string Series { get; set; }
public string Stock { get; set; }
public string Points { get; set; }
public sting Teamname {get; set;} //(dropdown fill)
public string Teamage {get;set;} //(dropwon fill)
public string TeamLocation {get;set;} //(dropwon fill)
}
You can populate your dropdown list like
ViewBag.Teamnames = _db.Team.Select(c => new SelectListItem
{
Text = c.Teamname ,
Value = c.MainEntryId.ToString(),
Selected = true
});
and in your view:
#Html.DropDownList(
"TeamName",
(IEnumerable<SelectListItem>)ViewBag.Teamnames,
new { style = "max-width: 600px;" }
)
Another way
I usually create a view model that contains both the model if you want collection in model you can change your model as below and update your migration configuration file
public class MainEntry {
public int MainEntryId { get; set; }
public string TeamAssignment { get; set; }
public string Role { get; set; }
public string Series { get; set; }
public string Stock { get; set; }
public string Points { get; set; }
public IEnumerable<Team> Teamname { get; set; }//(dropwon fill)
public IEnumerable<Team> Teamage //(dropwon fill)
public IEnumerable<Team> TeamLocation //(dropwon fill) }

Entity to model mapping in LINQ

I have article and author classes.
Fetch articles like so and map entity to model:
public List<Model.Article> GetArticleList() {
using (var db = new ArticlesContext()) {
return db.Articles.Select(t => new Model.Article() {
Author = MapUserEntityToModel(db.Users.FirstOrDefault(u => u.UserID == t.UserID))
Title = t.Title,
Teaser = t.Teaser
// etc
}).ToList();
}
}
This doesn't work because LINQ can't run that function at run-time. What's the simplest and cleanest way to do the mapping?
Here are models:
namespace Model {
public class Article {
public string Title { get; set; }
public string Teaser { get; set; }
public User Author { get; set; }
public DateTime DateAdded { get; set; }
}
public class User {
public string DisplayName { get; set; }
public string Email { get; set; }
public string Website { get; set; }
public string PasswordHash { get; set; }
}
}
Here are entities:
namespace MyProj {
public class Article {
[Key]
public int ArticleID { get; set; }
public string Title { get; set; }
public string Teaser { get; set; }
public int UserID { get; set; }
public DateTime DateAdded { get; set; }
}
public class User {
[Key]
public int UserID { get; set; }
public string DisplayName { get; set; }
public string Email { get; set; }
public string Website { get; set; }
public string PasswordHash { get; set; }
}
public class ArticleContext : DbContext {
public ArticleContext() : base("name=conn") {
public DbSet<Article> Articles { get; set; }
public DbSet<User> Users { get; set; }
}
}
}
Before continue, map your relationship in a navigation property:
public class Article {
[Key]
public int ArticleID { get; set; }
public string Title { get; set; }
public string Teaser { get; set; }
[ForeignKey("UserID")]
public virtual User Author {get; set; } // navigation property
public int UserID { get; set; }
public DateTime DateAdded { get; set; }
}
And then just project your navigation property to his equivalent Model:
public List<Model.Article> GetArticleList() {
using (var db = new ArticlesContext()) {
return db.Articles.Select(t => new Model.Article() {
Author = new Model.User {
DisplayName = t.User.DisplayName,
Email = t.User.Email,
Website = t.User.Website,
PasswordHash = t.User.PasswordHash
},
Title = t.Title,
Teaser = t.Teaser
// etc
}).ToList();
}
}
You don't need to do anything, just return db.Articles directly:
using Model;
public List<Article> GetArticleList() {
using(var db = new ArticlesContext()) {
return db.Articles.ToList();
}
}
Assuming your EF model is set-up correctly with Foreign Keys, your Article type will have a lazily-evaluated Author property which will return a User object when accessed.

value cannot be null in Entity Sql Statement

My News.cs class has a one to many relationship with Comment.cs as defined below
public class News
{
public int NewsId { get; set; }
[Display(Name = "Title")]
public string Title { get; set; }
[Display(Name = "Details")]
public string Details { get; set; }
public DateTime DateCreated { get; set; }
public int AppUserId { get; set; }
[ForeignKey("AppUserId")]
public virtual AppUser AppUser { get; set; }
public ICollection<Comment> Comment { get; set; }
}
public class Comment
{
public int CommentId { get; set; }
public string CommentText { get; set; }
public DateTime DateCreated { get; set; }
public int AppUserId { get; set; }
public int? NewsId { get; set; }
[ForeignKey("AppUserId")]
public virtual AppUser AppUser { get; set; }
[ForeignKey("NewsId")]
public virtual News News { get; set; }
}
I have a controller action where i am trying to fetch one News item alongside all its comments so i set up two viewModels like this
public class CommentVM
{
public string CommentText { get; set; }
public DateTime DateCreated { get; set; }
public string Author { get; set; }
}
public class NewsCommentsVM
{
[Display(Name = "Title")]
public string Title { get; set; }
[Display(Name = "Details")]
public string Details { get; set; }
public DateTime DateCreated { get; set; }
public string Author { get; set; }
public List<CommentVM> Comments { get; set; }
}
In my Controller action i have
public ActionResult Details(int? id)
{
UOW _unit = new UOW();
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
News news = _unit.NewsRepository.GetByID(id);
if (news == null)
{
return HttpNotFound();
}
var model = new NewsCommentsVM()
{
Title = news.Title,
Details = news.Details,
DateCreated = news.DateCreated,
Author = news.AppUser.FirstName
Comments = news.Comment.Select(c => new CommentVM()
{
CommentText = c.CommentText,
Author = c.AppUser.Email,
DateCreated = c.DateCreated
}).ToList()
};
return View(result);
}
The problem is that the debugger is showing that Comment is returning Null whereas in the database there are related comments to that particular news item so i'm getting the error
Value cannot be null. Parameter: source
I've been able to use this code in another project without issues.
I think the problem is because you need to change the Comments collection property as virtual. If you want that related entities be lazy loaded, you need to follow this requirements:
public class News
{
//...
public virtual ICollection<Comment> Comment { get; set; }
}
Now,If you have disabled lazy loading, another option could be using the Include extension method in your query when you need to find a particular news:
int id=3;
var specificNews=context.News.Include(n=>n.Comment).FirstOrDefault(n=>n.Id==id);
This way the related entity will be included in the query result

Foreign key value not populating automatically

I've created a foreign key reference to the 'ApplicationUser' entity in my 'YogaSpace' entity seen below, but when I create a new YogaSpace and save it to the DB the column 'ApplicationUserRefId' is null? Shouldn't it contain the user id from application user without me inserting it into the entity before I save it? I know the answer would seem to be yes because I have another member in 'YogaSpace' called Images and it gets the 'YogaSpace' entity id automatically without me inserting it into the 'Images' entity before I save. So I don't have to insert the id there. How come it doesn't insert the user id with 'YogaSpace'?
public class YogaSpace
{
public int YogaSpaceId { get; set; }
public virtual ICollection<YogaSpaceImage> Images { get; set; }
[MaxLength(128)]
public string ApplicationUserRefId { get; set; }
[ForeignKey("ApplicationUserRefId")]
public virtual ApplicationUser ApplicationUser { get; set; }
}
public class ApplicationUser : IdentityUser
{
public DateTime MembershipCreated { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime? Birthdate { get; set; }
public Gender Gender { get; set; }
public virtual ICollection<YogaSpace> YogaSpaces { get; set; }
}
public class YogaSpaceImage
{
public int YogaSpaceImageId { get; set; }
public byte[] Image { get; set; }
public byte[] ImageThumbnail { get; set; }
public string ContentType { get; set; }
public int Ordering { get; set; }
[Index]
public int YogaSpaceRefId { get; set; }
[ForeignKey("YogaSpaceRefId")]
public virtual YogaSpace YogaSpace { get; set; }
}
Here is how I create a 'YogaSpace' entity. You can see I don't put anything into 'ApplicationUserRefId', BUT I expect it to autopopulate with user id from Application User, just like YogaSpacerefId gets auto populated when I create an image and save it.
var newSpace = new YogaSpace
{
Overview = new YogaSpaceOverview
{
Title = viewModel.Title,
Completed = ListingComplete.Incomplete
},
Listing = new YogaSpaceListing
{
Accommodation = (YogaSpaceAccommodation)viewModel.YogaSpaceAccommodation,
SpaceLocation = (YogaSpaceLocation)viewModel.YogaSpaceLocation,
SpaceType = (YogaSpaceType)viewModel.YogaSpaceType
},
Details = new YogaSpaceDetails
{
Completed = ListingComplete.Complete
},
Address = new YogaSpaceAddress
{
Completed = ListingComplete.Incomplete
},
DateCreated = DateTime.Now
};
yogaSpaceRepository.InsertOrUpdate(newSpace);
yogaSpaceRepository.Save();

Categories