How do I post a one-to-many relationship? - c#

Pretty new to ASP.NET and programming. I have two models, two API controllers, two repositories. How do I post the data to the second model while attaching it to the first (I'm guessing by ID.) Do I possibly need a View Model? Also reading a little about unit of work. Maybe neither are necessary? Below is some code. Thanks!
Record.cs
namespace Train.Models {
public class Record {
public int Id { get; set; }
public int Quantity { get; set; }
public DateTime DateCreated { get; set; }
public bool IsActive { get; set; }
public string UserId { get; set; }
public virtual ICollection<Cars> Cars { get; set; }
}
}
Cars.cs
namespace Train.Models {
public class Cars {
public int Id { get; set; }
public string EmptyOrLoaded { get; set; }
public string CarType { get; set; }
//Hopper, flatbed, tank, gondola, etc.
public string ShippedBy { get; set; }
//UP(Union Pacific) or BNSF
public string RailcarNumber { get; set; }
//public virtual ApplicationUser ApplicationUser { get; set; }
public string UserId { get; set; }
public string RecordId { get; set; }
public virtual Record Record { get; set; }
}
}
Record Repository
public void SaveRecord(Record recordToSave) {
if (recordToSave.Id == 0) {
recordToSave.DateCreated = DateTime.Now;
_db.Record.Add(recordToSave);
_db.SaveChanges();
} else {
var original = this._db.Record.Find(recordToSave.Id);
original.Quantity = recordToSave.Quantity;
original.IsActive = true;
_db.SaveChanges();
}
}
EFRepository (Cars)
public void SaveCar(Cars carToSave) {
if (carToSave.Id == 0) {
_db.Cars.Add(carToSave);
_db.SaveChanges();
} else {
var original = this.Find(carToSave.Id);
original.EmptyOrLoaded = carToSave.EmptyOrLoaded;
original.CarType = carToSave.CarType;
original.ShippedBy = carToSave.ShippedBy;
original.RailcarNumber = carToSave.RailcarNumber;
_db.SaveChanges();
}
}

Related

Entity Framework: Can't save data because of tracking

I have got a complex class. Feedback and Steps. I am using SQL database and .NET Core 2. I can save main properties but can't save the sub class FeedbackSteps properties
public class FeedbackModel
{
[Key]
public int FeedBackID { get; set; }
public DateTime FBDate { get; set; }
public bool? VideoStatus { get; set; }
public string VideoDetail { get; set; }
public string PITFeedBack { get; set; }
public int ActivityID { get; set; }
public virtual ActivityModel Activity { get; set; }
public int ClientID { get; set; }
public virtual ClientModel Client { get; set; }
public int? SupportPlanID { get; set; }
public virtual SupportPlanModel SupportPlan { get; set; }
public int EmployeeID { get; set; }
public virtual Employee Employee { get; set; }
public bool FeedbackStatus { get; set; } = true;
virtual public List<FeedbackStepModel> FeedbackSteps { get; set; }
}
public class FeedbackStepModel
{
[Key]
public int FeedbackStepID { get; set; }
public int FeedbackID { get; set; } = 0;
public int SupportPlanID { get; set; }
public int StepNumber { get; set; }
public string StepDetail { get; set; }
public string AchievementStatus { get; set; }
public string AchievementComment { get; set; }
}
This is the post method. View returns Edited or Updated feedback and i just want to update the database with new data
[HttpPost]
public IActionResult Edit(FeedbackModel feedback)
{
if (ModelState.IsValid)
{
feedbackRepository.Save(feedback);
TempData["message"] = $"Feedback has been saved";
return RedirectToAction("Index");
}
}
After EDIT, I would like to save it...
public void Save(FeedbackModel feedback)
{
if (feedback.FeedBackID == 0)
{
context.FeedbackModels.Add(feedback);
}
else
{
FeedbackModel dbEntry = context.FeedbackModels.Include(s => s.FeedbackSteps).FirstOrDefault(a => a.FeedBackID == feedback.FeedBackID);
if (dbEntry != null)
{
dbEntry.FeedBackID = feedback.FeedBackID;
dbEntry.FBDate = feedback.FBDate;
dbEntry.VideoStatus = feedback.VideoStatus;
dbEntry.VideoDetail = feedback.VideoDetail;
dbEntry.SupportPlanID = feedback.SupportPlanID;
dbEntry.ActivityID = feedback.ActivityID;
dbEntry.PITFeedBack = feedback.PITFeedBack;
dbEntry.ClientID = feedback.ClientID;
dbEntry.EmployeeID = feedback.EmployeeID;
dbEntry.FeedbackStatus = feedback.FeedbackStatus;
dbEntry.FeedbackSteps = feedback.FeedbackSteps;
}
}
context.SaveChanges();
}
But I get this error all the time
The instance of entity type 'FeedbackStepModel' cannot be tracked because another instance with the key value '{FeedbackStepID: 1}' is already being tracked.
When attaching existing entities, ensure that only one entity instance with a given key value is attached.
Your FeedbackModel update operation with children (FeedbackSteps) should be as follows:
FeedbackModel dbEntry = context.FeedbackModels.Include(s => s.FeedbackSteps).FirstOrDefault(a => a.FeedBackID == feedback.FeedBackID);
if (dbEntry != null)
{
dbEntry.FeedBackID = feedback.FeedBackID;
dbEntry.FBDate = feedback.FBDate;
dbEntry.VideoStatus = feedback.VideoStatus;
dbEntry.VideoDetail = feedback.VideoDetail;
dbEntry.SupportPlanID = feedback.SupportPlanID;
dbEntry.ActivityID = feedback.ActivityID;
dbEntry.PITFeedBack = feedback.PITFeedBack;
dbEntry.ClientID = feedback.ClientID;
dbEntry.EmployeeID = feedback.EmployeeID;
dbEntry.FeedbackStatus = feedback.FeedbackStatus;
dbEntry.FeedbackSteps.Clear(); // First you have to clear the existing feedBackSteps
foreach(FeedbackStep feedBackStep in feedback.FeedbackSteps)
{
dbEntry.FeedbackSteps.Add(feedBackStep); // You have to add new and updated feedBackStep here.
}
}
If dbEntry.FeedbackSteps.Clear(); does not work (may be in EF Core 2.0 or lower Clear() does not work) then replace dbEntry.FeedbackSteps.Clear(); with the following code:
foreach(FeedbackStep feedbackStepToBeRemoved in dbEntry.FeedbackSteps)
{
context.Remove(feedbackStepToBeRemoved);
}

Dotnet Core Web API returning empty list | Lazy loading do not work

I have an endpoint in my API which returns all the data of a selected item. The item is a root object called Survey and it has a list of pages.
public partial class Surveys
{
public Surveys()
{
Pages = new HashSet<Pages>();
}
public string Description { get; set; }
public string Name { get; set; }
public long Syear { get; set; }
public long Quarter { get; set; }
public ICollection<Pages> Pages { get; set; }
}
Model class for the Pages look like this.
public partial class Pages
{
public Pages()
{
Elements = new HashSet<Elements>();
}
public string Id { get; set; }
public long Number { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public int? PageFlowId { get; set; }
public bool NamedPage { get; set; }
public bool? IsFirst { get; set; }
public bool? IsLast { get; set; }
public long? SurveyQuarter { get; set; }
public long? SurveySyear { get; set; }
public PagesFlows PageFlow { get; set; }
public Surveys Survey { get; set; }
public ICollection<Elements> Elements { get; set; }
}
But when I send the GET request is returning an empty list for Pages
[
{
"description": "Customer Satisfaction Survey",
"name": "Customer Survey",
"syear": 2019,
"quarter": 1,
"pages": []
}
]
The database contains data. primary key of the table is a composite key(Syear, Quarter). My API looks like this.
public async Task<IActionResult> GetSurveys([FromRoute]long syear, long quarter)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var surveys = await _context.Surveys.Include(s => s.Pages).SingleOrDefaultAsync(s => s.Syear == syear && s.Quarter == quarter);
if (surveys == null)
{
return NotFound();
}
return Ok(surveys);
}
I have been trying to figure this out for a week now. Any help will be highly appreciated, Thank you in advance.
I managed to get around the issue by doing the following.
Removed .Include(s => s.Pages) from here,
var surveys = await _context.Surveys.Include(s => s.Pages).SingleOrDefaultAsync(s => s.Syear == syear && s.Quarter == quarter);
I have added virtual keyword to all the referenced classes.
public partial class Pages
{
public Pages()
{
Elements = new HashSet<Elements>();
}
public string Id { get; set; }
public long Number { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public int? PageFlowId { get; set; }
public bool NamedPage { get; set; }
public bool? IsFirst { get; set; }
public bool? IsLast { get; set; }
public long? SurveyQuarter { get; set; }
public long? SurveySyear { get; set; }
public virtual PagesFlows PageFlow { get; set; }
public virtual Surveys Survey { get; set; }
public virtual ICollection<Elements> Elements { get; set; }
}
Then I have installed Microsoft.EntityFrameworkCore.Proxies package fron NuGet as described here
And enabled Lazy Loading Proxies in Sturtup.cs in ConfigureServices method.
services.AddDbContext<MyDBContext>
(options => options.UseLazyLoadingProxies().UseSqlServer(connection));

C# ASP.Net - Error when trying to delete data from table

I have table Player and table Statistic and other tables that are not important in this question.
Table Player has PK Player_ID and it is FK in table Statistic. The relationship between these tables is one-to-many (one player can have more statistics).
Here is the code:
GenericRepository(I have created it to have unique class for CRUD methods)
public async Task<int> Delete<T>(Guid id) where T : class
{
try
{
T entity = await Get<T>(id);
Context.Set<T>().Remove(entity);
return await Context.SaveChangesAsync();
}
catch (Exception ex)
{
throw ex;
}
}
PlayerRepository(for managing operations on Player table)
public async Task<int> Delete(Guid id)
{
try
{
var player = await GenRepository.Get<Player>(id);
if (player == null)
{
return 404;
}
else
{
return await GenRepository.Delete(player);
}
}
catch (Exception ex)
{
throw ex;
}
}
PlayerService(connection between repository and controller in WebAPI)
public async Task<int> Delete(Guid id)
{
try
{
return await PlayerRepository.Delete(id);
}
catch (Exception ex)
{
throw ex;
}
}
PlayerController
[HttpDelete]
[Route("deleteplayer")]
public async Task<HttpResponseMessage> Delete(Guid id)
{
try
{
var Finder = Mapper.Map<PlayerView>(await PlayerService.Get(id));
if(Finder == null)
{
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Player doesn't exist in database.");
}
var Response = await PlayerService.Delete(id);
var profile = "../../../app/pictures/FootballFanAppPictures/" + Finder.Club_ID.ToString().ToUpper() + "/profiles/" + id.ToString().ToUpper() + ".png";
var details = "../../../app/pictures/FootballFanAppPictures/" + Finder.Club_ID.ToString().ToUpper() + "/" + id.ToString().ToUpper() + ".png";
if (System.IO.File.Exists(profile))
{
System.IO.File.Delete(profile);
}
if (System.IO.File.Exists(details))
{
System.IO.File.Delete(details);
}
return Request.CreateResponse(HttpStatusCode.OK, Response);
}
catch(Exception ex)
{
return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, ex);
}
}
Entity models:
-database models
public partial class Player
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Player()
{
this.Statistic = new HashSet<Statistic>();
}
public System.Guid Player_ID { get; set; }
public System.Guid Club_ID { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public double Height { get; set; }
public int Weight { get; set; }
public System.DateTime BirthDate { get; set; }
public string Nationality { get; set; }
public string Position { get; set; }
public int Shirtnmbr { get; set; }
public virtual Club Club { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<Statistic> Statistic { get; set; }
}
using System;
using System.Collections.Generic;
public partial class Statistic
{
public System.Guid Statistic_ID { get; set; }
public System.Guid Player_ID { get; set; }
public int Goals { get; set; }
public int Assists { get; set; }
public int FoulsFor { get; set; }
public int FoulsAgainst { get; set; }
public int ShotsTotal { get; set; }
public int ShotsGoal { get; set; }
public virtual Player Player { get; set; }
}
-domain models (used in repository)
public class PlayerDomain : IPlayerDomain
{
public Guid Player_ID { get; set; }
public Guid Club_ID { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public double Height { get; set; }
public int Weight { get; set; }
public DateTime BirthDate { get; set; }
public string Nationality { get; set; }
public string Position { get; set; }
public int Shirtnmbr { get; set; }
public virtual ICollection<IStatisticDomain> Statistic { get; set; }
}
public class StatisticDomain: IStatisticDomain
{
public Guid Statistic_ID { get; set; }
public Guid Player_ID { get; set; }
public int Goals { get; set; }
public int Assists { get; set; }
public int FoulsFor { get; set; }
public int FoulsAgainst { get; set; }
public int ShotsTotal { get; set; }
public int ShotsGoal { get; set; }
}
-view models (used in controller)
public class PlayerView
{
public Guid Player_ID { get; set; }
public Guid Club_ID { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public double Height { get; set; }
public int Weight { get; set; }
public DateTime BirthDate { get; set; }
public string Nationality { get; set; }
public string Position { get; set; }
public int Shirtnmbr { get; set; }
public virtual ICollection<StatisticView> Statistic { get; set; }
}
public class StatisticView
{
public Guid Statistic_ID { get; set; }
public Guid Player_ID { get; set; }
public int Goals { get; set; }
public int Assists { get; set; }
public int FoulsFor { get; set; }
public int FoulsAgainst { get; set; }
public int ShotsTotal { get; set; }
public int ShotsGoal { get; set; }
}
Every class is in a separate file. I use database first approach so i got .edmx file along with database models. Database is created in SQL Server Management Studio.
I can update Player but when I try to delete it i get this error:
The operation failed: The relationship could not be changed because one or more of the foreign-key properties is non-nullable. When a change is made to a relationship, the related foreign-key property is set to a null value. If the foreign-key does not support null values, a new relationship must be defined, the foreign-key property must be assigned another non-null value, or the unrelated object must be deleted.
I have searched various answers on google and stackoverflow but I couldn't find an answer that solves my problem
Before you call this line:
var Response = await PlayerService.Delete(id);
You are going to need to retrieve a list of your statistics that are assigned to the player you are trying to delete.
Then loop trough each of the statistics and delete those from your database first.
var stats = Finder.Statistic.ToList();
if(stats != null && stats.Any())
{
foreach(var stat in stats)
{
//retrieve your stat record here from the database
//check that the stat record is not null
//delete your stat record here
}
}
Then you should be able to delete your player record as there will no longer be references to it.
OR
You could just set ON DELETE CASCADE to true, but you need to be careful that you fully understand what all will be deleted on player deletion.
I don't see Statistic deletion on Player delete in your code.
Before you can delete a Player, you need to delete all Player related Statistics first. If there is any left, a player will fail on delete operation.
Set PrimaryKey and Foreignkey Relationship :according to #Bad Dub suggestion.

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.

How to add foreign key to entity

An error occurred while saving entities that do not expose foreign key properties for their relationships. The EntityEntries property will return null because a single entity cannot be identified as the source of the exception.
I'm using EF Code first and I'm getting error above when I trying do this:
public ActionResult CreateTransaction(TransactionViewModel model)
{
try
{
if (model.MemberId != 0 && model.SubcategoryId != 0 & model.CategoryId != 0)
{
Transaction t = new Transaction();
t.Amount = model.Amount;
t.Comment = model.Comment;
t.Date = DateTime.Now;
t.TransactionSubcategory = db.Subcategories.Find(model.SubcategoryId);//and i have error in this line
//i tried also this code below but it's the same error
//db.Subcategories.Find(model.SubcategoryId).Transactions.Add(t);
db.Members.Find(model.MemberId).Transactions.Add(t);
db.SaveChanges();
return RedirectToAction("Index");
}
else
{
return RedirectToAction("CreateTransaction") ;
}
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
return RedirectToAction("CreateTransaction");
}
}
And there is my model
public class Subcategory
{
public Subcategory()
{
IsGlobal = false;
Transactions = new List<Transaction>();
}
public int Id { get; set; }
public string Name { get; set; }
public TransactionType TypeOfTransaction { get; set; }
public Category OwnerCategory { get; set; }
public List<Transaction> Transactions { get; set; }
public bool IsGlobal { get; set; }
}
public class Transaction
{
public int Id { get; set; }
public Decimal Amount { get; set; }
public DateTime Date { get; set; }
public string Comment { get; set; }
public Subcategory TransactionSubcategory { get; set; }
public Member OwnerMember { get; set; }
}
I don't know why thats happen because in database in Transaction table i see there is column with FK.
If you need there is rest of the model
public class Budget
{
public Budget()
{
Members = new List<Member>();
}
public int Id { get; set; }
public string BudgetName { get; set; }
public string OwnerName { get; set; }
public DateTime CreatedTime { get; set; }
public List<Member> Members { get; set; }
}
public class Category
{
public Category()
{
IsGlobal = false;
}
public int Id { get; set; }
public string Name { get; set; }
public List<Subcategory> Subcategories { get; set; }
public Budget OwnerBudget { get; set; }
public bool IsGlobal { get; set; }
}
public class Member
{
public Member()
{
Transactions = new List<Transaction>();
}
public Budget OwnerBudget { get; set; }
public int Id { get; set; }
public string Name { get; set; }
public DateTime CreatedTime { get; set; }
public List<Transaction> Transactions { get; set; }
}
public enum TransactionType
{
Income,
Expenditure
}
Try adding
public int TransactionSubcategoryId { get; set; }
public int OwnerMemberId { get; set; }
In your Transaction class.

Categories