My repository is being exposed through UnitOfWork and Add method have only following code:
public void Add(Employee emp)
{
context.add(emp);
}
Then, from UnitOfWork, i am calling the Add() method with this code:
this.UnitOfWork.EmployeeRepository.Create(emp);
this.UnitOfWork.Commit(); // This calls SaveChanges() EF method
Now the issue is how can i obtain the Id of newly created object here?
Normally if your Id is Identity, when you save changes the Id will be automatically filled.
context.Employees.Add(emp);
context.SaveChanges();
var newId=emp.Id; //The Id is filled after save changes
A second variant could be using the GetDatabaseValues method:
context.Entry(emp).GetDatabaseValues();
var id = emp.Id;
After SaveChanges the employee entity wil have the new id assigned. Find a way to return it to higher level methods.
Related
Am currently trying out my hand in .net core and EF core.
I have the following code to update the data
protected void Update(T entity, bool saveChanges = true)
{
_context.Set<T>().Attach(entity);
_context.Entry(entity).State = EntityState.Modified;
if (saveChanges) _context.SaveChanges();
}
The class I am updating
public partial class Blog
{
public Blog()
{
Post = new HashSet<Post>();
}
public int BlogId { get; set; }
public string Url { get; set; }
public virtual ICollection<Post> Post { get; set; }
}
When I try to update any entry the first time, it is successful. The second time I update the same entry, I get the below error:
System.InvalidOperationException: 'The instance of entity type 'Blog' cannot be tracked because another instance of this type with the same key is already being tracked. When adding new entities, for most key types a unique temporary key value will be created if no key is set (i.e. if the key property is assigned the default value for its type). If you are explicitly setting key values for new entities, ensure they do not collide with existing entities or temporary values generated for other new entities. When attaching existing entities, ensure that only one entity instance with a given key value is attached to the context.'
The same occurs for each entry, successful for the first time and fails the second time.
Additional Information:
I get all the data using the below code:
protected IEnumerable<T> GetAll()
{
return _context.Set<T>().AsNoTracking().AsEnumerable();
}
Display the above as a table in the view. DTO's are being used to communicate between the data and web layer.
The context is registered in startup as below
services.AddDbContext<BloggingContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
My question is why there is an error during the second update and how to resolve it. Thanks.
Your Service Provider is static and so it is a singleton in fact.
As suggested here
github.com/aspnet/EntityFramework/issues/2652
that exception means that you are trying to attach two entity instances that have the same key to a context. This occurs when a singleton instance of a repository is injected at startup.
I d suggest to change your generic Repository from an abstract class to an interface , and inject the proper repositories during the Startup:
services.AddScoped<IRepository<Blog>, BlogRepository>();
services.AddScoped<IRepository<Post>, PostRepository>();
instead of your static ServiceProvider which actually provides a singleton implementation of your BlogRepository
I have an entity class Timecard, I get this entity from this method:
public Timecard GetTimeCardForPerson(long timecardId)
{
return timecardContext.First(item => item.TimeCardId = timeCardId);
}
timecardContext is of type TimecardContext: DbContext.
I later make a change to the Timecard entity, the Timecard entity has a property:
public virtual ICollection<TimecardRow> TimeCardRows { get; set; }
which is initialized, in Timecard's constructor to a HashSet like so:
this.TimeCardRows = new HashSet<TimecardRow>();
I add a child TimecardRow I then call another method and this is its exact implementation and pass it the same Timecard instance as is returned from GetTimeCardForPerson:
public void SaveTimecard(Timecard timeCard)
{
timecardContext.Entry(timeCard).State = timeCard.TimecardId == 0
? EntityState.Added
: EntityState.Unchanged;
timecardContext.SaveChanges();
}
The passed in Timecard timeCard argument is not attached to the timecardContext and has a TimecardId > 0.
I am surprised that my new TimecardRow saves successfully as Entry(timeCard.State) is set to EntityState.Unchanged.
The EntityState.Unchanged tells my timecardContext that there is nothing to change, does it not? But all the same, the TimecardRow I added is inserted into the database when the SaveTimecard method is called.
The EntityState.Unchanged tells the context that nothing has changed for the Timecard entity.
The TimecardRow is a separate entity which EF will track separately so a call to SaveChanges will insert that entity.
The above assumes that the Timecard is already attached when passed to the Save method (which it will be if it's the same instance returned from the GetTimeCardForPerson method).
If the Id check in the Save method is there to cope with both detached and attached entities, would it be better to leave the state alone unless it is an id of 0?
I've got an aggregate for a specific type of entity which is stored in a collection inside the aggregate. Now I'd like to add a new entry of that type and update the aggregate afterwards, however Entity Framework never updates anything!
Model
public class MyAggregate {
protected ICollection<MyEntity> AggregateStorage { get; set; }
public void AddEntity(MyEntity entity) {
// some validation
AggregateStorage.Add(entity);
}
}
API Controller
[UnitOfWork, HttpPost]
public void UpdateMyEntity(int aggregateId, MyEntityDto dto) {
var aggregate = _aggregateRepository.Find(aggregateId);
aggregate.AddEntity(...// some mapping of the dto).
_aggregateRepository.Update(aggregate);
}
EF Update
EntitySet.Attach(aggregate);
Context.Entry(aggregate).State = EntityState.Modified;
(Please note that there's an unit of work interceptor on the API action who fires DbContext.SaveChanges() after successful execution of the method.)
Funny thing is, the update never get's executed by EF. I've added a log interceptor to the DbContext to see what's going on sql-wise and while everything else works fine, an update statement never occurs.
According to this answer in detached scenario (either aggregate is not loaded by EF or it is loaded by different context instance) you must attach the aggregate to context instance and tell it exactly what did you changed, set state for every entity and independent association in object graph.
You must either use eager loading and load all data together at the beginning and
instead of changing the state of aggregate, change the state of entities:
foreach(var entity in aggregate.AggregateStorage)
{
if(entity.Id == 0)
Context.Entry(entity).State = EntityState.Added;
}
I am working on an asp.net mvc 5 web project and I have created a Repository Model class to interact with my database , and I am calling my repository from my action method. For example inside my Post Edit action method I have the following code:-
Repository repository = new Repository();
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(SecurityRole Role)
{
try
{
if (ModelState.IsValid)
{
repository.InsertOrUpdateRole(Role);
repository.Save();
return RedirectToAction("Index");
}
}}
And here is the repository model clas:-
public class Repository
{
private TEntities tms = new TEntities();
public void InsertOrUpdateRole(SecurityRole role)
{
role.Name = role.Name.Trim();
if (role.SecurityRoleID == default(int))
{
// New entity
//Code goes here
else
{
t.Entry(role).State = EntityState.Modified;
}
}
So my question is when I pass the object from my action method to my repository, how will Entity Framework treat the object ; will it create another copy inside the repository , or it will be tracking the same object ?. And if entity framework will be working on the same object inside the action method and inside the repository method in the session , so how entity framework keeps tracking the object ?
Second question , what is the best approach to follow; to pass the whole object from action method to repository (as I am currently doing), or instead of that to only pass the object id, and to retrieve the object again inside the repository model something as follow:-
repository.InsertOrUpdateRole(Role.RoleID);
and inside the repository
public void InsertOrUpdateRole(int id)
{
var SecurityRole = t.SecurityRoles.SingleOrDefault(a => a.SecurityRoleID == role.SecurityRoleID);
}
In C# objects are passed by reference. When you save a new role in your ActionResult you notice it has gotten an Id after the InsertOrUpdateRole call. EF is starting to track changes as soon something is attached to the context. So your first approach works fine (I use it as well). Make sure the model is passed to the InsertOrUpdateRole function with all it's values set, EF will overwrite the entire record so if you forget a property in your view it will become an empty value in the database. If you only want to update a few properties of the model use your second approach.
I use EF as ORM, but I have problem with simple add object with foreign keys. I execute this code below to add new article to DB.
dataLayer = new CmsDataLayer();
var newArticle = new Article();
newArticle.Author = AuthorService.CurrentAuthor; //id =8
dataLayer.Articles.Insert(newArticle);
There is CmsDataLayer.ICmsRepository is repository pattern (simple CRUD operation)
class CmsDataLayer
{
public ICmsRepository<Author> Authors = new MsSqlServerCmsRepository<Author>();
}
In method insert I do this
class MsSqlServerCmsRepository
{
private DbSet<T> dataSet;
public MsSqlServerCmsRepository()
{
dataSet = dataContext.Set<T>();
}
public void Insert(T entity)
{
this.dataSet.Add(entity);
this.dataContext.SaveChanges(); //<--
}
After this operation newArticle.Author gets new value of Author ID, before SaveChanges() entity it has Author with id=8, after Author has id=14.
I don't understand why EF change AuthorId after save operation, author with id=8 exists w DB?
I don't know which data context the current author is loaded from in the author service, but I think you need to get it from the same context or attach it. Otherwise it will see it as a new object and insert it again. Alternatively, rather than setting the navigation property, you can leave it sert to null and set the key field .AuthorId instead.
Is the author loaded from the same DB context (or attached to it)?
If not set author ID instead of navigation property.
Also save changes isn't supposed to be in the add method, it is supposed called before closing the DB context.
Try to apply this modification:
public void InsertArticle(Article entity)
{
entity.Author = this.dataContext.Authors.Find(entity.AuthorEntityIdPropertyName);
this.dataSet.Add(entity);
this.dataContext.SaveChanges(); //<--
}
Looking your code I think the AuthorService.CurrentAuthor is not tracked by the MsSqlServerCmsRepository's dataContext. So that it will insert it wiht the next incremented id.