Asp.net core 2 webapi post related data insert - asp.net-core

How can I tell EF Core to not insert related data on post ?
this are my models for Deposito and Sucursal
public class IDeposito
{
[Key]
public int Id { get; set; }
public string Descrip { get; set; }
public string Alias { get; set; }
public Boolean Activo { get; set; }
public ISucursal Sucursal { get; set; }
}
public class ISucursal
{
[Key]
public int Id { get; set; }
public string Descrip { get; set; }
public string Alias { get; set; }
public Boolean Activo { get; set; }
}
This is my controller
[HttpPost]
public IActionResult Create([FromBody] IDeposito postData)
{
if (postData == null)
{
return BadRequest();
}
_context.Depositos.Add(postData);
_context.SaveChanges();
return CreatedAtRoute("GetDeposito", new { ID = postData.Id }, postData);
}
When I post this model
{
"Descrip": "Neuquen",
"Alias": "NQN",
"Activo": true,
"Sucursal": {
"Id": 1,
"Descrip": "Comodoro Rivadavia",
"Alias": "CR",
"Activo": true
}
}
It fails because it tries to insert into table "Sucursal", although there already exists a "Sucursal" with id: 1.
Is there anyway I can tell EF Core to not update the related tables ? thank you very much

First approach if you want connect new IDeposito to existing ISucursal with Id = 1 then use:
postData.Sucursal = _context.Sucursals.Find(postData.Sucursal.Id);
_context.Depositos.Add(postData);
_context.SaveChanges();
return CreatedAtRoute("GetDeposito", new { ID = postData.Id }, postData);
If you want add only IDeposito without any sucursal:
postData.Sucursal = null;
_context.Depositos.Add(postData);
_context.SaveChanges();
return CreatedAtRoute("GetDeposito", new { ID = postData.Id }, postData);

Related

How to use .Include() in EF Core with ViewModel

I have two models
public class PageGroup
{
public PageGroup()
{
Pages = new HashSet<Page>();
}
public int GroupID { get; set; }
public string GroupTitle { get; set; }
public virtual ICollection<Page> Pages { get; set; }
}
public class Page
{
public Page()
{
}
public int PageID { get; set; }
public int GroupID { get; set; }
public string PageTitle { get; set; }
public string PageText { get; set; }
public virtual PageGroup PageGroup { get; set; }
}
and a ViewModel
public class ShowGroupsViewModel
{
public int GroupID { get; set; }
public string GroupTitle { get; set; }
public int PageCount { get; set; }
}
I filled this ViewModel with this method
public async Task<IEnumerable<ShowGroupsViewModel>> GetListGroupsServiceAsync()
{
return await _context.PageGroups.Include(p => p.Pages.Count).Select(pa => new ShowGroupsViewModel()
{
GroupID = pa.GroupID,
GroupTitle = pa.GroupTitle,
PageCount = pa.Pages.Count
}).ToListAsync();
}
but PageCount is not work. when run the project is has zero value. How can I fill this property?
I use .net core 3.1
Since you are using Ef 3.1 Include should not be used because Include("Pages") will bring all Page instances from the SQl server and count them after this.
In EF Net5 it would be done much more simple, using Include, but since you are using EF 3.1 try this:
public async Task<IEnumerable<ShowGroupsViewModel>> GetListGroupsServiceAsync()
{
return await ( from pg in _context.PageGroups
join p in context.Pages
on pg.GroupId equals p.GroupId
group pg by new { pg.GroupId, pg.GroupTitle} into g
select new ShowGroupsViewModel{
GroupId = g.Key.GroupId,
GroupTitle =g.Key.GroupTitle
PagesCount = g.Count()
}).ToListAsync();
}

Can i add a parent record and a child record using the same _context.SaveChangesAsync()

I have the following 2 Parent/Child objects:-
public Submission()
{
SubmissionQuestionSubmission = new HashSet<SubmissionQuestionSubmission>();
}
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Npi { get; set; }
public bool Independent { get; set; }
public string Comment { get; set; }
public virtual ICollection<SubmissionQuestionSubmission> SubmissionQuestionSubmission { get; set; }
}
public partial class SubmissionQuestionSubmission
{
public int SubmissionQuestionId { get; set; }
public int SubmissionId { get; set; }
public string Answer { get; set; }
public virtual Submission Submission { get; set; }
}
and i created the following view model:-
public class SubmissionCreate
{
public Submission Submission {set; get;}
public IList<SubmissionQuestion> SubmissionQuestion { set; get; }
public IList<SubmissionQuestionSubmission> SubmissionQuestionSubmission { set; get; }
}
where i have the following action method to add a parent record (submission) and a child record (SubmissionQuestionSubmission ), but to do so, i have to issue 2 save requests to the database, one to save the parent and get its ID, while the other to save the child record and assign it the parent ID, as follow:-
public async Task<IActionResult> Create(SubmissionCreate sc)//Bind("Id,FirstName,LastName,Npi,Independent,Comment")]
{
if (ModelState.IsValid)
{
var newsubmission = _context.Submission.Add(sc.Submission);
await _context.SaveChangesAsync();
foreach (var v in sc.SubmissionQuestionSubmission)
{
v.SubmissionId = sc.Submission.Id;
_context.SubmissionQuestionSubmission.Add(v);
}
await _context.SaveChangesAsync();
TempData["message"] = "Thank You.. Your request has been submitted...";
return View("Confirmation");
}
return View(sc);
}
so my question is if i can do the above job, using one save statement instead of 2?
You don't need use two SaveChanges. You can assign newsubmission into Submission property instead of v.SubmissionId = sc.Submission.Id;.
In this case Id and ForeignKey created automatically by EF Core
var newsubmission = _context.Submission.Add(sc.Submission);
foreach (var v in sc.SubmissionQuestionSubmission)
{
v.Submission = newsubmission;
_context.SubmissionQuestionSubmission.Add(v);
}
await _context.SaveChangesAsync();
Another way
sc.Submission.SubmissionQuestionSubmission = new List<SubmissionQuestionSubmission>();
foreach (var v in sc.SubmissionQuestionSubmission)
{
sc.Submission.SubmissionQuestionSubmission.Add(v)
}
_context.Submission.Add(sc.Submission);
await _context.SaveChangesAsync();

Duplicate Foreign Key entries being created on insert - EF Core and Web Api 3.1

I am currently learning how to work with EF Core with a simple one to many setup, where a user can have many items. In terms of retrieving the data from the tables, this is fine with some DTO models; however, when I try and add a user with multiple items via Postman, I noticed that for each item it had duplicated the user that many times (i.e. a user with 3 items will create 3 items and 3 users):
Postman (POST)
{
"username": "xxx",
"dob": "xxx",
"location": "xxx",
"items":[{
"item": "xxx",
"category": "xxx",
"type": "xxx"
},
{
"item": "xxx",
"category": "xxx",
"type": "xxx"
},
{
"item": "xxx",
"category": "xxx",
"type": "xxx"
}]
}
Context:
namespace TestWebApplication.Database
{
public class MyContext : DbContext
{
public DbSet<User> Users { get; set; }
public DbSet<Items> Items { get; set; }
public MyContext(DbContextOptions<MyContext> options)
: base(options)
{
// erm, nothing here
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>()
.HasMany(i => i.Items)
.WithOne(u => u.User);
}
public override int SaveChanges()
{
var entities = from e in ChangeTracker.Entries()
where e.State == EntityState.Added
|| e.State == EntityState.Modified
select e.Entity;
foreach (var entity in entities)
{
var validationContext = new ValidationContext(entity);
Validator.ValidateObject(entity, validationContext);
}
return base.SaveChanges();
}
}
}
Controller:
[HttpPost]
[Route("insertuseranditems")]
public ActionResult InsertUserAndItems(User user)
{
if (ModelState.IsValid)
{
using (MyContext myContext = _myContext as MyContext)
{
myContext?.Users?.Add(user);
int changes = myContext.SaveChanges();
if (changes > 0)
{
return Created("User saved", user);
}
}
}
return Accepted();
}
Items:
namespace TestWebApplication.Database
{
public class Items
{
[Key]
public int ItemId { get; set; }
public string Item { get; set; }
public string Category { get; set; }
public string Type { get; set; }
public virtual User User { get; set; }
}
}
Users:
namespace TestWebApplication.Database
{
public class User
{
[Key]
public int UserId { get; set; }
public string UserName { get; set; }
public string Dob { get; set; }
public string Location { get; set; }
public ICollection<Items> Items { get; set; }
}
}
I revisited my code and changed my Items.cs model to:
namespace TestWebApplication.Database
{
public class Items
{
[Key]
public int ItemId { get; set; }
public string Item { get; set; }
public string Category { get; set; }
public string Type { get; set; }
[ForeignKey("User")] // added this
public int? UserId { get; set; } // added this
public virtual User User { get; set; }
}
}
Now, when I send the above json via Postman, I get multiple items with the same user as a Foreign Key.
The site below seemed to help:
The ForeignKey Attribute

Asp.net Core does not serialize my new type

Am using .net core +angular 5, and trying to return a list, but one field is null in JSON response. Am using Postman to trigger debugging and saw in VS that the field has a value coming from the DB.
Don't know why it doesn't in the JSON response.
[HttpGet("[action]")]
public IEnumerable<HikingTrail> HikingTrails()
{
var dbOptions = new DbContextOptionsBuilder<HikingTrailContext>();
dbOptions.UseSqlServer("Server = (localdb)\\mssqllocaldb; Database = HikingApp");
var dbContext = new DAO.HikingTrailContext(dbOptions.Options);
return dbContext.HikingTrails.ToList();
}
This returns:
I'm interested in the "mountainRange" field not being null. In debug window it has the right value.
{
"url": null,
"hikingTrailId": 159,
"mountainRange": null,
"name": "My custom name",
"startPoint": null,
"endPoint": null,
"trailCheckpoints": null,
"type": 2,
"dificulty": null,
"duration": "2 1/2 - 3 h",
"minDuration": "00:00:00",
"maxDuration": "00:00:00",
"seasonality": "mediu",
"equipmentLevel": null,
"trailMarking": null,
"hasTrailType": false
},
I was thinking maybe it's EF Core, and have made this 2nd try (i.e. added Include() to dbContext query):
[HttpGet("[action]")]
public IEnumerable<HikingTrail> HikingTrails()
{
var dbOptions = new DbContextOptionsBuilder<HikingTrailContext>();
dbOptions.UseSqlServer("Server = (localdb)\\mssqllocaldb; Database = HikingApp");
var dbContext = new DAO.HikingTrailContext(dbOptions.Options);
return dbContext.HikingTrails.Include( x => x.MountainRange).ToList();
}
Could not get any response in Postman.
EDIT:
public class HikingTrailContext : DbContext
{
public HikingTrailContext(DbContextOptions<HikingTrailContext> options) : base(options)
{
}
public HikingTrailContext():base(){
}
public DbSet<HikingTrail> HikingTrails { get; set; }
public DbSet<MountainRange> MountainRanges { get; set; }
public DbSet<TrailScrapingSessionInfo> TrailScrapingHistory { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
}
}
public class HikingTrail
{
[Key]
public int HikingTrailId { get; set; }
public HikingTrail() { }
public MountainRange MountainRange { get; set; }
public String Name { get; set; }
public Location StartPoint { get; set; }
public Location EndPoint { get; set; }
public List<Location> TrailCheckpoints { get; }
public TrailType Type => TrailType.Undetermined;
public String Dificulty { get; set; }
public String Duration { get; set; }
public TimeSpan MinDuration { get; set; }
public TimeSpan MaxDuration { get; set; }
public String Seasonality { get; set; }
public String EquipmentLevel { get; set; }
public String TrailMarking { get; set; }
public String URL;
public bool HasTrailType
{
get
{
return this.Type != TrailType.Undetermined;
}
}
public override bool Equals(object obj)
{
return (((HikingTrail)obj).Name == this.Name);
}
public override int GetHashCode()
{
int hash = 17;
// Suitable nullity checks etc, of course :)
hash = hash * 23 + Name.GetHashCode();
hash = hash * 23 + Type.GetHashCode();
hash = hash * 23 + StartPoint.GetHashCode();
return hash;
}
public override string ToString()
{
return Name.ToString();
}
}
EDIT :
I profiled the db on dbContext.HikingTrails.Include(x => x.MountainRange).Where(x => x.MountainRange != null).ToList(); and the generated query is OK, meaning it has a Name column for MountainRange as well.
P.S.: several fields are null, but those ones don't have data yet.
found one solution, Projection to an anonymous type. Also had to be careful not to have two fields with the same name of "Name"
[HttpGet("[action]")]
public dynamic HikingTrails3()
{
var dbOptions = new DbContextOptionsBuilder<HikingTrailContext>();
dbOptions.UseSqlServer("Server = (localdb)\\mssqllocaldb; Database = HikingApp");
var dbContext = new DAO.HikingTrailContext(dbOptions.Options);
var trails = dbContext.HikingTrails.Include(x => x.MountainRange).
Select( i =>new { Name= i.Name, MountainRangeName = i.MountainRange.Name, i.Duration,
i.Dificulty,i.EquipmentLevel, i.Seasonality, i.Type }).ToList();
return trails;
}

ASP.NET MVC query lambda expression

Hello I have problem in one query. Why it's always return no value.
public List<UserDetail> userSearchModel(UserSearchModel searchModel)
{
string userid = User.Identity.GetUserId();
var user = _dbContext.UserDetails.Where(x => x.Id == userid);
var result = _dbContext.UserDetails.Except(user).ToList().AsQueryable();
if (searchModel != null)
{
if (searchModel.LanguageId.Count() != 0)
{
List<UserDetailLanguage> usrDetails = new List<UserDetailLanguage>();
foreach (var item in searchModel.LanguageId)
{
var details = _dbContext.UserDetailLanguages.Where(x => x.LanguageId == item).ToList();
foreach (var item2 in details)
{
usrDetails.Add(item2);
}
}
result = result.Where(x => x.UserDetailLanguages == usrDetails);
}
}
return result.ToList();
}
I want to get results which are the same in usrDetails list and in result.UserDetailLanguages.
In result.UserDetailLanguages I have record equals to record in usrDetails but this not want retrieve.
Here is my model:
public class UserDetail
{
public UserDetail()
{
this.UserDetailLanguages = new HashSet<UserDetailLanguage>();
}
[Key, ForeignKey("User")]
public string Id { get; set; }
public DateTime Birthday { get; set; }
public string Sex { get; set; }
public string Country { get; set; }
public string About { get; set; }
[NotMapped]
public int Age { get { return DateTime.Now.Year - Birthday.Year; } }
public virtual ApplicationUser User { get; set; }
public virtual ICollection<UserDetailLanguage> UserDetailLanguages { get; set; }
}
public class UserDetailLanguage
{
public Int32 Id { get; set; }
public virtual UserDetail UserDetail { get; set; }
public string UserDetailId { get; set; }
public virtual Language Language { get; set; }
public Int32 LanguageId { get; set; }
public Boolean IsKnown { get; set; }
public static implicit operator List<object>(UserDetailLanguage v)
{
throw new NotImplementedException();
}
}
public class Language
{
public Language()
{
this.UserDetailLanguages = new HashSet<UserDetailLanguage>();
}
public int Id { get; set; }
public string Value { get; set; }
public string Name { get; set; }
public virtual ICollection<UserDetailLanguage> UserDetailLanguages { get; set; }
}
What I'm doing wrong?
If you want to see if your value is in a list you use the Contains function of the list -- like this:
result = result.Where(x => usrDetails.Contains(x.UserDetailLanguage));
If you want to see if there are any items in both lists you can use intersection like this:
result = result.Where(x => usrDetails.Intersect(x.UserDetailLanguage).Count() > 0);
Looks like you are checking equality between lists in following code
result = result.Where(x => x.UserDetailLanguages == usrDetails);
This might not work, to check equality for lists you can use something like
Enumerable.SequenceEqual(FirstList.OrderBy(fList => fList),
SecondList.OrderBy(sList => sList))