I'm trying to update existing entities in my database.
I want to add a m:n relationship.
This is how I try to do it:
Get all artists from database
Identify artists that are not yet in the database
Save new artists
Get all genres
Insert ArtistGenre-relationship for all artists
Update artists
public void SyncArtists(ICollection<FullArtistWrapper> fullArtists)
{
using (var unitOfWork = UnitOfWorkFactory.CreateUnitOfWork())
{
var dbArtists = unitOfWork.Repository<IArtistRepository>().GetArtists().ToList();
var newArtists = fullArtists.Where(s => dbArtists.All(d => d.ArtistId != s.Id)).ToList();
var artistsToInsert = newArtists.Select(artist =>
new Artist
{
ArtistId = artist.Id,
Name = artist.Name
}).ToList();
unitOfWork.Repository<IArtistRepository>().InsertEntities(artistsToInsert);
unitOfWork.Commit();
// dbArtists.AddRange(artistsToInsert);
var allArtists = unitOfWork.Repository<IArtistRepository>().GetArtists().ToList();
var allGenres = unitOfWork.Repository<IGenreRepository>().GetGenres();
foreach (var artist in allArtists)
{
var fullArtist = fullArtists.Single(f => f.Id == artist.ArtistId);
var assignedDbGenres = allGenres.Where(g => fullArtist.Genres.Any(f => f == g.Name));
artist.Genres.AddRange(assignedDbGenres);
}
unitOfWork.Repository<IArtistRepository>().UpdateEntities(allArtists);
unitOfWork.Commit();
}
}
My Entities look like this:
public class Artist : PersistenceEntity
{
public Artist()
{
Genres = new List<Genre>();
}
[Key]
public string ArtistId { get; set; }
public string Name { get; set; }
public virtual ICollection<Genre> Genres { get; set; }
}
public class Genre : PersistenceEntity
{
[Key]
public int GenreId { get; set; }
public string Name { get; set; }
public virtual ICollection<Artist> Artist { get; set; }
}
The problem is the following:
I do have genre in the database (they get saved in an earlier step)
The artists are saved correctly, all artists are in the database
But the relationships don't get updated
Why is this the case?
My UpdateEntities method looks like this:
public void UpdateEntities<TPersistentEntity>(ICollection<TPersistentEntity> persistentEntitiesToBeUpdated) where TPersistentEntity : PersistenceEntity
{
persistentEntitiesToBeUpdated.ForEach(UpdateAndSave);
}
public void Update(PersistenceEntity entity)
{
Context.Entry(entity).State = EntityState.Modified;
}
public void UpdateAndSave(PersistenceEntity entity)
{
Update(entity);
Context.SaveChanges();
}
Why aren't my relationships inserted?
Thanks in advance
What is not very well visible in your code is that you cache the artists in a private member in your class. This means that EF will now know about these entities. The proper implementation of the unit of work pattern is the following:
Load an entity from EF
Modify this instance
Save the instance
If you manage instances outside, EF will have a different instance associated, and it will not find any changes. the code you write marking the entity manually as modified is not needed, as EF does its own change tracking. Even if you put the state manually to modified, EF will just rescan the entity it has associated (not equal to the one you have actually modified) and will not find any changes.
The main question is why you cache these artists outside of EF. This is not good practice, in general you have more than one server / client in use, and the DB can be updated by anyone. These cached instances would never know about this. I would completely get rid of such a situation and reload entities always from EF. Then you ensure that you always get the latest state no matter who has updated the DB.
Related
I'm currently learning to work with Entity Framework by using it in a new MVC application. I had some struggles setting up a Many-To-Many relation between 2 tables, but got it working for showing data. However, when updating an entity, EF inserts duplicate records for the linked table.
My setup is as follows:
With BusinessUnit presenting an entity that groups WindowsLogins and WindowsGroups together for usage through the BusinessUnit. BusinessUnitWindowsLogin and BusinessUnitWindowsGroup serve as junction tables for the Many-To-Many relationship.
The entities are defined in C# as follows:
BusinessUnit
public class BusinessUnit
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Name { get; set; }
public bool IsPersonal { get; set; }
public virtual IList<WindowsGroup> WindowsGroups { get; set; }
public virtual IList<WindowsLogin> WindowsLogins { get; set; }
}
WindowsGroup (WindowsLogin being similar)
public class WindowsGroup
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Domain { get; set; }
public string Name { get; set; }
public ICollection<BusinessUnit> BusinessUnits { get; set; }
}
I wrote this inside the OnModelCreating method of my DbContext to register the junction tables and foreign keys:
modelBuilder.Entity<BusinessUnit>()
.HasMany(businessUnit => businessUnit.WindowsGroups)
.WithMany(windowsGroup => windowsGroup.BusinessUnits)
.Map(mc =>
{
mc.MapLeftKey("BusinessUnitId");
mc.MapRightKey("WindowsGroupId");
mc.ToTable("BusinessUnitWindowsGroup", "Config");
});
modelBuilder.Entity<BusinessUnit>()
.HasMany(businessUnit => businessUnit.WindowsLogins)
.WithMany(windowsLogin => windowsLogin.BusinessUnits)
.Map(mc =>
{
mc.MapLeftKey("BusinessUnitId");
mc.MapRightKey("WindowsLoginId");
mc.ToTable("BusinessUnitWindowsLogin", "Config");
});
I wrote my update like this:
public void Update(BusinessUnit businessUnit)
{
var oldBusinessUnit = _unitOfWork.BusinessUnits.GetById(businessUnit.Id);
oldBusinessUnit.Name = businessUnit.Name;
oldBusinessUnit.WindowsGroups.Clear();
oldBusinessUnit.WindowsLogins.Clear();
oldBusinessUnit.WindowsGroups = businessUnit.WindowsGroups;
oldBusinessUnit.WindowsLogins = businessUnit.WindowsLogins;
_unitOfWork.Complete();
}
I had to clear both lists of WindowsGroups and WindowsLogins to correctly update the junction table, which now works. But as soon as I assign the new list of WindowsGroups or WindowsLogins, duplicate WindowsGroups or WindowsLogins are inserted by Entity Framework. The junction table is updated with the new Id's, so it looks correct in the application, but it's wrong in the database.
I'm open for any suggestions and feedback. Thank you in advance!
Reading a bit more into the change tracking that Entity Framework does to the entities, I figured out a way to solve my problem. I altered my update statement as follows:
public void Update(BusinessUnit businessUnit)
{
var oldBusinessUnit = _unitOfWork.BusinessUnits.GetById(businessUnit.Id);
oldBusinessUnit.Name = businessUnit.Name;
oldBusinessUnit.WindowsGroups.Clear();
oldBusinessUnit.WindowsLogins.Clear();
if (businessUnit.WindowsGroups != null)
{
var windowsGroupIds = businessUnit.WindowsGroups.Select(x => x.Id).ToList();
foreach (var winGroup in _unitOfWork.WindowsGroups.Find(winGroup => windowsGroupIds.Contains(winGroup.Id)).ToList())
{
oldBusinessUnit.WindowsGroups.Add(_unitOfWork.WindowsGroups.GetById(winGroup.Id));
}
}
if (businessUnit.WindowsLogins != null)
{
var windowsLoginIds = businessUnit.WindowsLogins.Select(x => x.Id).ToList();
foreach (var winLogin in _unitOfWork.WindowsLogins.Find(winLogin => windowsLoginIds.Contains(winLogin.Id)).ToList())
{
oldBusinessUnit.WindowsLogins.Add(_unitOfWork.WindowsLogins.GetById(winLogin.Id));
}
}
_unitOfWork.Complete();
}
I'll first check if both list actually contain something. Then I loop over the list that I got back from my view and use the Id's to fetch the entities directly from my context (via UoW). I don't know if it's the best solution, but it works.
I save some data to my database and Im using code first. Everything works fine until I want to change an entity. For example: I add four persons to the database, and when I updating one of them the database adds duplicates of all persons.
UPDATE
The problem will be in this code:
Conversation newConv = new Conversation { Name = chatPerson.Name };
newConv.Members.Add(person);
newConv.Members.Add(chatPerson);
Conversation has a list of Members and when Im trying to add Members to the list then I will get my duplicate in the database.
OLD POST
The first thing I do is to load one person:
var person = repo.GetWholePerson(3);
Code that returns the person:
public Person GetWholePerson(int id)
{
return _ctx.Persons
.Include(a => a.Colleagues)
.Include(a => a.Conversations)
.FirstOrDefault(p => p.Id == id);
}
Here is the code that saves to database:
using (Repository<Person> repo = new Repository<Person>())
{
var per = repo.Get(person.Id);
var chatPer = repo.Get(chatPerson.Id);
per.Conversations.Add(newConv);
chatPer.Conversations.Add(newConv);
repo.Update(per);
repo.Update(chatPer);
}
And here is the code in the Repository:
public void Update(T entity)
{
dbSet.Attach(entity);
_ctx.Entry(entity).State = EntityState.Modified;
_ctx.SaveChanges();
}
Person class:
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public byte[] Image { get; set; }
public virtual ICollection<Conversation> Conversations { get; set; }
public virtual ICollection<Person> Colleagues { get; set; }
public virtual ICollection<Person> Test { get; set; }
public Person()
{
Conversations = new List<Conversation>();
Colleagues = new List<Person>();
Test = new List<Person>();
}
}
Conversation class:
public class Conversation
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Message> Messages { get; set; }
public virtual ICollection<Person> Members { get; set; }
public Conversation()
{
Messages = new List<Message>();
Members = new List<Person>();
}
}
Everything works fine! But when I connecting to my database there are four new persons. =(
Don't call the Attach method, it will mark the entity in the Unchanged state, which means it has not changed since it was attached to the context. Objects that you are attaching are assumed to exist in the database. Attach() is really useful for updating entities in a scenario like this:
using (var context = new MyDbContext())
{
context.Attach(person);
person.Name = "Bob";
context.SaveChanges();
}
If you have an entity that you know already exists in the database and you want to save all the changes that you did before, just call the Entry method modifying the entity state:
public void Update(T entity)
{
_ctx.Entry(entity).State = EntityState.Modified;
_ctx.SaveChanges();
}
When you change the state to Modified, all the properties of the entity will be marked as modified and all the property values will be sent to the database when SaveChanges is called.
In fact, if you are using the same instance of repo to get and modify your entity, I think you don't even need to call the Entry method, just call the SaveChanges method.
If you want to know more how work these methods, take a look this link.
Using Web API 2 and EF 6.1 code first.
I am trying to add a new Template (see model) which has relationships to already existing TimePeriods and Stations.
public class Template
{
public int TemplateID { get; set; }
public string Name { get; set; }
public List<TimePeriod> TimePeriods { get; set; }
public List<Station> Stations { get; set; }
}
public class Station
{
public int StationID { get; set; }
public string Name { get; set; }
public List<Template> Templates { get; set; }
}
public class TimePeriod
{
public int TimePeriodID { get; set; }
public TimeSpan From { get; set; }
public TimeSpan To { get; set; }
public List<Template> Templates { get; set; }
}
The new template object contains a list of Station and a list of TimePeriod with correct IDs/primary keys. I hoped that EF would recognize that the related entities were already existing by looking att their primary keys but it seems not. Instead, all the related entities are added again resulting in duplicates.
private SchedulingContext db = new SchedulingContext();
[ResponseType(typeof(Template))]
public IHttpActionResult PostTemplate(Template template)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
db.Templates.Add(template);
db.SaveChanges();
return CreatedAtRoute("DefaultApi", new { id = template.TemplateID }, template);
}
Does this have something to do with me using a new context? If so, how can I prevent this behavior?
Solution thanks to Evandro:
public void PostTemplate(Template template)
{
db.Templates.Add(template);
foreach (var item in template.Stations)
{
db.Entry<Station>(item).State = EntityState.Unchanged;
}
foreach (var item in template.TimePeriods)
{
db.Entry<TimePeriod>(item).State = EntityState.Unchanged;
}
db.SaveChanges();
}
Lorentz, this is the default behavior of the Entity Framework. You have to explicitly define your custom behavior based on what the system should do.
First, you can access the State of your entities within the context using hte following example:
EntityState state = db.Entry<Station>(station).State;
You can print the states and then see what EF is doing.
Now, when you first receive the instance of Template, its state on the context it will be Detached.
After you add it to the context, the state will change to Added. This will apply for Template(s), Station(s) and TimePeriod(s).
Even if you set the Id (Primary Key) correctly, EF will discard the ids, create new Ids and add new lines to the tables, which is what is happening with your program. That is what I managed to reproduce in my code.
You have to define the EntityState for each entity so the EF will know that it should not persist new items. Below are the possible values at EF 6.1:
// This is probably what you are looking for
db.Entry<Station>(station).State = EntityState.Unchanged;
// This one maybe, if you are receiving updated values for the State
db.Entry<Station>(station).State = EntityState.Modified;
// Others that may apply for other scenarios
db.Entry<Station>(station).State = EntityState.Detached;
db.Entry<Station>(station).State = EntityState.Added;
db.Entry<Station>(station).State = EntityState.Deleted;
Since Template have multiple itens for Station and TimePeriod you will have to iterate over them and set each one as "Unchanged" I assume, or "Modified".
Let me know if it works.
EDIT: The solution I selected probably wasn't the best, but it definitely worked. I'll be going through my code over the next week (once this project is done) and I'll update my question when I understand what went wrong.
I'm using the ASP.NET MVC 4 framework with Entity 5. Here's some code:
The class to be instantiated and saved (fresh) in the database:
public class ClassCancellation
{
[Key]
public int Id { get; set; }
public Faculty Professor { get; set; }
public DateTime CancelledOn { get; set; }
public Course Course { get; set; }
[Required]
public ClassDate ClassCancelled { get; set; }
public Message CancellationMessage { get; set; }
[Required]
public List<Student> Students { get; set; }
}
It's mapped from the viewmodel called CancellationFull (with AutoMapper):
public class CancellationForList
{
[Key]
public int Id { get; set; }
public CourseForList Course { get; set; }
public ClassDateForList ClassCancelled { get; set; }
}
public class CancellationFull : CancellationForList
{
public CancellationFull()
{
this.Students = new List<StudentForList>();
}
public FacultyForList Professor { get; set; }
public MessageForList CancellationMessage { get; set; }
public DateTime CancelledOn { get; set; }
public List<StudentForList> Students { get; set; }
}
This is the repo method that turns a CancellationFull into a ClassCancellation and then saves it to the database:
public CancellationFull createClassCancellation(CancellationFull c)
{
ClassCancellation newCancellation = Mapper.Map<ClassCancellation>(c);
dc.ClassCancellations.Add(newCancellation);
dc.SaveChanges();
return Mapper.Map<CancellationFull>(dc.ClassCancellations.FirstOrDefault(cc => cc.Id == newCancellation.Id));
}
Why, for the love of god why, does the database create new objects for Faculty and Course when the Id (primary key) of each's existing entity counterpart is provided? It might also be doing the same with Student objects but I haven't looked that closely.
Before the ClassCancellation instance is saved to the database the debugger shows that it's attributes Professor of type Faculty and Course of type Course have the correct primary key - that is, the primary key of the already existing entities of those types that I'm trying to update with a reference to the new ClassCancellation object.
Driving me nuts. Feel free to ask for clarification!
EDIT:
Here's the logic where the CancellationFull viewmodel is constructed from form data and viewmodels about existing objects retrieved from their respective repos:
newCancellation = new CancellationFull();
newCancellation.CancelledOn = DateTime.Now;
newCancellation.ClassCancelled = repoClass.getClassDateForListById(Int32.Parse(classIds[i]));
newCancellation.Course = repoCourse.getForList(newCancellation.ClassCancelled.Course.Id);
newCancellation.CancellationMessage = repoMessage.getMessageForList(newMessage.Id);
newCancellation.Professor = repoFac.getFacultyForList((int)Session["facId"]);
var students = repoStudent.getStudentsForListByCourse(newCancellation.Course.Id);
foreach ( var student in students )
{
newCancellation.Students.Add(student);
}
repoCancellation.createClassCancellation(newCancellation);
Here's an example of one of those repo methods (the rest are very similar):
public CourseForList getForList(int? id)
{
return Mapper.Map<CourseForList>(dc.Courses.FirstOrDefault(c => c.Id == id));
}
What I find the easiest solution is when updating a model, clear any related entities, then re add them.
ie:
newCancellation.Students.Clear();
foreach ( var student in students )
{
newCancellation.Students.Add(student);
}
Try using Attach() instead of Add()
dc.ClassCancellations.Attach(newCancellation);
dc.SaveChanges();
Add() is used for new objects that do not already exist in the database. Attach() is used for creating relationships to entities that already exist in the database.
EDIT
Without seeing your code, the best solution I can recommend to attach is to create a 'stub' instance and then attach that to your newCancellation:
var existingCourse = new Course{ Id = newCancellation.ClassCancelled.Course.Id };
db.Courses.Attach(existingCourse);
newCancellation.Course = existingCourse;
The problem is that you have multiple contexts, or units of work. When you add the newCancellation to the dc context, it also adds any related entity in the object graph that is not tracked in the dc context. I think your best option is:
dc.ClassCancellations.Add(newCancellation);
dc.Entry(newCancellation.Course).State = EntityState.Unchanged;
dc.Entry(newCancellation.Faculty).State = EntityState.Unchanged;
See Julie Lerman's article on this issue for an explanation and other options.
In my opinion, EF should recognize entities that have autonumbered keys and not insert them if the key is assigned.
I am encountered an error that I am not familier with. I tried to google with no success.
I wrote the following query where I am having this error.
The entity or complex type 'MyWebProject.Models.UserDetail' cannot be constructed in a LINQ to Entities query.
The query:
UsersContext db = new UsersContext();
var userdata = (from k in db.UserDetails
where k.UserId == WebSecurity.CurrentUserId
select new UserDetail()
{
FullName = k.FullName,
Email = k.Email,
About = k.About,
Link = k.Link,
UserSchool = new School()
{
SchoolId = k.UserSchool.SchoolId,
SchoolName = k.UserSchool.SchoolName
},
UserCourse = new Course()
{
CourseId=k.UserCourse.CourseId,
CourseName=k.UserCourse.CourseName
},
Country=k.Country
}).FirstOrDefault();
Class:
public class UserDetail
{
public int Id { get; set; }
public int UserId { get; set; }
public string FullName { get; set; }
public string Link { get; set; }
public bool? Verified { get; set; }
public string Email { get; set; }
public string About { get; set; }
public School UserSchool { get; set; }
public Course UserCourse { get; set; }
public string Country { get; set; }
}
public class School
{
public int SchoolId { get; set; }
public string SchoolName { get; set; }
public string Country { get; set; }
}
public class Course
{
public int CourseId { get; set; }
public string CourseName { get; set; }
public School School { get; set; }
}
Any idea what went wrong??
It looks like it is due to how you are creating the complex properties School and Course in the middle of the query. It would be better to select the User (remove the select transformation), then use navigation properties to access those objects instead of building them manually. The navigation are meant for this as long as you have the proper relations built with foreign keys.
UsersContext db = new UsersContext();
var userdata = (from k in db.UserDetails
where k.UserId == WebSecurity.CurrentUserId})
.FirstOrDefault();
// access navigation properties which will perform the joins on your behalf
// this also provides for lazy loading which would make it more effecient. (it wont load the school object until you need to access it)
userdata.School
userdata.Course
MSDN article about navigation properties: http://msdn.microsoft.com/en-us/library/vstudio/bb738520(v=vs.100).aspx
This should give you what you want. It will load your objects as part of the query (and not rely on lazy loading).
UsersContext db = new UsersContext();
var userdata = db.UserDetails.Include(x => x.UserSchool)
.Include(x => x.UserCourse)
.Include(x => x.Country)
.Where(x => x.UserId == WebSecurity.CurrentUserId)
.FirstOrDefault();
I think it's because your entity has the same name of the object you're trying to create. Try renaming the object you want to return back. If you want to return the same type as your entity try the eager loading with .Include("relationshipname") feature.
A great answer from #Yakimych is given below.
You cannot (and should not be able to) project onto a mapped entity. You can, however, project onto an annonymous type or onto a DTO:
public class ProductDTO
{
public string Name { get; set; }
// Other field you may need from the Product entity
}
And your method will return a List of DTO's.
public List<ProductDTO> GetProducts(int categoryID)
{
return (from p in db.Products
where p.CategoryID == categoryID
select new ProductDTO { Name = p.Name }).ToList();
}
Mapped entities in EF basically represent database tables. If you project onto a mapped entity, what you basically do is partially load an entity, which is not a valid state. EF won't have any clue how to e.g. handle an update of such an entity in the future (the default behaviour would be probably overwriting the non-loaded fields with nulls or whatever you'll have in your object). This would be a dangerous operation, since you would risk losing some of your data in the DB, therefore it is not allowed to partially load entities (or project onto mapped entities) in EF.
For more details please go to the following link:
The entity cannot be constructed in a LINQ to Entities query