How to Read Related Data Using LINQ/C# [one to many] - c#

I have two related tables with one to many relationship, I m trying to get a JSON response that populates ICollection<PatPar> PatPar the controller's code below reads fine as long as I have only one related record, once I have more than one related record I get a selection exception.
These are related Models
public class PatReg
{
[NotMapped]
private Int64 _FileId;
[Key, Display(Name = "File Id"), ScaffoldColumn(false), DatabaseGenerated(DatabaseGeneratedOption.None)]
public Int64 FileId
{
get
{
return this._FileId;
}
set
{
this._FileId = value;
}
}
[Required, Display(Name = "First Name")]
public string FName { get; set; }
public ICollection<PatPar> PatPar { get; set; }
}
public class PatPar
{
[Key]
public Int64 RecId { get; set; }
[Display(Name = "Patient File Id"), Required]
public Int64 FileId { set; get; }
[Display(Name = "Partner File Id"), Required]
public Int64 ParFileId { set; get; }
[Display(Name = "Start Date"), DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true), Required]
public DateTime SDate { set; get; }
[Display(Name = "End Date"), DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime? EDate { set; get; }
}
This is my API Controller
[HttpGet]
public IEnumerable<PatReg> GetPatReg()
{
return _context.PatReg;
}
[HttpGet("{id}")]
public async Task<IActionResult> GetPatReg([FromRoute] long id)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var patReg = await _context.PatReg.SingleOrDefaultAsync(m => m.FileId == id);
var parinfo = await _context.PatPar.SingleOrDefaultAsync(m => m.FileId == id); // I should select many records here
if (patReg == null)
{
return NotFound();
}
var DataRes = new {
sData = patReg
};
return Ok(DataRes);
}
I know that I should use selectMany instead of using SingleOrDefaultAsync so I tried
IEnumerable<PatPar> parinfo = await _context.PatPar.SelectMany(m => m.FileId == id);
but the compiler is giving me errors, what is the way to do it?

You should be using Where instead of SelectMany:
var result = _context.PatPar.Where(m => m.FileId == id);
SelectMany is used to retrieve a collection from each item of the iterated collection. However your condition is to check if the FileId equals some id - the resulted type is a boolean. I assume what you are trying to do is return all the times that have that FileId.
To have it with the await:
var result = await _context.PatPar.Where(m => m.FileId == id).ToListAsync();

Maybe you want actually this:
IEnumerable<PatPar> parinfo = _context.PatPar.Where(m => m.FileId == id);
SelectMany description:
Projects each element of a sequence to an IEnumerable and flattens
the resulting sequences into one sequence.
So it is needed if you want to get IEnumerable<SomeSpecificField> as the result.
However, it is applicable only if PatPar had IEnumerable<SomeType> and you want to SelectMany<PatPar, SomeType>.
In your case there can be only Select
IEnumerable<Int64> parFileIds = _context.PatPar.Select(m => m.ParFileId );

Related

UPDATE issue with .Include() EF Coe

I've been following ms docs tutorial on handling concurrency conflicts in EF core. I have two models:
Movie.cs
public class Movie
{
[Key]
public int ID { get; set; }
[Required]
[StringLength(60, MinimumLength = 3)]
public string Title { get; set; }
[DataType(DataType.Date)]
[Display(Name = "Release Date")]
[DisplayFormat(DataFormatString = "{0:dd-MM-yyyy}", ApplyFormatInEditMode = true)]
public DateTime ReleaseDate { get; set; }
[Range(1, 300)]
[DataType(DataType.Currency)]
[Column(TypeName = "decimal(18,2)")]
public decimal Price { get; set; }
[Required]
[StringLength(5)]
[RegularExpression(#"^[A-Z]+[a-zA-Z0-9""'\s-]*$")]
public string Rating { get; set; }
public string Description { get; set; }
[Timestamp]
public byte[] ConcurrencyToken { get; set; }
public int GenreId { get; set; }
public Genre Genre { get; set; }
public override string ToString()
{
return Title;
}
}
Genre.cs
public class Genre
{
public Genre()
{
Movies = new List<Movie>();
}
[Key]
public int GenreId { get; set; }
[Required]
[StringLength(30, MinimumLength = 5)]
[Display(Name = "Genre Title")]
[RegularExpression(#"^[A-Z]+[a-zA-Z]*$")]
public string GenreTitle { get; set; }
[Timestamp]
public byte[] ConcurrencyToken { get; set; }
[Display(Name = "Number of movies")]
public ICollection<Movie> Movies { get; set; }
public override string ToString()
{
return GenreTitle;
}
}
When I tried to update my Models I faced some weird issues and could not figure why, there were no errors thrown, models were just simply not getting updated. Here is my post method for Update:
public async Task<IActionResult> OnPostAsync(int? id)
{
if (!ModelState.IsValid)
{
return Page();
}
// ConcurrencyToken may have changed.
var movieToUpdate = await _context.Movie
.Include(m => m.Genre)
.FirstOrDefaultAsync(m => m.ID == id);
if(movieToUpdate == null)
{
return HandleDeletedMovie();
}
// Set ConcurrencyToken to value read in OnGetAsync
_context.Entry(movieToUpdate).Property(
m => m.ConcurrencyToken).OriginalValue = Movie.ConcurrencyToken;
if (await TryUpdateModelAsync<Movie>(
movieToUpdate,
"movie",
m => m.Title, m => m.ReleaseDate,
m => m.Price, m => m.Rating,
m => m.GenreId))
{
try
{
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
catch(DbUpdateConcurrencyException ex)
{
var exceptionEntry = ex.Entries.Single();
var clientValues = (Movie)exceptionEntry.Entity;
var databaseEntry = exceptionEntry.GetDatabaseValues();
if(databaseEntry == null)
{
ModelState.AddModelError(string.Empty, "Unable to save. The movie was deleted by another user.");
return Page();
}
var dbValues = (Movie)databaseEntry.ToObject();
await SetDbErrorMessage(dbValues, clientValues, _context);
// Save the current ConcurrencyToken so next postback
// matches unless an new concurrency issue happens.
Movie.ConcurrencyToken = (byte[])dbValues.ConcurrencyToken;
// Clear model error for the next postback
ModelState.Remove($"{nameof(Movie)}.{nameof(Movie.ConcurrencyToken)}");
}
}
// Get errors from TryUpdate
var validationErrors = ModelState.Values.Where(E => E.Errors.Count > 0)
.SelectMany(E => E.Errors)
.Select(E => E.ErrorMessage)
.ToList();
PopelateGenresDropDownList(_context, movieToUpdate.Genre);
return Page();
}
The update was failing on the line TryUpdateModelAsync, and it did not go to try-catch. I figured out the error that was causing the issue was validation for the Genre.GenreTitle property which is Included in movie. Even though ModelState was not failing it still caused an error for updating the entity. However, when I checked the Movie.Genre property was totally fine and there should not have been any errors. Only when I removed .Include from var movieToUpdate = await _context.Movie.FirstOrDefaultAsync(id) it worked out.
Now, I'm wondering what was my mistake because in the documentation the nested object was actually .Included.
I tested your regular expression ^[A-Z]+[a-zA-Z]*$ and it does not seems to accept space caracter. So Genre Title is not a match to the expression and trigger an error.
You can add space in the [] to tell the expression that you also need to accept space character. I don't know if it is how it's supposed to be done but it works in RegexStorm or Regex101 (No c# for this last one but Regex are kinda universal I think).

DateTime in the transfer parameter of the edit method in the controller is always 01.01.0001 00:00:00 asp.net mvc5

I a problem in the transfer parameter in the Edit Method "issue".
In Issue the CreatedDate and UpdatedDate is always {01.01.0001 00:00:00}.
The parameter Id, Title and Description is always correct.
My Controller:
public ActionResult Edit([Bind(Include = "Id,Title,Description")] Issue issue)
{
if (ModelState.IsValid)
{
db.Entry(issue).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(issue);
}
My Model:
public class Issue : BaseEntity
{
public int Id { get; set; }
[Required(ErrorMessage = "Required")]
public string Title { get; set; }
[AllowHtml]
[Required(ErrorMessage = "Required")]
[DataType(DataType.MultilineText)]
public string Description { get; set; }
}
public class BaseEntity
{
public DateTime CreatedDate { get; set; }
public DateTime UpdatedDate { get; set; }
}
I can unfortunately can not debug from where the "issue" parameter comes from.
From where the transfer parameter "Issue issue" in the Edit method comes from and why are all DateTimes always {01.01.0001 00:00:00}?
When I create the first time a issue entity I add the DateTimme in the SaveChanges() Method with the following modification in my DBContext:
public override int SaveChanges()
{
var entries = ChangeTracker
.Entries()
.Where(e => e.Entity is BaseEntity && (
e.State == EntityState.Added
|| e.State == EntityState.Modified));
foreach (var entityEntry in entries)
{
((BaseEntity)entityEntry.Entity).UpdatedDate = DateTime.UtcNow;
if (entityEntry.State == EntityState.Added)
{
((BaseEntity)entityEntry.Entity).CreatedDate = DateTime.UtcNow;
}
}
return base.SaveChanges();
}
And the SaveChanges() works without problem. When I create the issue entity the first time the DateTime has the correct value and I can also see it in the Detail View.
Change your edit code to this
var exist = d.Set<Issue>().FindAsync(issue.Id);
if (exist == null)
{
//ErrorMessage = "Can't find item to update";
}
else{
db.Entry(exist).CurrentValues.SetValues(issue);
var result = await db.SaveChanges(); // result should be > 0
}

How to update data with child using EF Core

I have these two models:
public class Film
{
public long Id { get; set; }
[Required]
public string Title { get; set; }
[Required]
public string Director { get; set; }
public string Synopsis { get; set; }
public int? Release { get; set; }
[Required]
public Genre Genre { get; set; }
}
public class Genre
{
public long Id { get; set; }
public string Description { get; set; }
}
And I want to be able to update a Film's Genre through a PUT method. I am currently trying this, but I get the following error:
[HttpPut]
public async Task<IActionResult> UpdateFilm(Film film)
{
var existingFilm = await _context.Films
.Include(f => f.Genre)
.FirstOrDefaultAsync(f => f.Id == film.Id);
if (existingFilm == null)
{
return NotFound(new JsonResponse { Success = false, Message = "Impossible to update, film was not found", Data = null });
}
existingFilm.Title = film.Title;
existingFilm.Synopsis = film.Synopsis;
existingFilm.Release = film.Release;
existingFilm.Director = film.Director;
if (existingFilm.Genre.Id != film.Genre.Id)
{
existingFilm.Genre.Id = film.Genre.Id;
existingFilm.Genre.Description = film.Genre.Description;
//_context.Entry(existingFilm.Genre).State = EntityState.Modified;
_context.Entry(existingFilm).CurrentValues.SetValues(film);
}
_context.Films.Update(existingFilm);
try
{
await _context.SaveChangesAsync();
}
catch (Exception e)
{
return BadRequest(new JsonResponse { Success = false, Message = e.Message, Data = null });
}
return Ok(new JsonResponse { Success = true, Message = "Film updated with success", Data = film });
}
The error message is:
System.InvalidOperationException: The property 'Id' on entity type 'Genre' is part of a key and so cannot be modified or marked as modified. To change the principal of an existing entity with an identifying foreign key first delete the dependent and invoke 'SaveChanges' then associate the dependent with the new principal.
Anyone able to help? Thanks a lot.
According to the error, its existingFilm.Genre.Id that you cannot update the id, if its not equal to the id.
My suggestion would be ignore the id update, but if it is necessary:
if (existingFilm.Genre.Id != film.Genre.Id)
{
var genre = _context.Genre.FirstOrDefault(x => x.Id == film.Genre.Id);
// Update the fields needed except the id
existingFilm.Genre = genre;
}

How do I update a field of a record using linq

I am trying to do 2 things. The first is to get the id of a record of the Client table and convert it to integer. My query is okay for that but how do I accomplish the second task - that is to update the record in the Investments table (field: Client_ID) to the first mentioned id. Thanks for your help. Maybe there's a better way to do this.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "ID,LastName,Fund,PurchaseDate,Shares,PurchasePrice,PurchaseAmount")] Investments investments)
{
if (ModelState.IsValid)
{
db.Ports.Add(investments);
db.SaveChanges();
//db.SaveChanges();
// write the Client_ID to the saved record
// first get the ID of the client
var q = (from c in context.Clients
where c.LastName == investments.LastName
select new
{
c.ID
}).ToList();
// next update the Client_ID of the Investment record
var result = (from p in context.Investments
where (p.LastName == investments.LastName)
&& (p.Client_ID == null)
select p);
//I'm not sure what to do next
//Context.SaveChanges();
return RedirectToAction("Index");
}
return View(investments);
}
++++++++++++++ new code 12/22/2016 - controller +++++++++++++++++
public ActionResult Create([Bind(Include = "ID,LastName,Fund,PurchaseDate,Shares,PurchasePrice,PurchaseAmount")] Investments investments)
{
if (ModelState.IsValid)
{
var clientId = (from c in context.Clients
where c.LastName == investments.LastName
select new
{
c.ID
}).ToList().First();
var productList = System.Collections.Generic.IList < Investments >{ investments };
foreach (var investment in investments)
{
investment.Client_ID = clientId;
}
db.Ports.Add(investments);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(investments);
}
+++++++++++++++++++++ model Investments +++++++++++++++++++++
using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
namespace StockHoldings.Models
{
public class Investments
{
public int ID { get; set; }
[Required]
[StringLength(20)]
public string LastName { get; set; }
[Required]
public string Fund { get; set; }
[Required]
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime PurchaseDate { get; set; }
[Required]
[Range(1, 1000)]
public int Shares { get; set; }
[Required]
[Range(0.01, 200.00)]
[DataType(DataType.Currency)]
public double PurchasePrice { get; set; }
[DataType(DataType.Currency)]
public double PurchaseAmount { get; set; }
}
You need to mark the entities as Modified:
foreach(var toUpdate in result){
toUpdate.Client_ID = q.First(); // or int.Parse(q.First());
Context.Entry(toUpdate).State = EntityState.Modified;
}
Context.SaveChanges();
But why don't you actually update the investements before inserting them:
public ActionResult Create([Bind(Include = "ID,LastName,Fund,PurchaseDate,Shares,PurchasePrice,PurchaseAmount")] Investments investments)
{
if (ModelState.IsValid)
{
var clientId = (from c in context.Clients
where c.LastName == investments.LastName
select new
{
c.ID
}).ToList().First();
foreach(var investment in investements){
investment.Client_ID = clientId;
}
db.Ports.Add(investments);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(investments);
}
The true questions here are:
why retrieving the clientid with it's last name? Is it a good idea ? Maybe your endpoint should also receive the clientId ?
can't you already pass the clientId to the initial view bad or data and build your investments objects already with this property set at front end ?

An exception of type 'System.InvalidCastException' occurred in EntityFramework.Core.dll but was not handled in user code

I am trying to get the long value associated to an ActivityLogType it is associated to either a create, edit, and delete record in the database.
It is used for an audit / activity log that is needed.
public enum ActivityLogType
{
Create = 1,
Update = 2,
Delete = 3
}
My getter method:
public ActivityType GetType(ActivityLogType type)
{
var id = (long)type;
Console.WriteLine(id); // <---- this produces a 1 in the console. So the cast works?
return _context.ActivityTypes.Where(x => x.Id == id).FirstOrDefault(); // <-- This line throws the error
}
UPDATE 1
un-lucky suggested using (long)ActivityLogType.Create to get the desired output. I tried this: (Still not working)
public ActivityType GetType(ActivityLogType type)
{
switch (type)
{
case ActivityLogType.Create:
return _context.ActivityTypes.Where(x => x.Id == (long)ActivityLogType.Create).FirstOrDefault();
case ActivityLogType.Update:
return _context.ActivityTypes.Where(x => x.Id == (long)ActivityLogType.Update).FirstOrDefault();
case ActivityLogType.Delete:
return _context.ActivityTypes.Where(x => x.Id == (long)ActivityLogType.Delete).FirstOrDefault();
default:
return null;
}
}
UPDATE 2
Here is the ActivityType entity
public class ActivityType
{
public ActivityType()
{
this.Activities = new HashSet<Activity>();
}
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long Id { get; set; }
[Display(Name = "Label")]
public string Label { get; set; }
[Display(Name = "Display Order")]
public int Order { get; set; }
[Display(Name = "Is Active")]
public bool IsActive { get; set; }
[Display(Name = "Activities")]
public virtual ICollection<Activity> Activities { get; set; }
}
You can try this:
long id= Int64.Parse(Convert.ChangeType(type, Enum.GetUnderlyingType(type.GetType())).ToString());

Categories