I'm using EF code first with the following model:
public class Root
{
public ChildA A { get; set; }
public ChildB B { get; set; }
public ChildC C { get; set; }
}
Suppose you have a controller
public class RecordController
{
...
public void Save(Root root)
{
...
}
...
}
and your Root controller has received a model from client that contains the following changes: property A is totally new it has not yet been added to database and needs to be created, property B already exists in database and needs to be updated, property C not changed.
Action Save is not aware of what the property changes are, it just needs to update the Record properly and create missing or update existing sub models, it is also possible that some Child classes may also have their own nested changes, so I need a method that will somehow recurse through the model compare new model to existing one and will apply appropriate changes. So how do I do that?
I've ended up with concatenating each of my model and ViewModel classes with EntityState property, so now when I change some property I set the EntityState to changed state, when I create one I set the property to state Added, initially models are initialised with Unchanged state, basically it looks something like the following:
[Table("City")]
[KnownType(typeof(Country))]
public class City
{
public City()
{
Airports = new List<Airport>();
LastUpdate = DateTime.Now;
}
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Int32 Id { get; set; }
public Int32? CountryId { get; set; }
[StringLength(50)]
public String Name { get; set; }
[Range(-12, 13)]
public Int32? TimeZone { get; set; }
public Boolean? SummerTime { get; set; }
public DateTime? LastUpdate { get; set; }
[ForeignKey("CountryId")]
public virtual Country Country { get; set; }
[NotMapped]
public EntityState? EntityState { get; set; } // <----------- Here it is
}
Then on server I do the following
[HttpPost, HttpGet]
public HttpResponseMessage SaveRecord(RecordViewModel record)
{
var model = Mapper.Map<Record>(record);
if (!ModelState.IsValid)
{
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
}
db.Attach(model);
try
{
db.SaveChanges();
}
catch (DbUpdateConcurrencyException ex)
{
return Request.CreateErrorResponse(HttpStatusCode.NotFound, ex);
}
return Request.CreateResponse(HttpStatusCode.OK);
}
Here is implementation for Attach method:
public void Attach(City entity)
{
if (entity != null)
{
Attach(entity.Country);
AttachAndMarkAs(entity, entity.EntityState ?? EntityState.Added, instance => instance.Id);
}
}
public void Attach(Country entity)
{
if (entity != null)
{
AttachAndMarkAs(entity, entity.EntityState ?? EntityState.Added, instance => instance.Id);
}
}
AttachAndMarkAs has the following implementation:
public void AttachAndMarkAs<T>(T entity, EntityState state, Func<T, object> id) where T : class
{
var entry = Entry(entity);
if (entry.State == EntityState.Detached)
{
var set = Set<T>();
T attachedEntity = set.Find(id(entity));
if (attachedEntity != null)
{
var attachedEntry = Entry(attachedEntity);
attachedEntry.CurrentValues.SetValues(entity);
}
else
{
entry.State = state;
}
}
}
Related
I have the following models in my API:
namespace API.Models
{
public class StudentDetailsViewModel
{
[Key]
public int StudentId { get; set; }
public AddressViewModel Address { get; set; }
public List<CoursesViewModel> Courses { get; set; }
}
public class AddressViewModel
{
public int AddressId { get; set; }
public int StudentId { get; set; }
public string Address { set; set; }
}
public CoursesViewModel
{
public int CourseId { get; set; }
public int StudentId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string Grade { get; set; }
}
}
I am writing a PUT method for StudentDetailsViewModel. The list in this model could have a number of records removed or added or a number of fields in one of the records updated. For example, grade for one of the courses updated or a course added or dropped.
What is the best approach in updating a model containing an object list like the above? Is it best to delete the entire list and re-add them?
I have the following thus far:
[ResponseType(typeof(void))]
public async Task<IHttpActionResult> PutStudenDetailsViewModel(StudentDetailsViewModel studentDetailsViewModel)
{
if(!ModelState.IsValid)
return BadRequest(ModelState);
var address = new DataAccess.Address
{
AddressID = studentDetailsViewModel.Address.AddessId,
StudentID = studentDetailsViewModel.Address.StudentId,
Address = studentDetailsViewModel.Address.Address
};
_context.Entry(address).State = EntityState.Modified;
// TODO: This is where the list Course entity needs to be updated
try
{
await _context.SaveChangesAsync();
}
catch(DbUpdateConcurrencyException)
{
if(!AddressViewModelExists(address.AddressID))
return NotFound();
throw;
}
return StatusCode(HttpStatusCode.NoContent);
}
Just an example from MS documentation for EF Core
public static void InsertOrUpdateGraph(BloggingContext context, Blog blog)
{
var existingBlog = context.Blogs
.Include(b => b.Posts)
.FirstOrDefault(b => b.BlogId == blog.BlogId);
if (existingBlog == null)
{
context.Add(blog); //or 404 response, or custom exception, etc...
}
else
{
context.Entry(existingBlog).CurrentValues.SetValues(blog);
foreach (var post in blog.Posts)
{
var existingPost = existingBlog.Posts
.FirstOrDefault(p => p.PostId == post.PostId);
if (existingPost == null)
{
existingBlog.Posts.Add(post);
}
else
{
context.Entry(existingPost).CurrentValues.SetValues(post);
}
}
}
context.SaveChanges();
}
I have a lot of models in my program that do similar things but we have a database practice to give each column specific names so "PersonId" instead of "Id"
But I would like to deal with them in a generic way. So for example in a class I have included below I want to do something like:
public virtual List<TModel> GetAll(int parentId)
{
return Context.Where(i => i.ParentID == parentId).ToList();
}
But I can't do that if ParentID unless its mapped to a database column
The specified type member is not supported in LINQ to Entities. Only initializers, entity members, and entity navigation properties are supported
So I am now trying to map it to a database column:
public interface IGenModel
{
int Id { get; set; }
int ParentID { get; set; }
}
public class ChildNote : BaseModel, IGenModel
{
[Key]
[DisplayName("Child Note ID")]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Column("ChildId")]
public int Id { get; set; }
[DisplayName("Referral")]
[Column("ReferralId")]
public int ParentID { get; set; }
[DisplayName("Cinican Notes")]
public string Notes { get; set; }
[ForeignKey("ReferralId")]
public virtual Referral Referral { get; set; }
/* Finally I tried to add something like this here
[NotMapped]
[Required]
[DisplayName("Referral")]
public int ReferralId => ParentID
*/
/* Original set uup
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[DisplayName("Child Note ID")]
public int ChildId { get; set; }
[Required]
[DisplayName("Referral")]
public int ReferralId { get; set; }
public int Id { get; set; }
{
get { return ChildId; }
set { ChildId = value; }
}
public int ParentID { get; set; }
{
get { return ReferralId; }
set { ReferralId = value; }
}*/
}
But when I try and build or update the database I get errors due to:
[ForeignKey("ReferralId")]
public virtual Referral Referral { get; set; }
As it says ReferralId does not map to something in the model.
Am I fighting a losing battle here? Is is simply not possible to generalise this? And just accept that all my accesses of context may have to be specialised? I am not going to be able to change the rule that we always prefix "Id" with the model name, apparently its very useful for searches within the database which makes sense to me.
and the classes I want to use
public abstract class GenLoader<TSelf, TModel> where TModel : BaseModel, IGenModel where TSelf : class, new()
{
private static TSelf instance = null;
public static TSelf Instance
{
get
{
if (instance == null)
{
instance = new TSelf();
}
return instance;
}
}
protected abstract DbSet<TModel> Context { get; }
public virtual TModel Get(int id)
{
return Context.FirstOrDefault(i => i.Id == id);
}
public virtual List<TModel> GetAll(int parentId)
{
return Context.Where(i => i.ParentID == parentId).ToList();
}
public virtual void OnAdd(TModel model)
{
}
public virtual void Add(TModel model)
{
model.LastModifiedBy = WebSecurity.CurrentUserId;
model.LastModified = DateTime.Now;
OnAdd(model);
using (TransactionScope scope = new TransactionScope())
{
Context.Add(model);
ModelContext.Current.SaveChanges();
scope.Complete();
}
}
public virtual void OnUpdate(TModel model)
{
}
public void Update(TModel model)
{
model.LastModifiedBy = WebSecurity.CurrentUserId;
model.LastModified = DateTime.Now;
OnUpdate(model);
using (TransactionScope scope = new TransactionScope())
{
ModelContext.Current.Entry(model).State = EntityState.Modified;
ModelContext.Current.SaveChanges();
scope.Complete();
}
}
}
public class ChildNotes : GenLoader<ChildNotes, ChildNote>
{
protected override DbSet<ClinicalNote> Context => ModelContext.Current.ClinicalNotes;
}
When applied to navigation property, ForeignKey attribute expects the FK property name, not the mapped db column name. So the following should fix the issue:
[ForeignKey("ParentID")]
public virtual Referral Referral { get; set; }
The alternative is, since you put many attributes on FK property, remove the ForeignKey attribute from navigation property and apply it on the FK property, in which case it expects the navigation property name as an argument:
[DisplayName("Referral")]
[Column("ReferralId")]
[ForeignKey("Referral")]
public int ParentID { get; set; }
...
public virtual Referral Referral { get; set; }
using .net 4.5.2, MVC5, Entity framework 6 and visual studio 2015.
I have a repository pattern set up with Ninject as my DI here is the common file.
private static void RegisterServices(IKernel kernel)
{
kernel.Bind<ApplicationDbContext>().ToSelf().InRequestScope();
kernel.Bind<IUserBlueRayLists>().To<UserBlueRayListRepository>().InRequestScope();
kernel.Bind<IBlueRays>().To<BlueRaysRepository>().InRequestScope();
}
my Context
public ApplicationDbContext()
: base("DefaultConnection", throwIfV1Schema: false)
{
}
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
public IDbSet<UserBlueRayList> UserBlueRayLists { get; set; }
public IDbSet<BlueRays> BlueRays { get; set; }
public new void SaveChanges()
{
base.SaveChanges();
}
}
public interface IDevTestContext
{
IDbSet<UserBlueRayList> UserBlueRayLists { get; set; }
IDbSet<BlueRays> BlueRays { get; set; }
void SaveChanges();
}
Then my Repository update method.
public bool Update(UserBlueRayList item)
{
var userBRList = _db.UserBlueRayLists.FirstOrDefault(x => x.Id == item.Id);
if(userBRList != null)
{
userBRList = item;
//_db.Entry(userBRList).State = EntityState.Modified;
_db.SaveChanges();
return true;
}
return false;
}
Now when i save via my controller and call the repository update method, nothing is updated.
So i use
_db.Entry(userBRList).State = EntityState.Modified;
But i get an error,
Additional information: Attaching an entity of type 'myapp.Models.UserBlueRayList' failed because another entity of the same type already has the same primary key value. This can happen when using the 'Attach' method or setting the state of an entity to 'Unchanged' or 'Modified' if any entities in the graph have conflicting key values... etc
Many to many models, userlist model.
public class UserBlueRayList
{
public UserBlueRayList()
{
this.BlueRays = new HashSet<BlueRays>();
}
[Key]
public int Id { get; set; }
[Required]
public string Name { get; set; }
[Required]
public string Description { get; set; }
[Required]
public string UserId { get; set; }
public virtual ICollection<BlueRays> BlueRays { get; set; }
}
And the
public class BlueRays
{
public BlueRays()
{
this.UserBlueRayList = new HashSet<UserBlueRayList>();
}
[Key]
public int Id { get; set; }
[Required]
public string Name { get; set; }
[Required]
public string Description { get; set; }
public virtual ICollection<UserBlueRayList> UserBlueRayList { get; set; }
}
Question is why this wont update, and why it errors if i try to set state to modified.
Since you are using EF6, you may try to use auto property mapping built into EF6
public bool Update(UserBlueRayList item)
{
var userBRList = _db.UserBlueRayLists.FirstOrDefault(x => x.Id == item.Id);
if(userBRList != null)
{
_dbContext.Entry(userBRList).CurrentValues.SetValues(item);
return return _dbContext.SaveChanges() > 0;
}
return false;
}
Cheers
First Solution you have to update like that
public bool Update(UserBlueRayList item)
{
var userBRList = _db.UserBlueRayLists.FirstOrDefault(x => x.Id == item.Id);
if(userBRList != null)
{
userBRList.Name = item.Name;
//value assign to other entity
_db.Entry(userBRList).State = EntityState.Modified;
_db.SaveChanges();
return true;
}
return false;
}
if this will not solve your problem you Find method instead of FirstorDefault()
public bool Update(UserBlueRayList item)
{
var userBRList = _db.UserBlueRayLists.Find(x => x.Id == item.Id);
if(userBRList != null)
{
userBRList.Name = item.Name;
//value assign to other entity
_db.Entry(userBRList).State = EntityState.Modified;
_db.SaveChanges();
return true;
}
return false;
}
I need to delete a record of an entity but keep all the records of another entity that is related with it:
Entity record to remove is:
public class Ask
{
// Primary properties
public int Id { get; set; }
public string Title { get; set; }
public string Message { get; set; }
public DateTime DateCreated { get; set; }
}
The related records that I want to keep after deleting an Ask record is of type MessageAsk :
public class Message
{
// Primary properties
public int Id { get; set; }
public string NameFrom { get; set; }
public string EmailFrom { get; set; }
public string TelephoneFrom { get; set; }
public string Title { get; set; }
public string MessageText { get; set; }
public bool? Approved { get; set; }
public DateTime? DateCreated { get; set; }
public DateTime? DateRead { get; set; }
// Navigation properties
public Member MemberFrom { get; set; }
public Member MemberTo { get; set; }
public MessageType MessageType { get; set; }
public Message MessageParent { get; set; }
}
public class MessageAsk : Message
{
public Ask Ask { get; set; }
}
Resuming, I want to delete an Ask and keep all related MessageAsk's.
EDIT:
I use the service Delete:
private readonly IRepository<Ask> _askRepository;
private readonly IRepository<MessageAsk> _messageAskRepository;
public bool Delete(int askId)
{
try
{
Ask askToDelete = _askRepository.GetById(askId);
IList<MessageAsk> relatedMessageAsks = _messageAskRepository.Query.Where(m => m.Ask.Id == askId).ToList();
_askRepository.Delete(askToDelete);
_askRepository.Save();
}
catch
{
return false;
}
return true;
}
And I use a repository to Delete the Entity:
public class Repository<T> : IRepository<T> where T : class
{
protected DbContext _dataContext;
protected DbSet<T> _dbSet;
public Repository(DbContext context)
{
_dataContext = context;
_dbSet = _dataContext.Set<T>();
}
public T NewEntityInstance()
{
return _dbSet.Create();
}
public void Delete(T entity)
{
if (_dataContext.Entry(entity).State == EntityState.Detached)
{
_dbSet.Attach(entity);
}
_dbSet.Remove(entity);
}
public virtual void Delete(object id)
{
T entity = _dbSet.Find(id);
Delete(entity);
}
public T GetById(int id)
{
return _dbSet.Find(id);
}
public virtual IQueryable<T> Query
{
get
{
return _dbSet.AsNoTracking(); <------ SOURCE OF THE PROBLEM - I HAD TO REMOVE THE ASNOTRACKING OPTION TO SOLVE THE PROBLEM
}
}
}
Error I get now:
"The DELETE statement conflicted with the REFERENCE constraint "FK_Messages_Asks". The conflict occurred in database "Heelp", table "dbo.Messages", column 'Ask_Id'.
Thanks
If your relationship is optional (that is, the foreign key from MessageAsk table to Ask table allows NULL values), you can do it this way:
using (var context = new MyContext())
{
var askToDelete = context.Asks.Single(a => a.Id == askToDeleteId);
var relatedMessageAsks = context.MessageAsks
.Where(m => m.Ask.Id == askToDeleteId)
.ToList();
// or just: context.MessageAsks.Where(m => m.Ask.Id == askToDeleteId).Load();
context.Asks.Remove(askToDelete);
// or DeleteObject if you use ObjectContext
context.SaveChanges();
}
(or context.Messages.OfType<MessageAsk>()... instead of context.MessageAsks... if you don't have a set for the derived type in your context)
You don't need to set the MessageAsk.Ask property to null explicitly here. EF will do that automatically when the askToDelete is removed and update the MessageAsk with FK = NULL in the database.
It does not work if the relationship is required (no NULLs for the FK are allowed) as you would violate a referential foreign key constraint in the database when the principal (askToDelete) would be deleted. In that case you need to assign the relatedMessageAsks to another Ask before you delete the askToDelete.
I have a one-to-many relationship between my table Case and my other table CaseReplies. I'm using EF Code First and now wants to delete a CaseReply from a Case object, however it seems impossible to do such thing because it just tries to remove the CaseId from the specific CaseReply record and not the record itself..
I've tried to set cascade delete/update at database level without luck...
short: Case just removes the relationship between itself and the CaseReply.. it does not delete the CaseReply.
My code:
// Case.cs (Case Object)
public class Case
{
[Key]
public int Id { get; set; }
public string Topic { get; set; }
public string Message { get; set; }
public DateTime Date { get; set; }
public Guid UserId { get; set; }
public virtual User User { get; set; }
public virtual ICollection<CaseReply> Replies { get; set; }
}
// CaseReply.cs (CaseReply Object)
public class CaseReply
{
[Key]
public int Id { get; set; }
public string Message { get; set; }
public DateTime Date { get; set; }
public int CaseId { get; set; }
public Guid UserId { get; set; }
public virtual User User { get; set; }
public virtual Case Case { get; set; }
}
// RepositoryBase.cs
public class RepositoryBase<T> : IRepository<T> where T : class
{
public IDbContext Context { get; private set; }
public IDbSet<T> ObjectSet { get; private set; }
public RepositoryBase(IDbContext context)
{
Contract.Requires(context != null);
Context = context;
if (context != null)
{
ObjectSet = Context.CreateDbSet<T>();
if (ObjectSet == null)
{
throw new InvalidOperationException();
}
}
}
public IRepository<T> Remove(T entity)
{
ObjectSet.Remove(entity);
return this;
}
public IRepository<T> SaveChanges()
{
Context.SaveChanges();
return this;
}
}
// CaseRepository.cs
public class CaseRepository : RepositoryBase<Case>, ICaseRepository
{
public CaseRepository(IDbContext context)
: base(context)
{
Contract.Requires(context != null);
}
public bool RemoveCaseReplyFromCase(int caseId, int caseReplyId)
{
Case caseToRemoveReplyFrom = ObjectSet.Include(x => x.Replies).FirstOrDefault(x => x.Id == caseId);
var delete = caseToRemoveReplyFrom.Replies.FirstOrDefault(x => x.Id == caseReplyId);
caseToRemoveReplyFrom.Replies.Remove(delete);
return Context.SaveChanges() >= 1;
}
}
Thanks in advance.
If you want EF to remove CaseReply from DB when you remove it from parent's object collection, you have to use Identifying relations. It means that your CaseReply must have composite primary key based on Id and CaseId columns.
I believe you are using EF CT4? I had the same problem too and hated it. I think your code is totally fine.
Since your association is one to many, the only solution is to setting cascading delete at database side. <http://blogs.msdn.com/b/alexj/archive/2009/08/19/tip-33-how-cascade-delete-really-works-in-ef.aspx>
The above solution works for deleting orphans on deleting their parent record. I hope it will solve your issue too.
You have to remove from context.Replies if you want to delete it in db.
What you are doing right now is removing it from case's replies collection which removes the association. So you have to call:
context.Replies.Remove(delete)