ASP MVC Code First working with datetime computed columns - c#

I have gone through numerous examples, trying to figure out how to automatically set the CreatedDate value for each entity when saving changes to through unitOfWork and Repository pattern.
So what i have is simple POCO:
public class Partners:IAutoGenerateDateFields
{
[Key]
public int PartnerId { get; set; }
public string PartnerCode { get; set; }
public string PartnerName { get; set; }
public string CompanyName { get; set; }
public string City { get; set; }
public string Address { get; set; }
public string Phone { get; set; }
public string Fax { get; set; }
public string TaxId { get; set; }
public int PartnerTypeId { get; set; }
public int PartnerStateId { get; set; }
public int LocationId { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime CreatedDate { get; set; }
// [DatabaseGenerated(DatabaseGeneratedOption.Computed)]
// public DateTime ModifiedDate { get;set;}
public virtual Locations Location { get; set; }
}
This Class implements the IAutoGenerateDateFields interface:
interface IAutoGenerateDateFields
{
DateTime CreatedDate { get; set; }
// DateTime ModifiedDate { get; set; }
}
Finally in my unitOfWork class i check if the added/modified entity implements the above interface and set the CreatedDate value to current date:
public void Save() {
context.ChangeTracker.DetectChanges();
var added = context.ChangeTracker.Entries()
.Where(t => t.State == EntityState.Added)
.Select(t => t.Entity).ToArray();
foreach (var entity in added) {
if (entity is IAutoGenerateDateFields) {
var track = entity as IAutoGenerateDateFields;
track.CreatedDate = DateTime.Now;
}
}
var modified = context.ChangeTracker.Entries()
.Where(t => t.State == EntityState.Modified)
.Select(t => t.Entity).ToArray();
foreach (var entity in modified) {
if (entity is IAutoGenerateDateFields) {
var track = entity as IAutoGenerateDateFields;
track.CreatedDate = DateTime.Now;
}
}
context.SaveChanges();
}
But every time i hit savechanges i get an error that CreatedDate cannot be null. Thus, the entity does not get set with the current date.
What am i missing here?

Remove the [DatabaseGenerated(DatabaseGeneratedOption.Computed)] attribute, since you generate the value in your code.
I basically did the same thing yesterday, and the following code is working fine :
var pendingChanges = GetContext().ChangeTracker.Entries<T>().Select(e => e.Entity).ToList();
foreach (var entity in pendingChanges)
{
entity.DateModified = DateTime.Now
}

Related

How to avoid populating same prop for different classes C#

my simple app has 10 tables, each of these tables has same propery "CreatedDate" which is a DateTime prop and as its name says its holding creation date.
Since I'm fetching all the data through DTO's for example if I want to get articles from database I'm mapping it to ArticleDto and returning data to the user.
And I'm doing that all for each 10 classes-entites.
And each Dto, (ArticleDto, GroupDto, UserDto, TownDto, AddressDto) all of them now have DateTime property which I'm populating when retrieving data from database..
Is it possible to achieve somehow that this prop is automatically populated ?
This is how I am doing it right now for Towns for example:
public class TownGetDto : DateTimeGetDto
{
public long Id { get; set; }
public string Title { get; set; }
public string ZipCode { get; set; }
public string DialingCode { get; set; }
public long CountryId { get; set; }
}
public class DateTimeGetDto
{
public DateTime CreatedDate { get; set; }
}
public async Task<TownGetDto> GetTownsAsync(CancellationToken cancellationToken)
{
var towns = await _dbContext.Towns
.Select(x => new TownGetDto
{
Id = x.Id,
Title = x.Title,
DialingCode = x.DialingCode,
ZipCode = x.ZipCode,
CountryId = x.CountryId,
CreatedDate = x.CreatedDate // How to get rid of this prop?
})
.ToListAsync(cancellationToken);
return towns;
}
As you can see I am popualting CreatedDate in Select and I'm doing that for all my classes/entities..
Is it possible somehow to fill-populate this prop automatically ?
Thanks everyone,
Cheers
You still need to specify to each of the 10 classes where the DateTime value should come from, but it might be cleaner to use constructors and inheritance
In DateTimeGetDto:
public DateTime CreatedDate { get; set; }
public DateTimeGetDto(DateTime dateTime)
{
CreatedDate = dateTime;
}
In TownGetDto:
public class TownGetDto : DateTimeGetDto
{
public long Id { get; set; }
public string Title { get; set; }
public string ZipCode { get; set; }
public string DialingCode { get; set; }
public long CountryId { get; set; }
public TownGetDto(Town town) : base(town.CreatedDate)
{
Id = town.Id;
Title = town.Title;
DialingCode = town.DialingCode;
ZipCode = town.ZipCode;
CountryId = town.CountryId;
CreatedDate = town.CreatedDate;
}
public async Task<TownGetDto> GetTownsAsync(CancellationToken cancellationToken)
{
var towns = await _dbContext.Towns
.Select(x => new TownGetDto(x))
.ToListAsync(cancellationToken);
return towns;
}
}

net core asp with automatic create and update data field

I have a problem with EF and MVC on net core.
I have an "item" model. This model has the required "createdate" and "updateDate" fields (I can't have a record without having the record date).
I use both fields with inheritance over BaseEntity.
The point is that to assign these dates, I do it directly in the context, overriding the "SaveChanges" function.
Because of this, by not giving it a value in either the view or the controller, the model evaluation fails because the dates are null. I actually give it value but after passing validation.
What do you think would be the most correct solution?
This is my model:
public class ItemType : BaseEntity
{
[Key]
public int Id { get; set; }
[Required]
public string Description { get; set; }
public ICollection<Item> Items { get; set; }
public int SizeTypeId { get; set; }
public SizeType SizeType { get; set; }
}
public class BaseEntity
{
[Required]
public bool Active { get; set; }
public DateTime? DeleteDate { get; set; }
public DateTime CreatedDate { get; set; }
public DateTime UpdatedDate { get; set; }
public string Comments { get; set; }
}
this is my savechanges override:
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.Now;
if (entityEntry.State == EntityState.Added)
{
((BaseEntity)entityEntry.Entity).CreatedDate = DateTime.Now;
}
}
return base.SaveChanges();
}
this is my controller:
public async Task<IActionResult> Create([Bind("Id,Description,Active,DeleteDate,CreatedDate,UpdatedDate,Comments")] SizeType sizeType)
{
if (ModelState.IsValid)
{
_context.Add(sizeType);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(sizeType);
}
Thanks you very much!!
One easy option would be to just make the properties nullable i.e. DateTime? CreatedDate since the entity isn't already created or deleted it makes sense for the CreatedDate or UpdatedDate properties to be null. However, a better option is to just create a DTO or in other words, a ViewModel that wraps the data of your entity and exposes it to the View. For example:
public class ItemTypeRequest : BaseEntityRequest
{
[Required]
public string Description { get; set; }
public ICollection<Item> Items { get; set; }
public int SizeTypeId { get; set; }
public SizeType SizeType { get; set; }
}
public class BaseEntityRequest
{
[Required]
public bool Active { get; set; }
public string Comments { get; set; }
}
Normally in a well-defined ViewModel, you are exposing to the View only the minimal set of data that is needed to perform the operation. In this case the Create View does not need an Id, CreatedDate, or UpdatedDate, because the entity isn't already created i.e. it does not exist in the database and therefore has no Id or creation date. After creating the ViewModels you can leverage AutoMapper to map the ViewModels data to the entity data
var config = new MapperConfiguration(cfg => {
cfg.CreateMap<ItemTypeRequest , ItemType>();
cfg.CreateMap<ItemType, ItemTypeResponse>();
});
var mapper = config.CreateMapper();
public async Task<IActionResult> Create(ItemTypeRequest itemTypeRequest)
{
if (ModelState.IsValid)
{
ItemType itemType = mapper.Map<ItemType>(itemTypeRequest);
_context.Add(itemType);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View();
}
Following the same login, you should create a response DTO/ViewMode this time with more data if you need it i.e.
public class ItemTypeResponse : BaseEntityResponse
{
public int Id { get; set; }
public string Description { get; set; }
public ICollection<Item> Items { get; set; }
public int SizeTypeId { get; set; }
public SizeType SizeType { get; set; }
}
public class BaseEntityResponse
{
public bool Active { get; set; }
public DateTime CreatedDate { get; set; }
public DateTime UpdatedDate { get; set; }
public string Comments { get; set; }
}
This time the properties CreatedDate and UpdatedDate are included since they have values after you have created the entity.

EF core won't let me access the same property twice

I'm trying to store older versions of entities in my database. To do that I am copying the existing values before I update them. For some reason EF Core won't let me use the same batch.Values property twice.
public async Task<Batch> UpdateBatch(Batch batch, Batch updatedBatch)
{
foreach (var valueParameter in batch.Values)
{
batch.ValuesHistory.Add(new ParameterValueHistory
{
Parameter = valueParameter.Parameter,
ParameterBatchNumber = valueParameter.ParameterBatchNumber,
Value = valueParameter.Value
});
}
batch.Values = updatedBatch.Values;
batch.Version++;
await this.context.SaveChangesAsync();
return batch;
}
The foreach loop and batch.Values = updatedBatch.Values; work exactly like they should when only one of them exists. But whenever they're both active I get the following error:
The instance of entity type 'ParameterValue' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.
These are the relevant models:
ParameterValue:
public class ParameterValue
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long Id { get; set; }
[Required]
public virtual RecipeParameter Parameter { get; set; }
public string Value { get; set; }
public string? ParameterBatchNumber { get; set; }
}
ParameterValueHistory:
public class ParameterValueHistory
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long Id { get; set; }
[Required]
public virtual RecipeParameter Parameter { get; set; }
public string Value { get; set; }
public string? ParameterBatchNumber { get; set; }
}
RecipeParameter for context:
public class RecipeParameter
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long Id { get; set; }
[Required]
public string Name { get; set; }
[Required]
public string Type { get; set; }
public string Unit { get; set; }
public string Value { get; set; }
public bool BatchRequired { get; set; }
}
Batch:
public class Batch
{
[Key]
[MaxLength(12)]
public string BatchNumber { get; set; }
public virtual List<ParameterValue> Values { get; set; }
public virtual List<ParameterValueHistory> ValuesHistory { get; set; }
public int Version { get; set; }
[Required]
public bool IsResearch { get; set; }
[Required]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public DateTime CreatedOn { get; set; } = DateTime.UtcNow;
}
This is my DbContext class:
public class ApplicationDataContext : DbContext
{
public ApplicationDataContext(DbContextOptions<ApplicationDataContext> options)
: base(options)
{
}
public DbSet<Product> Product { get; set; }
public DbSet<Batch> Batch { get; set; }
public DbSet<ParameterValue> ParameterValue { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseLazyLoadingProxies();
base.OnConfiguring(optionsBuilder);
}
}
Why does this error keep showing up? Even when I am just accessing the propety as batch.Values more than once, it gives me this error.
UPDATE:
This is the controller method that calls the UpdateBatch method.
[HttpPut("{productId}/batches/{batchNumber}")]
public async Task<ActionResult<Batch>> PutBatch(string batchNumber, Batch updatedBatch)
{
Batch batch = await this.repository.GetBatchByBatchNumber(batchNumber);
if (batch == null)
{
return NotFound()
}
return await this.repository.UpdateBatch(batch, updatedBatch);
}
When you use batch.Values = updatedBatch.Values;, because batch.Values contains the foreign key of Batch, and if the value in updatedBatch.Values also contains the key value,if the equal operation is performed directly, due to the foreign key constraint, the foreign key cannot be modified directly, which will cause your error.
Therefore, you cannot include the key value in the Values in your updateBatch.
Regarding your question. I did a simple test. You can see the following code(updateBatch.Values have no Id).
var batch = _context.Batches.Include(c => c.Values)
.ThenInclude(c => c.Parameter)
.Include(b => b.ValuesHistory)
.ThenInclude(c => c.Parameter)
.Where(c => c.BatchNumber == "1")
.FirstOrDefault();
var updateBatch = new Batch
{
Version = 3,
CreatedOn = new DateTime(),
IsResearch = true,
Values = new List<ParameterValue>
{
new ParameterValue
{
Value = "hello",
Parameter = new RecipeParameter
{
BatchRequired = true,
Name = "h",
Type = "e",
Unit = "l",
Value = "o"
}
},
},
ValuesHistory = new List<ParameterValueHistory>()
};
foreach (var valueParameter in batch.Values)
{
batch.ValuesHistory.Add(new ParameterValueHistory
{
Parameter = valueParameter.Parameter,
ParameterBatchNumber = valueParameter.ParameterBatchNumber,
Value = valueParameter.Value
});
}
batch.Values = updateBatch.Values;
batch.Version++;
_context.SaveChanges();
Test result:
start by making these changes..
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
should not be on
[Required]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public DateTime CreatedOn { get; set; } = DateTime.UtcNow;
instead model like
public class Batch
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long Id { get; set; }
//you can add index on this
[MaxLength(12)]
public string BatchNumber { get; set; }
public int Version { get; set; }
[Required]
public bool IsResearch { get; set; }
[Required]
public DateTime CreatedOn { get; set; };// set this in the repo or create do another way
//you add this but don't see the linkage aka ParameterValue does not have a BatchId
public virtual List<ParameterValue> Values { get; set; }
public virtual List<ParameterValueHistory> ValuesHistory { get; set; }
}

Collections duplicated when trying to update a detached entity's related collection

I have two API calls. GetExam and SaveExam. GetExam serializes to JSON which means by the time I go to save, the entity is detached. This isnt a problem, I can go retrieve the entity by its primary key and update its properties manually.
However, when I do so the exam questions get its current collection duplicated. For example, if examToSave.ExamQuestions had a few questions deleted, and a new one added all selectedExam.exam_question are duplicated and the new one is added in. Eg. if 3 questions existed, I deleted 1 and added 4 there will now be 7.
Domain models:
public partial class exam
{
public exam()
{
this.exam_question = new HashSet<exam_question>();
}
public int ID { get; set; }
public string ExamName { get; set; }
public string ExamDesc { get; set; }
public Nullable<decimal> TimeToComplete { get; set; }
public bool AllowBackStep { get; set; }
public bool RandomizeAnswerOrder { get; set; }
public int Attempts { get; set; }
public virtual ICollection<exam_question> exam_question { get; set; }
}
public partial class exam_question
{
public exam_question()
{
this.exam_answer = new HashSet<exam_answer>();
}
public int ID { get; set; }
public int ExamID { get; set; }
public string QuestionText { get; set; }
public bool IsFreeForm { get; set; }
public virtual exam exam { get; set; }
public virtual ICollection<exam_answer> exam_answer { get; set; }
}
public partial class exam_answer
{
public int ID { get; set; }
public string AnswerText { get; set; }
public int QuestionID { get; set; }
public bool IsCorrect { get; set; }
public virtual exam_question exam_question { get; set; }
}
Save method:
[Route("SaveExam")]
[HttpPost]
public IHttpActionResult SaveExam(ExamViewModel examToSave)
{
using (var db = new IntranetEntities())
{
// try to locate the desired exam to update
var selectedExam = db.exams.Where(w => w.ID == examToSave.ID).SingleOrDefault();
if (selectedExam == null)
{
return NotFound();
}
// Redacted business logic
// Map the viewmodel to the domain model
Mapper.CreateMap<ExamAnswerViewModel, exam_answer>();
Mapper.CreateMap<ExamQuestionViewModel, exam_question>().ForMember(dest => dest.exam_answer, opt => opt.MapFrom(src => src.QuestionAnswers));
Mapper.CreateMap<ExamViewModel, exam>().ForMember(dest => dest.exam_question, opt => opt.MapFrom(src => src.ExamQuestions));
var viewmodel = Mapper.Map<exam>(examToSave);
// Update exam properties
selectedExam.ExamName = viewmodel.ExamName;
selectedExam.ExamDesc = viewmodel.ExamDesc;
selectedExam.AllowBackStep = viewmodel.AllowBackStep;
selectedExam.Attempts = viewmodel.Attempts;
selectedExam.RandomizeAnswerOrder = viewmodel.RandomizeAnswerOrder;
selectedExam.exam_question = viewmodel.exam_question; // DUPLICATES PROPS
// Save
db.SaveChanges();
return Ok(examToSave);
}
}

Update Parent and Insert Child Telerik Opn Access with AutoMapper

I am using Telerik Open Access for database operation. I have a Parent Class named Order and it includes 2 classes named OrderHistory and Tasks. Now any kind of event happen like if order is put on hold or put for dispatching it's related entry will be put on OrderHistory table and Order table will be updated. Now I have done following code but it works sometimes but sometimes not. I don't know much about this telerik open access and automapper.
controller call:
OrderDTO updateorder = orderManagement.GetOrdersByOrderId(2);
updateorder.QueueId = 3;
updateorder.IsLocked = false;
updateorder.UpdatedBy = Convert.ToInt32(Session["UserId"], CultureInfo.CurrentCulture);
updateorder.UpdatedDate = DateTime.Now;
OrderHistoryDTO alertDto = new OrderHistoryDTO()
{
Event = 'Putting On Hold',
OrderID = orderDTO.Id
UserID = Convert.ToInt32(Session["UserId"], CultureInfo.CurrentCulture),
OccuerdDate = DateTime.Now,
EventType = 'Event'
};
updateorder.OrderHistories.Clear();
updateorder.OrderHistories.Add(alertDto);
updateorder = orderManagement.UpdateOrder(updateorder);
db operations
public OrderDTO UpdateOrder(OrderDTO orderEntity)
{
AutoMapper.Mapper.CreateMap<OrderDTO, Order>()
.ForMember(d => d.Tasks, m => m.Ignore())
.ForMember(d => d.OrderHistories, m => m.MapFrom(s => s.OrderHistories));
AutoMapper.Mapper.CreateMap<OrderHistoryDTO, OrderHistory>();
var orderBase = AutoMapper.Mapper.Map<Order>(orderEntity); // It will sometimes make OrderHistories list count to 0. though in starting orderEntity has OrderHistories count = 1.
base.Update(orderBase);
base.Save();
orderEntity = AutoMapper.Mapper.Map<OrderDTO>(orderBase);
return orderEntity;
}
OrderDTO
public class OrderDTO
{
public int OrderID { get; set; }
public bool? IsLocked { get; set; }
public int QueueId { get; set; }
[ScriptIgnore]
private IList<OrderHistoryDTO> _orderHistories = new List<OrderHistoryDTO>();
[ScriptIgnore]
public virtual IList<OrderHistoryDTO> OrderHistories
{
get { return this._orderHistories; }
}
public DateTime? UpdatedDate { get; set; }
public int? UpdatedBy { get; set; }
}
OrderHistoryDTO
public class OrderHistoryDTO
{
public int ID { get; set; }
public int UserID { get; set; }
public int OrderID { get; set; }
public string Event { get; set; }
public string EventType { get; set; }
public DateTime? OccuerdDate { get; set; }
public UserDTO User { get; set; }
}

Categories

Resources