Should I use custom setters in EF Core models? - c#

I was just wondering if i should use custom setters in EF Core models. Consider this very simple example:
using System;
namespace EFTest.Models
{
public class Reservation
{
public int ReservationId { get; set; }
public DateTime Start { get; set; }
public DateTime End { get; set; }
public int ResourceId { get; set; }
public int CustomerId { get; set; }
public Customer Customer { get; set; }
public Resource Resource { get; set; }
}
public class Resource
{
public int ResourceId { get; set; }
public string Name { get; set; }
}
public class Customer
{
public int CustomerId { get; set; }
public string Name { get; set; }
}
}
The issue being that when i have to save a model, and add an instance of a class to it, it handles getting the foreign key from said added instance just fine, like so:
public void SaveReservation()
{
var db = new Datebase();
var reservation = new Reservation(){ Start = new DateTime().Now, End = new DateTime().Now.AddDays(7)};
reservation.Resource = db.Resources.Find(2);
reservation.Customer = db.Customers.Find(4);
db.Reservations.Add(reservation);
db.SaveChanges();
}
but if i set the foreign key property for customer, but then add the instance of a resource, forexample, it is utterly unable to handle getting the foreignkey, like so:
public void SaveReservation()
{
var db = new Datebase();
var reservation = new Reservation(){ Start = new DateTime().Now, End = new DateTime().Now.AddDays(7)};
reservation.Resource = db.Resources.Find(2);
reservation.CustomerId = 4;
db.Reservations.Add(reservation);
db.SaveChanges();
}
Resulting in a sqlite exception 19, 'failing to get foreignkey' or somesuch.
The only solution i can think of is to do custom setters and getters to handle setting the property based on the key, and vice versa, e.g.:
using System;
namespace EFTest.Models
{
public class Reservation
{
public int ReservationId { get; set; }
public DateTime Start { get; set; }
public DateTime End { get; set; }
public int ResourceId
{
get
{
if(_Resource != null)
{
return _Resource.ResourceId;
}
else
{
return 0;
}
}
set
{
if(_Resource != null && !_Resource.ResourceId.Equals(value))
{
_Resource = null;
}
}
}
public int CustomerId
{
get
{
if(_Customer != null)
{
return _Customer.CustomerId;
}
else
{
return 0;
}
}
set
{
if(_Customer != null && !_Customer.CustomerId.Equals(value))
{
_Customer = null;
}
}
}
public Customer Customer
{
get
{
return _Customer;
}
set
{
_Customer = value;
if(value != null)
{
CustomerId = _Customer.CustomerId;
}
else
{
CustomerId = 0;
}
}
}
private Customer _Customer { get; set; }
public Resource Resource
{
get
{
return _Resource;
}
set
{
_Resource = value;
if(value != null)
{
ResourceId = _Resource.ResourceId;
}
else
{
ResourceId = 0;
}
}
}
private Resource _Resource { get; set; }
}
public class Resource
{
public int ResourceId { get; set; }
public string Name { get; set; }
}
public class Customer
{
public int CustomerId { get; set; }
public string Name { get; set; }
}
}
But I am not at all certain this is a good way to handle the issue, can someone provide some insight on whether it is a good way to handle it? and whether there is a better one?
thank you.

A thank you to DevilSuichiro for providing the very clear and useful answer:
"But no, you shouldn't use custom getters or setters in your BO's, those should reflect your db schema pretty neatly."

Related

I am working on an API for a class I am in and I have a cannot convert error

I am working on an API and I have an error that says "cannot convert from "HolidayChallenge.Models.Ornament.Ornament2Edit to HolidayChallenge.Data.Ornament2". The code follows:
[Route("api/Ornament2/Update")]
public IHttpActionResult Put(Ornament2Edit ornament2)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var service = CreateOrnament2Service();
if (!service.UpdateOrnament(ornament2)) //This is the line the error is on
{
return InternalServerError();
}
return Ok("Your Ornament was updated!");
I am not sure how to fix this one. I have figured a lot of similar ones out but this one is fighting me. Any help would be much appreciated. Thank you in advance.
Update to question
OrnamentEdit:
namespace HolidayChallenge.Models.Ornament
{
public class Ornament2Edit
{
[Key]
public int Id { get; set; }
public string Description { get; set; }
public int TreeId { get; set; }
}
}
Ornament2:
namespace HolidayChallenge.Data
{
public class Ornament2
{
[Key]
public int Id { get; set; }
public Guid UserId { get; set; }
public string Description { get; set; }
public virtual List<Ornament2> Ornaments { get; set; }
[ForeignKey("ChristmasTree")]
public int TreeId { get; set; }
[Required]
public virtual ChristmasTree ChristmasTree { get; set; }
}
}
Ornament2Service:
public bool UpdateOrnament(Ornament2 model)
{
using (var ctx = new ApplicationDbContext())
{
var entity =
ctx
.Ornaments
.Single(e => e.Id == model.Id && e.UserId == _userId);
entity.Id = model.Id;
entity.Description = model.Description;
entity.TreeId = model.TreeId;
return ctx.SaveChanges() == 1;
}
}
your Service input is Ornament2 object but you pass an Ornament2Edit object and this causes the error. you must pass an object with the correct type.

ASP.Net Core 5.0 Binding model to action with [Bind] attribute for nested child collection

I am trying to bind a model in a post action method. i.e binding with the help of [Bind] attribute.
Where I post some fields for parent while a collection of child properties at the same time.
Supose I have parent as following
class Parent
{
int field0;
string field1;
string field2;
ICollection<Child> Children;
}
class Child
{
int field3;
string field4;
string field5;
}
at the time of binding I can choose fields to bind for simple binding like [Bind("field1, field2")] and to include children as well then [Bind("field1,field2,children")]
But I need to include some fields of children like children("field4", "field5")
Is there any possibility so that I can write like following
public IActionResult UTOneFlight([Bind("field1, field2, children(field4, field5)")] Parent p)
{
}
UPDATE
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> UTOneFlight([Bind("FlightID, SrcAirportID, DestAirportID, FlightDate, Sector, RegistrationNo, FlightNo, CallSign, CrewMembers, EmbDetails, UpdateRemarks")] FlightViewModel f)
{
if (f != null && f.EmbDetails != null)
{
if (f.FlightID == 0)
{
var flight = new Flight()
{
EmbDetails = new List<EmbDetail>(),
FlightType = "emb",
AirlineOperatorID = _user.OperatorID,
SrcAirportID = f.SrcAirportID,
DestAirportID = f.DestAirportID,
FlightDate = f.FlightDate,
Sector = f.Sector.ToString().ToLower()[0],
FlightNo = f.FlightNo.Trim().ToLower(),
CallSign = f.CallSign.Trim().ToLower(),
RegistrationNo = f.RegistrationNo.Trim().ToLower(),
CrewMembers = f.CrewMembers,
UpdateRemarks = f.UpdateRemarks?? f.UpdateRemarks,
EmbDataStatus = 'u',
CreatedBy = _user.UserID
};
foreach (var e in f.EmbDetails)
{
flight.EmbDetails.Add(
new EmbDetail()
{
PaxType = e.PaxType,
PaxClass = e.PaxClass,
AdultPax = e.AdultPax,
Infants = e.Infants,
Dips = e.Dips,
FOC = e.FOC,
TransferPax = e.TransferPax,
CreatedBy = _user.UserID
}
);
}
await _db.AddAsync(flight);
return RedirectToAction("Index");
}
else
{
//var flight = await _db.SingleAsync<Flight>(x => x.FlightID == f.FlightID);
//return RedirectToAction("Index");
}
}
else
return NotFound();
}
and my models are
public class FlightViewModel
{
public long FlightID { get; set; }
public int SrcAirportID { get; set; }
public int DestAirportID { get; set; }
public string RegistrationNo { get; set; }
public string FlightNo { get; set; }
public string CallSign { get; set; }
public DateTime FlightDate { get; set; }
public int CrewMembers { get; set; }
public char Sector { get; set; }
public string UpdateRemarks { get; set; }
public ICollection<EmbDetViewModel> EmbDetails { get; set; }
}
and
public class EmbDetViewModel
{
public string PaxType { get; set; }
public char PaxClass { get; set; }
public int AdultPax { get; set; }
public int Infants { get; set; }
public int Dips { get; set; }
public int Crew { get; set; }
public int FOC { get; set; }
public int TransferPax { get; set; }
}
I need to write signature of the method like
public async Task<IActionResult> UTOneFlight([Bind("FlightID, SrcAirportID, DestAirportID, FlightDate, Sector, RegistrationNo, FlightNo, CallSign, CrewMembers, EmbDetails(PaxType, PaxClass), UpdateRemarks")] FlightViewModel f)
Please have a look at
EmbDetails(PaxType, PaxClass)
How do you send your request body? I test in my side and here's the result.
My model:
public class ParentTestModel
{
public int id { get; set; }
public ICollection<TestModel> testModels { get; set; }
}
public class TestModel
{
public string prefix { get; set; }
}
==============================Update=============================
I test in my side with [JsonIgnore] and the property which added this annotation will be ignored and this is suitable when the request body is a json object like the screenshot above. And if you are sending the request in form-data then you can use [Bind] annotation, I think you may have referred to this document.

setting value of a property in source list

I have 2 lists
public class EmailDetails
{
public int EmailMasterID { get; set; }
public string Name { get; set; }
public string Body { get; set; }
public string Number { get; set; }
public string IsModified { get; set; }
}
public class EmailDetailsActual
{
public string ProgramNumber { get; set; }
public string IsModified { get; set; }
public int EmailMasterID_FK { get; set; }
}
I need to set value of IsModified column to YES in EmailDetails list if EmailMasterID = EmailMasterID_FK (from EmailDetailsActual list) . If not, then set value to NO. The final result should be EmailDetails list.
im not sure but i put new EmailDetailsActual in EmailDetails and I put the details for that (ProgramNumber , IsModified , EmailMasterID_FK)
and then I put input to when Call EmailDetails , should fill inputs like
EmailDetails p1 = new EmailDetails("ProgramNumber", "IsModified", 0000);
after i put IsModified Get properties in EmailDetails >>
if (EmailMasterID == EDA.EmailMasterID_FK)
{
return "yes";
}
else
{
return "no";
}
// EDA is new EmailDetailsActual
And I accessed it values ​​in a this way (accessed EmailMasterID_FK (we create new EmailDetailsActual ) )
and its my finally >>>
public class EmailDetails
{
EmailDetailsActual EDA = new EmailDetailsActual();
public EmailDetails(string ProgramNumber , string IsModified , int EmailMasterID_FK)
{
EDA.ProgramNumber = ProgramNumber;
EDA.IsModified = IsModified;
EDA.EmailMasterID_FK = EmailMasterID_FK;
}
public int EmailMasterID { get; set; }
public string Name { get; set; }
public string Body { get; set; }
public string Number { get; set; }
public string IsModified { get
{
if (EmailMasterID == EDA.EmailMasterID_FK)
{
return "yes";
}
else
{
return "no";
}
}
}
}
public class EmailDetailsActual
{
public string ProgramNumber { get; set; }
public string IsModified { get; set; }
public int EmailMasterID_FK { get; set; }
}
this is example to work >>
EmailDetails p1 = new EmailDetails("ProgramNumber", "IsModified",9991);
p1.EmailMasterID = 9992;
Console.WriteLine(p1.IsModified);
its output no bc EmailMasterID (9991) its not same with EmailMasterID_FK(9992)
I hope I able to help you :)

WebAPI [FromBody] as EF Model behaves like it is immutable

I have an WebAPI Controller that uses complex types from Entity Framework. When I receive the object I check to see if it exists. If it doesn't I'd like to create a new. Before I create a new I'd like to add a couple additional values to the object. If I add a break point and a watch I can see the value and it appears like it has changed. But the value doesn't make it to the database.
[Authorize(Roles ="customerprofileuser")]
[Route("api/CustomerProfile/Save")]
[HttpPost]
public IHttpActionResult SaveCustomerProfile([FromBody] MERP.Customer _input)
{
Models.Message.Response _return = new Models.Message.Response();
_return.Message = "Profile Saved!";
_return.Now = DateTime.Now;
try {
ERPEntities ent = new ERPEntities();
var cust = ent.Customers.AsNoTracking().Where(w => w.ID == _input.ID).FirstOrDefault();
if (cust == null)
{
_input.ID = Guid.NewGuid();
_input.Alias = getCustomerNumberNext(_input.Type);
_input.CreatedOn = DateTime.Now;
ent.Customers.Add(_input);
}
else
{
ent.Customers.Attach(_input);
ent.Entry(_input).State = System.Data.Entity.EntityState.Modified;
}
_return.ResponseObject = _input.ID.ToString();
ent.SaveChanges();
}
catch (Exception ex)
{
_return.Message = ex.Message;
_return.Severity = 3;
}
return Ok(_return);
}
If I map the values to a new object like this, everything works as expected.
var val = new Customer();
val.ID = Guid.NewGuid();
val.Active = _input.Active;
val.Alias = getCustomerNumberNext(_input.Type);
val.CreatedOn = DateTime.Now;
ent.Customers.Add(val);
I'd rather not map every single property to the new object property. Is there a way around this behavior?
Here's a sample of the auto-generated Customer class from my Entity Model.
public partial class Customer
{
public System.Guid ID { get; set; }
public string Name { get; set; }
public Nullable<System.Guid> Type { get; set; }
public string Alias { get; set; }
public string Website { get; set; }
public string Note { get; set; }
public string Email { get; set; }
public Nullable<System.Guid> Salesman { get; set; }
public Nullable<System.Guid> SalesRegion { get; set; }
public Nullable<bool> Active { get; set; }
public string LinkedIn { get; set; }
public string Facebook { get; set; }
public string Twitter { get; set; }
public string GoldmineFK { get; set; }
public string SalesFK { get; set; }
public string InventoryFK { get; set; }
public Nullable<System.Guid> Industry { get; set; }
public Nullable<System.Guid> Lead { get; set; }
public Nullable<System.Guid> Show { get; set; }
public Nullable<System.Guid> Territory { get; set; }
public Nullable<System.DateTime> CreatedOn { get; set; }
}
Here's the getCustomerNumberNext function
private string getCustomerNumberNext(Guid? companyid)
{
ERPEntities ent = new ERPEntities();
var _prefix = (from p in ent.CompanyLookups
where p.Type == "CustomerNumberPrefix"
select p.Value.ToString()).FirstOrDefault();
var _number = (from p in ent.CompanyLookups
where p.Type == "CustomerNumberSequence"
select p.Value.ToString()).FirstOrDefault();
var _newNumber = Convert.ToInt32(_number) + 1;
try
{
var _update = (from p in ent.CompanyLookups
where p.Type == "CustomerNumberSequence"
select p).FirstOrDefault();
_update.Value = _newNumber.ToString();
ent.SaveChanges();
}
catch (Exception ex)
{ return ex.Message; }
return _prefix + _number;
}
EDIT: The C# code works as expected. The issue was with the data round tripping from the client and incompleteness.
I believe there is a typo in your question, which says "does make it to the database" but I believe you meant "does not make it to the database"
With that assumption I tried running similar code locally and was able to save the values as expected. The primary difference is that Alias is an integer in my code and I am assuming it is a complex class in your code. Here is the code that successfully saved the values to the database,
public class HomeController : ApiController
{
[HttpPost]
[Route("api/CustomerProfile/Save")]
public IHttpActionResult SaveCustomerProfile([FromBody] Customer _input)
{
masterEntities masterEntities = new masterEntities();
var cust = masterEntities.Customers.AsNoTracking().Where(w => w.ID == _input.ID).FirstOrDefault();
if (cust == null)
{
_input.ID = Guid.NewGuid();
_input.Alias = 0;
_input.CreatedOn = DateTime.Now;
masterEntities.Customers.Add(_input);
}
else
{
masterEntities.Customers.Attach(_input);
masterEntities.Entry(_input).State = System.Data.Entity.EntityState.Modified;
}
masterEntities.SaveChanges();
return Ok();
}
}
Here is what the generated Customer class like,
public partial class Customer
{
public System.Guid ID { get; set; }
public bool Active { get; set; }
public Nullable<int> Alias { get; set; }
public Nullable<System.DateTime> CreatedOn { get; set; }
}
Can you update your question with the Customer and Alias classes from your code and I can try reproducing that?
On a side note, I would suggest changing
var cust = ent.Customers.AsNoTracking().Where(w => w.ID == _input.ID).FirstOrDefault();
to
var cust = ent.Customers.AsNoTracking().FirstOrDefault(w => w.ID == _input.ID);

How to add many to many relationship through Entity Framework 6, in code?

Hello I have following classes/Models mapped to database using Code-First
public class Speaker
{
public int Id { get; set; }
public string Title { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public string Mobile { get; set; }
public virtual ICollection<SpeakerCertification> Certifications { get; set; }
}
public class Certification
{
public int Id { get; set; }
public string Description { get; set; }
public virtual ICollection<SpeakerCertification> Speakers { get; set; }
}
public class SpeakerCertification
{
public int Id { get; set; }
public int SpeakerId { get; set; }
public int CertificationId { get; set; }
public virtual Speaker Speaker { get; set; }
public virtual Certification Certifications { get; set; }
}
The certification table is already populated with some data. When speaker information is added, the speakers certification must be added as well (which can be chosen through checkboxes). Now I have to add this information to the database. I have written following code and stuck (_ctx is the dbContext). I require the speakerId for speakerCertification table but the speaker is not yet added in the database. The requirement is speakerId must be an identity field. How can I achieve this? The string array selectedCertifications contain the certifications selected when adding the speaker
public bool AddSpeaker(Entities.Speaker speaker, string[] selectedCertifications)
{
if (selectedCertifications != null)
{
SpeakerCertification speakerCertification = new SpeakerCertification();
speaker.Certifications = new List<SpeakerCertification>();
foreach (var certificate in selectedCertifications)
{
var certificationToAdd = _ctx.Certifications.Find(int.Parse(certificate));
speakerCertification.CertificationId = certificationToAdd.Id;
//speakerCertification.SpeakerId
speaker.Certifications.Add(speakerCertification);
}
}
_ctx.Speakers.Add(speaker);
_ctx.SaveChanges();
return true;
}
Sorry for troubling those who went through the question, there was almost no problem at all. I was just too silly to miss it. I forgot the magic of Entity Framework. I implemented the add function in following way
public bool AddSpeaker(Entities.Speaker speaker, string[] selectedCertifications)
{
if (selectedCertifications != null)
{
SpeakerCertification speakerCertification = new SpeakerCertification();
speaker.Certifications = new List<SpeakerCertification>();
foreach (var certificate in selectedCertifications)
{
if (certificate.CompareTo("false")!=0)
{
var certificationToAdd = _ctx.Certifications.Find(int.Parse(certificate));
speakerCertification = new SpeakerCertification();
speakerCertification.CertificationId = certificationToAdd.Id;
speaker.Certifications.Add(speakerCertification);
}
}
}
_ctx.Speakers.Add(speaker);
_ctx.SaveChanges();
return true;
}

Categories

Resources