I have a Survey that contains questions and also which users can/will participate in the survey.
like so
public virtual ICollection<User> ParticipatingUsers { get; set; }
public virtual ICollection<Question> SpecificQuestions { get; set; }
However, due to the ajaxy solution I create the questions first and then simply send in the ID of my created question with the survey data. So all I need to do is change the sortingIndex of the question and then add a reference to it in my Survey.
When it comes to users they belong to a Company entity and I only want to reference them from the survey not own them.
But currently I get all the id's for questions and users in my action method (.net mvc) and so currently I load all questions and users and attach them to my survey entity before sending the survey to the repository.
But when my Repository calls Add on dbset it clones the user and question data instead of simply referencing existing data.
I am lost, I have solved this exact problem for a normal navigation property by adding [Foreignkey] but i don't know how that would work with ICollection
For completeness
Here is my action method recieving the data
[HttpPost]
[FlexAuthorize(Roles = "RootAdmin")]
public ActionResult SaveSurvey(EditSurveyViewModel editModel)
{
if (!ModelState.IsValid)
{
//We dont bother to send this in so we need to fetch the list again
editModel.CompanyList = _companyRepository.GetAll();
List<string> deletionList = new List<string>();
//We clear out all questions from the state as we have custom logic to rerender them with the correct values
foreach (var modelstateItem in ModelState)
{
if (modelstateItem.Key.StartsWith("Questions"))
{
deletionList.Add(modelstateItem.Key);
}
}
foreach (string key in deletionList)
{
ModelState.Remove(key);
}
return View("EditSurvey", editModel);
}
List<Question> questionlist = new List<Question>();
int sort = 1;
Question q;
//We have questions sent in from the ui/client
if (editModel.Questions != null)
{
//Go trough each questions sent in
foreach (var question in editModel.Questions)
{
//if it's a page break, just assign our new question the sent in one and set sort index
if (question.IsPageBreak)
{
q = question;
q.SortIndex = sort;
}
else
{
//It's a question and all questions are already created with ajax from the client
//So we simply find the question and then set sort index and tie it to our survey
q = _questionRepository.GetById(question.Id);
q.SortIndex = sort;
}
questionlist.Add(q);
sort++;
}
}
//assign the new sorted questions to our Survey
editModel.Item.SpecificQuestions = questionlist;
List<User> userlist = new List<User>();
foreach (int id in editModel.SelectedUsers)
{
userlist.Add(_userRepository.GetById(id));
}
editModel.Item.ParticipatingUsers = userlist.ToList();
_surveyRepository.SaveSurveyBindAndSortQuestionsLinkUsers(editModel.Item);
return RedirectToAction("Index");
}
Here is the viewmodel the method gets sent in
public class EditSurveyViewModel
{
public Survey Item { get; set; }
public IEnumerable<Question> Questions { get; set; }
public bool FullyEditable { get; set; }
public IEnumerable<Company> CompanyList { get; set; }
public IEnumerable<int> SelectedUsers { get; set; }
}
Finally here is the repo method (so far i only implemented insert, not update)
public void SaveSurveyBindAndSortQuestionsLinkUsers(Survey item)
{
if (item.Id == 0)
{
Add(item);
}
ActiveContext.SaveChanges();
}
Update/Edit
Moho: You are of course correct, I think to my shame I was testing some things and forgot to reset the method before pasting it in here.
I have updated the action method above.
Slauma: Sorry for lack of details, here comes more.
All my repositories look like this
public class EFSurveyRepository : Repository<Survey>, ISurveyRepository
So they inherit a generic repository and implement an interface
The generic repository (the part we use in code above, looks like this)
public abstract class Repository<T> : IRepository<T> where T : class
{
public EFDbContext ActiveContext { get; private set; }
private readonly IDbSet<T> dbset;
public Repository()
{
this.ActiveContext = new EFDbContext("SurveyConnection");
dbset = ActiveContext.Set<T>();
}
public virtual void Add(T entity)
{
dbset.Add(entity);
}
public virtual T GetById(int id)
{
return dbset.Find(id);
}
I have noticed in the database that my User table (for User entity) now contains a Survey_Id field which i do not want it to have. I want a many-to-many where many surveys can link to many users (the same users) but the users should entity-wise still only belong to a Department in a Company.
Also, right now when I run the code (after I corrected my action method) I get the following error:
An entity object cannot be referenced by multiple instances of IEntityChangeTracker.
No InnerException, only that when i try to add the new survey.
The problem is that you are using separate contexts per repository:
public Repository()
{
this.ActiveContext = new EFDbContext("SurveyConnection");
//...
}
In your POST action you have four repositories in place: _companyRepository, _questionRepository, _userRepository and _surveyRepository. It means you are working with four different contexts, i.e. you load data from different contexts, create relationships between entities that are attached to different contexts and save the data in yet another context.
That's the reason for the entity duplication in the database, for the "multiple instances of IEntityChangeTracker" exception and will be the source for many other problems you might encounter in future.
You must refactor the architecture so that you are using only one and the same context instance ("unit of work") in every repository, for example by injecting it into the constructor instead of creating a new one:
private readonly EFDbContext _activeContext;
private readonly IDbSet<T> _dbset;
public Repository(EFDbContext activeContext)
{
_activeContext = activeContext;
_dbset = activeContext.Set<T>();
}
You build up questionList, set it to editModel.Item.SpecificQuestions, then overwrite the reference by settting that same property to editModel.Questions.ToList(), which is from your view model (i.e.: not loaded via your database context like questionList's question objects) and therefore appears to be new questions to your database context,
editModel.Item.SpecificQuestions = questionlist;
// what is this? why?
editModel.Item.SpecificQuestions = editModel.Questions.ToList();
Edit after question update:
Instead of using questionList and assigning to the questions property of the Survey, simply use the property directly.
Also, do you realize that if you're reusing Question records from the DB for multiple Surveys, you're updating the sort order at the question itself and not simply for that Survey? Each time you save a new survey that reuses questions, other surveys' question ordering will me altered. Looks like you need a relationship entity that will map Questions to Surveys where you can also store the sort order so that each survey can reuse question entities without messing up existing surveys question ordering.
Related
The entity/model has a child object, during ADD (POST) operations where I just want the parent object to be updated in the database, I simply set the child object to null. Parent object adds to database just fine and child object doesn't touch the database.
However, when I do an UPDATE (PUT) and set the same child object to null, the parent object is actually deleted from the database and child object not touched in the database?
Model code:
namespace PROJ.API.Models
{
public partial class Todo
{
public Todo()
{
}
public long TdoId { get; set; }
public string TdoDescription { get; set; } = null!;
public long PtyId { get; set; }
public virtual Priority? Priority { get; set; }
}
public partial class Priority
{
public Priority()
{
}
public long PtyId { get; set; }
public byte PtyLevel { get; set; }
public string PtyDescription { get; set; } = null!;
}
}
Entities code:
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace PROJ.API.Entities
{
public class Todo
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long TdoId { get; set; }
public string TdoDescription { get; set; } = null!;
public long PtyId { get; set; }
[ForeignKey("PtyId")]
public virtual Priority? Priority { get; set; }
}
public class Priority
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long PtyId { get; set; }
public byte PtyLevel { get; set; }
public string PtyDescription { get; set; } = null!;
}
}
Repository code:
public async Task<Todo?> GetTodoAsync(long tdoId)
{
var todo = await _context.Todo.Where(c => c.TdoId == tdoId)
.Include(x => x.Priority)
.FirstOrDefaultAsync();
return todo;
}
Controller code:
[HttpPut()] // UPDATE
public async Task<ActionResult> UpdateTodoAsync(Todo todo)
{
var eTodo = await _myRepository.GetTodoAsync(todo.TdoId);
if (todo.Priority == null || todo.Priority.PtyId == 0)
{
var priority = await _myRepository.GetPriorityAsync(todo.PtyId);
if (priority != null)
{
_mapper.Map(priority, todo.Priority);
}
}
_mapper.Map(todo, eTodo);
await _myRepository.SaveChangesAsync();
return NoContent();
}
My understanding is that setting the child object to null tells EF to NOT perform any operation on it in the database. TODO.PtyId is setup with a FK to PRIORITY.PtyId in the SQL database but I have NOT defined this in context (OnModelCreating) as I don't "think" I need the Fluent API approach here.
Any thoughts on what I'm doing wrong and/or why an UPDATE is actually deleting a record when I set a child object to NULL? As I noted before an ADD using the same null approach works just fine.
A couple things.
In your example and naming convention you should be explicitly nominating your FK either by attribute or fluent declaration. EF's convention is to base FK names on the "type" of the relationship, not the property name. So for instance if you have:
public virtual Priority? Pty { get; set; }
EF will be looking for a FK named Priority_ID or PriorityID, not PtyID. This behaviour may have changed in EF Core, I honestly haven't delved back into whether EF conventions can be trusted to work this out.
Lastly, this is overall a typical example of issues that can come up whenever you mix concerns with entities and use detached entities as view models. It's also outlining an issue with your repository implementation. In your case you are detaching an entity, then when passing it back to the server to update, loading the entity from data state, and using Automapper to copy the values across.
The first problem is that your repository is automatically and unconditionally eager-loading the related entity when in at least this example you don't need or want that related entity. When EF eager loads a relationship and then you set that related entity to #null, the proxy records this action and EF will interpret that as "Remove this relationship". If the related entity is not loaded/associated and left as #null when saving that top-level entity, nothing is removed. Either way you will want to avoid doing something like setting a related entity to #null if you don't want to save changes to them. The solution is either not to load related entities in the first place, ignore mapping across related entities, or marking those entities as Unchanged to avoid persisting changes to them.
Not loading Related entities:
This could either be solved by adding arguments to indicate what should be eager loaded, or considering adopting IQueryable for the repository method:
Argument:
public async Task<Todo?> GetTodoAsync(long tdoId, bool includeRelated = true)
{
var query = _context.Todo.Where(c => c.TdoId == tdoId);
if (includeRelated)
{
query = query.Include(c => c.Pty);
}
return query.FirstOrDefaultAsync();
}
In simple cases this isn't too bad, but in more complex entities it can be a pain, especially if you want to selectively include relatives. This way when you load eToDo from data state, you can tell it to not eager load the Priority. This isn't foolproof as it is still possible that a Priority could be associated if that DbContext instance had previously loaded the Priority associated with that Todo. Tracked entities will be associated even if you don't explicitly eager load them. To be safe this should be combined with the Automapper changes further below.(Excluding mapping changes) This is still a worthwhile change as you can avoid resource/performance costs of unconditionally eager loading every read.
IQueryable:
public IQueryable<Todo> GetTodoById(long tdoId)
{
var query = _context.Todo.Where(c => c.TdoId == tdoId);
return query;
}
IQueryable gives your consumer a lot more flexibility into what it wants to do with regards to data that will be coming back, but it does require a shift in thinking around the unit of work pattern to move the scope of the DbContext out into a Unit of Work so that consumers are responsible for that scope rather than at the individual repository level. The advantages of this approach are that the unit of work (DbContext scope) can be shared across repositories if needed, and with this pattern your consumer has control over things like:
Projection using Select or Count, Any, etc.
async vs. synchronous operations.
Assessing whether or not to eager load related entities.
So as an example with this pattern, the controller or service code would function more like:
[HttpPut()] // UPDATE
public async Task<ActionResult> UpdateTodoAsync(Todo todo)
{
using (var contextScope = _contextScopeFactory.Create())
{
var eTodo = await _myRepository.GetTodoById(todo.TdoId)
.SingleAsync();
_mapper.Map(todo, eTodo);
await contextScope.SaveChangesAsync();
return NoContent();
}
}
contextScope / _contextScopeFactory are a UoW pattern called DbContextScope by Medhi El Gueddari for EF6 which has a number of forks covering EF Core. I like this pattern as it gives the Repository a dependency to a locator to resolve the DbContext from the Scope rather than passing around DbContext instances, giving that scope full control over whether or not SaveChanges() gets committed or not. Leveraging IQueryable enables projection so it can help avoid this issue all-together when used to read data to send to a view to Project to a ViewModel using Automapper's ProjectTo rather than sending Entities to a View which come back to the controller as a deserialized and typically incomplete shell of what they once were.
Excluding mapping changes:
This involves adjusting the mapping you use to exclude copying across changes to the related entity when mapping one Todo across to another. If the mapping ignores Todo.Pty then you can map across just the Todo fields from the one instance to the DB instance and save the DbInstance without change tracking tripping anything changing in Pty or the relationship.
Marking as Unchanged:
Given your repository is managing the scope of the DbContext what you will likely need to do is add a method to isolate changes to just that top-level entity. Since the Repository is scoping the DbContext, this means some form of clunky method since we need to pass the entity to tweak tracking.
// eTodo.Pty = null; don't do
_myRepository.IgnoreRelatedChanges(eTodo);
await _myRepository.SaveChangesAsync();
then...
public void IgnoreRelatedChanges(Todo todo)
{
_context.Entry(todo.Pty).State = EntityState.Unchanged;
}
The trouble with this approach is that it is clumsy and prone to bugs/exceptions.
In any case that should provide you with some options to consider to solve your issue, and possibly consider for updating your repository pattern.
I am new to EF Core 6.0.1, using it with Blazor (WebAssembly), .NET 6.0, and Visual Studio 2022. I am creating a database of internal software projects, including their author(s) and maintainer(s).
I am having trouble getting EF Core to take in a List of Authors / List of Maintainers as part of creating a new SoftwareItem from a webform submission.
SoftwareItem in defined (in part) as follows:
public class SoftwareItem
{
[Key]
public int SoftwareId { get; set; }
public string Name { get; set; }
public string CurrentVersion { get; set; }
public string Status { get; set; }
public List<Author> Authors { get; set; }
public List<Maintainer> Maintainers { get; set;}
[other properties omitted]
}
An Author is defined as follows:
public class Author
{
[Key]
public int AuthorId { get; set; }
public int SoftwareItemId { get; set; }
public int ProgrammerId { get; set; }
public Programmer Programmer { get; set; }
}
Maintainer is identical, except for having a MaintainerId instead of an AuthorId.
Programmer is defined as:
public class Programmer
{
[Key]
public int ProgrammerId { get; set; }
public string ProgrammerName { get; set; }
}
EF Core created the tables for me based on a migration, and I have manually populated the Programmer table with the nine people who might be an Author and/or a Maintainer.
I have a webform where the user can create a new SoftwareItem, with pre-populated drop-downs for Authors and Maintainers that, after querying the database, contain the potential ProgrammerNames. The user can assign up to three Authors and up to three Maintainers before submitting the webform (via an Author1 dropdown, an Author2 dropdown etc.) Submitting the webform calls the InsertSoftware method, included below.
Note that I'm not a fan of the repetition between the Author logic and Maintainer logic, and the List should probably be a HashSet (in case the same author is set in Author1 and Author2) but those are issues for another day. The Author1 and similar variables are the int IDs set by the webform. I've previously verified they are being set to the appropriate values via a JavaScript alert. An ID of 0 means the value was never set (e.g. there is no second author).
The SoftwareItem here is instantiated as a new object on OnIntializedAsync and bound as the webform's model.
public async Task InsertSoftware()
{
List<int> authorIdsToAdd = new List<int>();
authorIdsToAdd.Add(Author1);
authorIdsToAdd.Add(Author2);
authorIdsToAdd.Add(Author3);
SoftwareItem.Authors = new List<Author>();
foreach (int author in authorIdsToAdd)
{
if (author != 0)
{
foreach (Programmer programmer in ProgrammerList)
{
if (programmer.ProgrammerId == author)
{
Author addedAuthor = new Author();
addedAuthor.Programmer = new Programmer();
addedAuthor.Programmer.ProgrammerId = author;
SoftwareItem.Authors.Add(addedAuthor);
}
}
}
}
[repeat code for the Maintainers]
await Http.PostAsJsonAsync("api/softwareitem", SoftwareItem);
Navigation.NavigateTo("software/fetchsoftware");
}
The SoftwareItem API is (in part) as follows:
[HttpPost]
public async Task<IActionResult> Create([FromBody] SoftwareItem softwareItem)
{
_context.Software.Add(softwareItem);
await _context.SaveChangesAsync();
return Ok(softwareItem);
}
My understanding from this Stack Overflow question is that if objects have been instantiated for a navigation property when the parent entity is added and saved to the database context, then EF Core will also add the new navigation property values to their appropriate tables. However, that isn't happening, and all I'm getting is a 500 error in the console.
What I'm expecting is that...
A new entry will be inserted into the SoftwareItem table
New entries will be inserted into the Author table, containing an auto-incremented AuthorId, the SoftwareItem's SoftwareItemId, and the ProgrammerId from the webform
New entries will be inserted into the Maintainer table, containing an auto-incremented MaintainerId, the SoftwareItem's SoftwareItemId, and the ProgrammerId from the webform.
Ok, it's a bit difficult to make out what your code is precisely doing but there are a few issues I see.
First, with entities you should always avoid ever reinitializing navigation property lists. During inserts it's "ok", but anywhere else it would lead to bugs/errors so it's better to simply not see it in the code. Pre-initialize your properties in the entity itself:
public class SoftwareItem
{
// ...
public virtual ICollection<Author> Authors { get; set; } = new List<Author>();
public virtual ICollection<Maintainer> Maintainers { get; set;} = new List<Maintainer>();
}
This ensures the collections are ready to go when you need them for a new entity.
Next, it can be helpful to structure your code to avoid things like module level variables. Your InsertSoftware() method references an instance of SoftwareItem and it isn't clear where, or what this reference would be pointing at. If you have a method chain that loaded a particular software item instance to be updated, pass the reference through the chain of methods as a parameter. This helps encapsulate the logic. You should also look to define a scope for whenever you are referencing a DbContext. With Blazor this needs to be done a bit more explicitly to avoid DbContext instances from being too long-lived. Long-lived DbContext instances are a problem because they lead to performance degradation as they track increasing numbers of entities, and can easly become "poisoned" with invalid entities that prevent things like SaveChanges() calls from succeeding. Keep instances alive only as long as absolutely necessary. I would strongly recommend looking at unit of work patterns to help encapsulate the lifetime scope of a DbContext. Ideally entities loaded by a DbContext should not be passed outside of that scope to avoid issues and complexity with detached or orphaned entities.
Next, it is important to know when you are looking to create new entities vs. reference existing data. Code like this is a big red flag:
Author addedAuthor = new Author();
addedAuthor.Programmer = new Programmer();
addedAuthor.Programmer.ProgrammerId = author;
From what I can make out, the Author (and Maintainer) are linking entities so we will want to create one for each "link" between a software item and a programmer. However, Programmer is a reference to what should be an existing row in the database.
If you do something like:
var programmer = new Programmer { ProgrammerId == author };
then associate that programmer as a reference to another entity, you might guess this would tell EF to find and associate an existing programmer.. Except it doesn't. You are telling EF to associate a new programmer with a particular ID. Depending on how EF has been configured for that entity (whether to use an identity column for the PK or not) this will result in one of three things happening if that programmer ID already exists:
A new programmer is created with an entirely new ID (identity gives it a new id and ProgrammerId is ignored)
EF throws an exception when it tries to insert a new programmer with the same ID. (Duplicate PK)
EF throws an exception if you tell it add a new programmer and it happens to already be tracking an instance with the same ID.
So, to fix this, load your references:
List<int> authorIdsToAdd = new List<int>();
// likely need logic to only add authors if they are selected, and unique.
authorIdsToAdd.Add(Author1);
authorIdsToAdd.Add(Author2);
authorIdsToAdd.Add(Author3);
// Define your own suitable scope mechanism for this method or method chain
using (var context = new AppDbContext())
{
var softwareItem = new SoftwareItem { /* populate values from DTO or Map from DTO */ }
// Retrieve references
var authors = await context.Programmers.Where(x => authorIdsToAdd.Contains(x.ProgrammerId)).ToListAsync();
foreach(var author in authors)
{
softwareItem.Authors.Add(new Author { Programmer = author });
}
// Continue for Maintainers...
await context.SaveChangesAsync();
}
I have found a solution that works (using DTOs and AutoMapper), which is reproduced below, but I would prefer an answer that lists the different approaches to the problem with examples and this will be marked as the answer if received.
In my entity model I have a navigation property that goes from a child entity to the parent entity. My project was working swimmingly. Then I began to use AutoFixture for unit testing, and testing failed, AutoFixture saying I had a circular reference.
Now, I realise that circular reference navigation properties like this are OK within Entity Framework, but I found this post (Use value of a parent property when creating a complex child in AutoFixture), where Mark Seemann, the creator of AutoFixture states:
"For the record, I haven't written an API with a circular reference for years, so it's quite possible to avoid those Parent/Child relations."
So, I want to understand HOW a domain model can be refactored to avoid child/parent relations.
Below are the entity classes in question, the repository method, and how I use the property causing the circular reference in my View. The perfect answer would explain the different options I could choose from with examples, and the basic pros/cons of each approach.
Note: The property causing the circular reference is User, in the UserTeam model.
Models:
public class UserProfile
{
public UserProfile()
{
UserTeams = new HashSet<UserTeam>();
Games = new HashSet<Game>();
}
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
public string UserName { get; set; }
public virtual ICollection<UserTeam> UserTeams { get; set; }
public virtual ICollection<Game> Games { get; set; }
}
public class Game
{
public Game()
{
UserTeams = new HashSet<UserTeam>();
}
public int Id { get; set; }
public int CreatorId { get; set; }
public virtual ICollection<UserTeam> UserTeams { get; set; }
}
public class UserTeam
{
public UserTeam()
{
UserTeam_Players = new HashSet<UserTeam_Player>();
}
public int Id { get; set; }
public int UserId { get; set; }
public int GameId { get; set; }
public virtual UserProfile User { get; set; }
public virtual ICollection<UserTeam_Player> UserTeam_Players { get; set; }
}
Repository Method
public IEnumerable<Game> GetAllGames()
{
using (DataContext)
{
var _games = DataContext.Games
.Include(x => x.UserTeams)
.Include(x => x.UserTeams.Select(y => y.User))
.ToList();
if (_games == null)
{
// log error
return null;
}
return _games;
}
}
View
#model IEnumerable<Game>
#foreach (var item in Model){
foreach (var userteam in item.UserTeams){
<p>#userteam.User.UserName</p>
}
}
Now, if I remove the 'User' navigation property, I wouldn't be able to do '#userteam.User.UserName'
So, how do I refactor the domain model to remove the circular reference, whilst being able to easily loop through Games, and do something like
UserTeam.User.Username?
I had a similar problem with AutoFixture and EntityFramework a while ago. My solution was to add an extension to AutoFixture, that allows you to build a SUT with a few recursions. That extension has recently been adopted in AutoFixture.
But I understand that your question was not about how to make AutoFixture construct recursive data structures, which is indeed possible, but how to create domain models without recursion.
First, you have tree or graph structures. Here anything but recursion would mean indirection through loose coupled node ids. Instead of defining an association, you would have to traverse the tree query-by-query or cache the whole thing and traverse by node-key lookup, which may be impractical depending on the tree-size. Here it is very convenient to make EF do the work for you.
The other common structure is a two-way navigational structure similar to your user / game scenario. Here it is often not that inconvenient to prune the navigation flow to a single direction. If you omit one direction, say from game to team, you can still easily query all teams for a given game. So: User has a list of games and a list of teams. Team has a list of games. Games have no navigational reference to either. To get all users for a specific game you could write something like:
var users = (from user in DataContext.Users
from game in user.Games
where game.Name == 'Chess'
select user).Distinct()
I have found a solution that works (using DTOs and AutoMapper), which is reproduced below, but I would still prefer an answer that lists the different approaches to the problem with examples, in particular whether this is a desirable solution, or whether I should stick with the navigation properties as they were, get rid of AutoFixture, and when it comes to serializing for json just utilise other work arounds (attributes etc)...
So, in my View Model, I added a couple of classes:
public class GameDTO
{
public int Id { get; set; }
public int CreatorId { get; set; }
public ICollection<UserTeamDTO> UserTeamsDTO { get; set; }
}
public class UserTeamDTO : UserTeam
{
public UserProfile User { get; set; }
}
And in my controller, I use AutoMapper to map the Game / UserTeam objects from the repository to my DTO objects, and return the IList _gamesDto to the View.
var _games = _gameRepository.GetAllGames();
IList<GameDTO> _gamesDto = new List<GameDTO>();
IList<UserTeamDTO> _userteamsDto = new List<UserTeamDTO>();
GameDTO _gameDto = new GameDTO();
UserTeamDTO _userteamDto = new UserTeamDTO();
Mapper.CreateMap<Game, GameDTO>();
Mapper.CreateMap<UserTeam, UserTeamDTO>();
foreach (Game _game in _games)
{
foreach (UserTeam _userteam in _game.UserTeams)
{
_userteamDto = Mapper.Map<UserTeamDTO>(_userteam);
_userteamDto.User = _userRepository.GetUser(_userteam.UserId);
_userteamsDto.Add(_userteamDto);
}
_gameDto = Mapper.Map<GameDTO>(_game);
_gameDto.UserTeamsDTO = _userteamsDto;
_gamesDto.Add(_gameDto);
}
I had a similar problem recently which also impacted serializing JSON objects. I decided to remove the circular references from my data model.
I first removed the redundant navigation properties which were creating the circular references. I made sure that my resulting tree of data made sense. This allowed me to make it clear which objects own which relationships.
This also made EF unable to automatically reason about my relationships. I had to specify the One-to-Many and Many-to-Many relationships using the FluentAPI. I found a solution here: https://stackoverflow.com/a/16719203/1887885
Hope this is helpful.
I have just spent a couple of hours on this problem, and I would like to simplify it in future because I can see it being a common requirement.
I have a Question class with a navigation property that is a collection:
public class AnsweredQuestion : ModelBase
{
public virtual ICollection<Answer> Answers { get; set; }
}
All my models inherit a single base class:
public abstract class ModelBase
{
public int ID { get; set; }
}
Now I want to set the Answers collection from a collection of answer ids and I have this method in my controller - and it does work
private void SetAnswers(AnsweredQuestion question,
IEnumerable<int> newAnswerIDs)
{
//First remove any answers we don't want
question.Answers.RemoveAll(a => !newAnswerIDs.Contains(a.ID));
//Then project the current ids
IEnumerable<int> currentAnswerIds = question.Answers.Select(a => a.ID);
//Now go to the database to get the answers that match the ids that
//we have to add
IQueryable<Answer> answersToAdd = _uow.AnswerRepository.All
.Where(dbAnswers => newAnswerIDs.Contains(dbAnswers.ID)
&&
!currentAnswerIds.Contains(dbAnswers.ID));
//and add them to the navigation property
question.Answers.AddRange(answersToAdd);
}
But this code is quite complicated, and I can see me having to write it again and again in each Model where I have a navigation property.
If this was a 1 to many relationship I'd have an Answer property and an AnswerID property in my entity and the framework would resolve the issue for me. But, as far as I know, I cant do that for many to many relationships.
Can anyone think of a way to turn this into a method that can be called on any navigation property in any model? I thought about creating an extension method on a collection of models, but my stumbling block is that I need to go to the database to get the Answers that match the ids I have before I add them to the Answers collection and that would mean that my extension method would need to know which repository to use
Here is what I have come up with:
public static bool SetById<T>(this ICollection<T> collection,
IEnumerable<int> ids,
IRepository<T> repo)
where T : ModelBase
{
//First remove any answers we don't want
int count = collection.Count;
collection.RemoveAll(a => !ids.Contains(a.ID));
bool isAltered = count != collection.Count;
//Then project the current ids
IEnumerable<int> currentIds = collection.Select(a => a.ID);
IQueryable<T> toAdd = repo.All.Where(dbAnswers => ids.Contains(dbAnswers.ID) && !currentIds.Contains(dbAnswers.ID));
isAltered = isAltered || toAdd.Any();
//and add them to the navigation property
collection.AddRange(toAdd);
return isAltered;
}
This is dependent on all my entities inheriting from a base class with an ID:
public abstract class ModelBase
{
public int ID { get; set; }
}
And in my controller I call it like this (passing in my repository):
question.Answers.SetById(newAnswerIDs, _uow.AnswerRepository);
What are some ways I can delete an item from a collection? (I am using MVC 4 and EF.)
As an example:
public class Birthday
{
public string Name { get; set; }
public virtual ICollection<Gift> Gifts { get; set; }
}
public class Gift
{
public string Name { get; set; }
public double Price { get; set; }
}
I'm using Editing a variable length list, ASP.NET MVC 2-style to create a dynamic list of Gifts.
The example is shows how to "Delete" a row. This will delete the row from the page and the correct Gifts are sent to the controller.
When I update the Birthday / Gifts everything new is updated properly, but anything deleted is still there.
So my question is what are some preferred ways to remove Gifts?
Two ways I've thought of already:
Get a Birthday from the DB and compare the Gifts removing as needed. I don't love this idea because it seems heavy handed.
Use WebApi / Ajax and delete the Gift from the list and the DB when the user pushes the delete link. I like this better than #1 but does this put too much business logic in the presentation layer?
I'm guessing that other people have had this similar problem and have a clever solution I haven't thought of yet.
Thanks in advance!
Make a Gifts api controller.
Let it have a Delete method accepting an Id of whatever type your Id is.
And do something like this in it:
public class GiftsController: ApiController
{
public void Delete(Guid Id)
{
var context = new MyContext();
var giftToDelete = context.Gifts.FirstOrDefault(g=> g.Id == Id);
if(giftToDelete != null)
{
context.Gifts.Remove(giftToDelete);
context.SaveChanges();
}
}
}
Make sure you make a DELETE request to this api in your JS delete function.
You may also replace the body of this method with some Service.DeleteGift(Id) if you're too concerned about doing things in the right place.
Like this:
public class ValuesController : ApiController
{
private List<string> list = new List<string>{"Item1","Item2","Item3","Item4","Item5"};
// DELETE api/values/5
public List<string> DeleteItem(int id)
{
list.Remove(list.Find((i => i.ToString().Contains(id.ToString()))));
return list;
}
}