Using Asp.net MVC 4 and EF 5.0
THE PROBLEM:
Whenever I choose a user in the dropdownlist and submit the post to the Action.
Two things happen:
The user is added to the DinnerEvent as supposed to:
The User that was selected is duplicated, so that I now have two instances of the user in the User table in the database. Which is bad. :(
Here is the setup
I have created two entities:
DinnerEvent and User
The DinnerEvent class has a navigation property to a collection of Attendants (Users)
public virtual ICollection<User> Attendants { get; set; }
I have created repositories for both DinnerEvent and Users
The AddAttendantToEvent Action in the DinnerEvent Controller
[HttpPost]
public ActionResult AddAttendantToEvent(int users , int EventID )
{
if (ModelState.IsValid)
{
var user = userRepository.Find(users);
dinnereventRepository.Find(EventID).Attendants.Add(user); //Add the User to the Event.
dinnereventRepository.Save();
return RedirectToAction("Index");
}
else
{
return View();
}
}
The view - Iterating over all the events, and adding a dropdownlist populated with all the users foreach event.
#model Madklub.Models.ViewModel
#foreach (var item in Model.events) {
//Code to display available events
#using (Html.BeginForm("AddAttendantToEvent", "DinnerEvents")) {
#Html.Hidden("EventID", item.DinnerEventID);
#Html.DropDownListFor(m => m.users, new SelectList(Model.users, "UserID", "RoomNumber"));
<input type="submit" value="Add" />
}
ViewModel:
public class ViewModel
{
public IQueryable<DinnerEvent> events { get; set; }
public IQueryable<User> users { get; set; }
}
which is initialized like this:
Index Action in DinnerEventController:
public ViewResult Index()
{
ViewModel viewdata = new ViewModel();
viewdata.events = dinnereventRepository.AllIncluding(m => m.Attendants);
viewdata.users = userRepository.All;
return View(viewdata);
}
What am I doing wrong?
The repository code as requested:
Scaled down to only contain the Save() method.
Note that this is all autogenerated code by Scaffolding.
public class DinnerEventRepository : IDinnerEventRepository
{
MadklubContext context = new MadklubContext();
public void Save()
{
context.SaveChanges();
}
}
public class MadklubContext : DbContext
{
public DbSet<Madklub.Models.User> Users { get; set; }
public DbSet<Madklub.Models.MadklubEvent> MadklubEvents { get; set; }
}
Unit of Work pattern: (BTW, make your repo's/uow IDisposable so you can clean up the DbContext instances)
public class DinnerEventRepository : IDinnerEventRepository
{
MadklubContext _context = new MadklubContext();
public void Save()
{
_context.SaveChanges();
}
public DinnerEventRepository( MadklubContext context = null )
{
_context = context ?? new MadklubContext();
}
}
public class UserRepository //: IUserRepository
{
MadklubContext _context = new MadklubContext();
public void Save()
{
_context.SaveChanges();
}
public UserRepository( MadklubContext context = null )
{
_context = context ?? new MadklubContext();
}
}
public class RsvpUnitOfWork // come up with a better name
{
MadklubContext _context = new MadklubContext();
public DinnerEventRepository DinnerEventRepo { get; private set; }
public UserRepository UserRepo { get; private set; }
public RsvpUnitOfWork()
{
DinnerEventRepo = new DinnerEventRepository( _context );
UserRepo = new UserRepository( _context );
}
public void Save()
{
_context.SaveChanges();
}
}
Usage:
[HttpPost]
public ActionResult AddAttendantToEvent(int users , int EventID )
{
if (ModelState.IsValid)
{
// this should be a using statement once you implement IDisposable
// e.g. using( var uow = new RsvpUnitOfWork() ) { ... }
var uow = new RsvpUnitOfWork();
// you need to validate that user != null
var user = uow.UserRepo.Find(users);
// possible null reference exception if dinner event not found for EventID
uow.DinnerEventRepo.Find(EventID).Attendants.Add(user); //Add the User to the Event.
uow.Save();
return RedirectToAction("Index");
}
else
{
return View();
}
}
Related
I am learning ASP .NET Core and I am trying to use repository pattern to clean up my controllers. The way I thought this out was:
create a repository interface containing the basic methods for a repository
implement the previously created the interface
create a model based repository interface
create a model-based repository extending the basic repository created at step 2 and implemented the model-based interface
create a wrapper class which contains the context and all the application repositories and inject it in every controller
Unfortunately the Complete of 'Edit' method causes a DbConcurrencyException which I have tried to solve using this. using the previous solution causes an InvalidOperationException as one of the properties is read-only.
For some code:
public class User : IdentityUser
{
[PersonalData]
[DisplayName("First Name")]
[Required(ErrorMessage = "The first name is required!")]
[StringLength(30, MinimumLength = 3, ErrorMessage = "The first name must be between 3 and 30 characters long!")]
public string firstName { get; set; }
[PersonalData]
[DisplayName("Last Name")]
[Required(ErrorMessage = "The last name is required!")]
[StringLength(30, MinimumLength = 3, ErrorMessage = "The last name must be between 3 and 30 characters long!")]
public string lastName { get; set; }
[PersonalData]
[DisplayName("CNP")]
[Required(ErrorMessage = "The PNC is required!")]
[StringLength(13, MinimumLength = 13, ErrorMessage = "The last name must 13 digits long!")]
[RegularExpression(#"^[0-9]{0,13}$", ErrorMessage = "Invalid PNC!")]
public string personalNumericalCode { get; set; }
[PersonalData]
[DisplayName("Gender")]
[StringRange(AllowableValues = new[] { "M", "F" }, ErrorMessage = "Gender must be either 'M' or 'F'.")]
public string gender { get; set; }
public Address address { get; set; }
}
public class Medic : User
{
[DisplayName("Departments")]
public ICollection<MedicDepartment> departments { get; set; }
[DisplayName("Adiagnostics")]
public ICollection<MedicDiagnostic> diagnostics { get; set; }
[PersonalData]
[DisplayName("Rank")]
[StringLength(30, MinimumLength = 3, ErrorMessage = "The rank name must be between 3 and 30 characters long!")]
public string rank { get; set; }
}
public class MedicController : Controller
{
private readonly IUnitOfWork unitOfWork;
public MedicController(IUnitOfWork unitOfWork)
{
this.unitOfWork = unitOfWork;
}
// GET: Medic
public async Task<IActionResult> Index()
{
return View(await unitOfWork.Medics.GetAll());
}
// GET: Medic/Details/5
public async Task<IActionResult> Details(string id)
{
if (id == null)
{
return NotFound();
}
Medic medic = await unitOfWork.Medics.FirstOrDefault(m => m.Id == id);
if (medic == null)
{
return NotFound();
}
return View(medic);
}
// GET: Medic/Create
public IActionResult Create()
{
return View();
}
// POST: Medic/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("rank,firstName,lastName,personalNumericalCode,Id,gender,Email")] Medic medic)
{
if (ModelState.IsValid)
{
unitOfWork.Medics.Add(medic);
await unitOfWork.Complete();
return RedirectToAction(nameof(Index));
}
return View(medic);
}
// GET: Medic/Edit/5
public async Task<IActionResult> Edit(string id)
{
if (id == null)
{
return NotFound();
}
Medic medic = await unitOfWork.Medics.Get(id);
if (medic == null)
{
return NotFound();
}
return View(medic);
}
// POST: Medic/Edit/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(string id, [Bind("rank,firstName,lastName,Id,personalNumericalCode,gender,Email")] Medic medic)
{
if (id != medic.Id)
{
return NotFound();
}
if (ModelState.IsValid)
{
var saved = false;
while (!saved)
{
try
{
unitOfWork.Medics.Update(medic);
await unitOfWork.Complete();
saved = true;
}
catch (DbUpdateConcurrencyException ex)
{
if (!MedicExists(medic.Id))
{
return NotFound();
}
else
{
foreach (var entry in ex.Entries)
{
if (entry.Entity is Medic)
{
var proposedValues = entry.CurrentValues;
var databaseValues = entry.GetDatabaseValues();
foreach (var property in proposedValues.Properties)
{
var proposedValue = proposedValues[property];
var databaseValue = databaseValues[property];
proposedValues[property] = proposedValue;
// TODO: decide which value should be written to database
// proposedValues[property] = <value to be saved>;
}
// Refresh original values to bypass next concurrency check
entry.OriginalValues.SetValues(databaseValues);
}
else
{
throw new NotSupportedException(
"Don't know how to handle concurrency conflicts for "
+ entry.Metadata.Name);
}
}
}
}
}
return RedirectToAction(nameof(Index));
}
return View(medic);
}
// GET: Medic/Delete/5
public async Task<IActionResult> Delete(string id)
{
if (id == null)
{
return NotFound();
}
Medic medic = await unitOfWork.Medics.FirstOrDefault(m => m.Id == id);
if (medic == null)
{
return NotFound();
}
return View(medic);
}
// POST: Medic/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(string id)
{
Medic medic = await unitOfWork.Medics.Get(id);
unitOfWork.Medics.Remove(medic);
await unitOfWork.Complete();
return RedirectToAction(nameof(Index));
}
private bool MedicExists(string id)
{
return unitOfWork.Medics.Any(e => e.Id == id);
}
}
public class Repository<TEntity> : IRepository<TEntity> where TEntity : class
{
protected readonly ApplicationDbContext context;
public Repository(ApplicationDbContext context)
{
this.context = context;
}
public void Add(TEntity entity)
{
context.Set<TEntity>().AddAsync(entity);
}
public void AddRange(IEnumerable<TEntity> entities)
{
context.Set<TEntity>().AddRangeAsync(entities);
}
public bool Any(Expression<Func<TEntity, bool>> predicate)
{
return context.Set<TEntity>().Any(predicate);
}
public async Task<IEnumerable<TEntity>> Find(Expression<Func<TEntity, bool>> predicate)
{
return await context.Set<TEntity>().Where(predicate).ToListAsync();
}
public async Task<TEntity> FirstOrDefault(Expression<Func<TEntity, bool>> predicate)
{
return await context.Set<TEntity>().FirstOrDefaultAsync(predicate);
}
public async Task<TEntity> Get(string id)
{
return await context.Set<TEntity>().FindAsync(id);
}
public async Task<IEnumerable<TEntity>> GetAll()
{
return await context.Set<TEntity>().ToListAsync();
}
public void Remove(TEntity entity)
{
context.Set<TEntity>().Remove(entity);
}
public void RemoveRange(IEnumerable<TEntity> entities)
{
context.Set<TEntity>().RemoveRange(entities);
}
public TEntity SingleOrDefault(Expression<Func<TEntity, bool>> predicate)
{
return context.Set<TEntity>().SingleOrDefault(predicate);
}
public void Update(TEntity entity)
{
context.Set<TEntity>().Update(entity);
}
}
public class MedicRepository : Repository<Medic>, IMedicRepository
{
public MedicRepository(ApplicationDbContext _context) : base(_context) { }
//TODO: add medic repository specific methods
}
public class UnitOfWork : IUnitOfWork
{
private readonly ApplicationDbContext _context;
public IMedicRepository Medics { get; private set; }
public IPatientRepository Patients { get; private set; }
public IReceptionistRepository Receptionists { get; private set; }
public IDiagnosticRepository Diagnostics { get; private set; }
public IMedicationRepository Medications { get; private set; }
public IMedicineRepository Medicine { get; private set; }
public ILabTestRepository LabTests { get; private set; }
public ILabResultRepository LabResults { get; private set; }
public UnitOfWork(ApplicationDbContext context)
{
_context = context;
Medics = new MedicRepository(_context);
Patients = new PatientRepository(_context);
Receptionists = new ReceptionistRepository(_context);
Diagnostics = new DiagnosticRepository(_context);
Medications = new MedicationRepository(_context);
Medicine = new MedicineRepository(_context);
LabTests = new LabTestRepository(_context);
LabResults = new LabResultRepository(_context);
}
public async Task<int> Complete()
{
return await _context.SaveChangesAsync();
}
public void Dispose()
{
_context.Dispose();
}
}
Thanks!
There are a lot of things to notice. But I will only point to the biggest one. DbContext or ApplicationDbContext classes are not meant to be long lived and cross spanned. I am guessing ApplicationDbContext is a singleton. Which is a long lived object and is shared among different classes and also may be threads. This is exactly the design pattern you should avoid. In terms of Microsoft -
Entity Framework Core does not support multiple parallel operations being run on the same DbContext instance. Concurrent access can result in undefined behavior, application crashes and data corruption. Because of this it's important to always use separate DbContext instances for operations that execute in parallel.
This page here describes the problem - https://learn.microsoft.com/en-us/ef/core/miscellaneous/configuring-dbcontext#avoiding-dbcontext-threading-issues
In short, use scoped dbcontext.
If you are learning, I would say implement it yourself and change the implementations of your classes. Create and dispose contexts when you need them. Do not keep long lived contexts.
If you just need a repository, you can use this package, I use it for myself - https://github.com/Activehigh/Atl.GenericRepository
I managed to fix this out. The concurrency exception was thrown because I was creating users (which were inheriting IDentityUser) without using a UserManager<User>. After inspecting the database fields, I have discovered that the Identityuser related fields (like email, username, etc..) were empty. This was due to me only adding information for the class which inherited IDentityUser.
I have seen plenty of questions regarding the same topic, but never found solution as yet. The code that is bringing me problems is:
public async Task<IHttpActionResult> Post(FullOrderViewModel fullOrder)
{
//Get all order items
List<OrderItem> orderItems = new List<OrderItem>();
foreach (var oi in fullOrder.Order)
{
var color = _colorRepo.Find(oi.ColorId);
var item = new OrderItem
{
Quantity = oi.Quantity,
Color = color
};
orderItems.Add(item);
}
//Get customer id
var customer = await _adminRepo.GetCustomerByUserName(fullOrder.Customer.UserName);
var billAddress = _addressRepo.All.FirstOrDefault(x => x.AddressLine == fullOrder.BillingAddress.AddressLine && x.PostalCode == fullOrder.BillingAddress.PostalCode);
var deliveryAddress = _addressRepo.All.FirstOrDefault(x => x.AddressLine == fullOrder.DeliveryAddress.AddressLine && x.PostalCode == fullOrder.DeliveryAddress.PostalCode);
//CASE : sample order
if (fullOrder.OrderType == OrderType.Sample)
{
var order = new SampleOrder {
Type = OrderType.Sample,
Status = "Unauthorized",
Origin = Origin.Online,
OrderItems = orderItems,
CreationDate = DateTime.Now,
CustomerId = customer.Id,
BillAddressId = billAddress.AddressId,
DeliveryAddressId = deliveryAddress.AddressId
};
try
{
_sampleOrderRepo.InserGraph(order);
_unitOfWork.Save();
}
catch (Exception e)
{
return InternalServerError(e);
}
The way I get my repositories is via dependency injection in the OrderController constructor, so in a standard way as I believe. Each of my repos is build in this way:
public class SampleOrderRepository : EntityRepository<SampleOrder, IPhoeniceUnitOfWork<PhoeniceContext>>, ISampleOrderRepository
{
public SampleOrderRepository(IPhoeniceUnitOfWork<PhoeniceContext> unitOfWork) : base(unitOfWork)
{
}
protected override IDbSet<SampleOrder> List(IPhoeniceUnitOfWork<PhoeniceContext> unitOfWork)
{
return unitOfWork.Context.SampleOrders;
}
protected override Expression<Func<SampleOrder, bool>> FindInt(int id)
{
return x => x.OrderId == id;
}
}
Where I override methods from general entity repository:
public abstract class EntityRepository<T, TContext> : IEntityRepository<T>
where T : class, IObjectWithState
where TContext : IUnitOfWork<EntityContext>
{
protected readonly TContext _unitOfWork;
protected EntityRepository(TContext unitOfWork)
{
_unitOfWork = unitOfWork;
}
protected virtual IDbSet<T> List(TContext unitOfWork)
{
throw new NotImplementedException("List operation not implemented!");
}
protected virtual Expression<Func<T, bool>> FindInt(int id)
{
throw new NotImplementedException("Finding entity with an int is not implemented!");
}
protected virtual Expression<Func<T, bool>> FindString(string id)
{
throw new NotImplementedException("Finding entity with an int is not implemented!");
}
public virtual IQueryable<T> All { get { return List(_unitOfWork); } }
public virtual IQueryable<T> AllIncluding(params Expression<Func<T, object>>[] includeProperties)
{
return includeProperties.Aggregate(All, (current, includeProperty) => current.Include(includeProperty));
}
public virtual T Find(int id)
{
return All.FirstOrDefault(FindInt(id));
}
public virtual T FindByString(string id)
{
return All.FirstOrDefault(FindString(id));
}
public virtual void InserGraph(T entity)
{
List(_unitOfWork).Add(entity);
}
public void InsertOrUpdate(T entity)
{
if (entity.ObjectState == State.Added) // New entity
{
_unitOfWork.Context.Entry(entity).State = EntityState.Added;
}
else // Existing entity
{
_unitOfWork.Context.Entry(entity).State = EntityState.Modified;
_unitOfWork.Context.ApplyStateChanges();
}
}
public virtual void Delete(int id)
{
var entity = Find(id);
List(_unitOfWork).Remove(entity);
}
public void Dispose()
{
}
}
Finally this is my UnitOfWork item:
public class PhoeniceUnitOfWork : IPhoeniceUnitOfWork<PhoeniceContext>
{
private readonly PhoeniceContext _context;
public PhoeniceUnitOfWork() : base()
{
_context = new PhoeniceContext();
}
//Left for testing purposes
public PhoeniceUnitOfWork(PhoeniceContext context)
{
_context = context;
}
public int Save()
{
return _context.SaveChanges();
}
public void Dispose()
{
_context.Dispose();
}
public IAddressRepository AddressRepository
{
get { return new AddressRepository(this); }
}
public IColorRepository ColorRepository
{
get { return new ColorRepository(this); }
}
public IHideOrderRepository HideOrderReposiotory
{
get { return new HideOrderRepository(this); }
}
public ISampleOrderRepository SampleOrderRepository
{
get { return new SampleOrderRepository(this); }
}
public ITaxonomyRepository TaxonomyRepository
{
get { return new TaxonomyRepository(this); }
}
PhoeniceContext IUnitOfWork<PhoeniceContext>.Context
{
get { return _context; }
}
}
and context that I'm using
public class PhoeniceContext : EntityContext
{
// You can add custom code to this file. Changes will not be overwritten.
//
// If you want Entity Framework to drop and regenerate your database
// automatically whenever you change your model schema, add the following
// code to the Application_Start method in your Global.asax file.
// Note: this will destroy and re-create your database with every model change.
//
// System.Data.Entity.Database.SetInitializer(new System.Data.Entity.DropCreateDatabaseIfModelChanges<Phoenice.EntityFramework.Models.PhoeniceContext>());
public PhoeniceContext()
: base("Phoenice")
{
Database.SetInitializer<PhoeniceContext>(new DropCreateIfChangeInitializer());
//Database.SetInitializer<PhoeniceContext>(new DropCreateDatabaseAlways());
}
public DbSet<Address> Addresses { get; set; }
public DbSet<SampleOrder> SampleOrders { get; set; }
public DbSet<HideOrder> HideOrders { get; set; }
public DbSet<OrderItem> OrderItems { get; set; }
public DbSet<Color> Colors { get; set; }
public DbSet<Taxonomy> Taxonomies { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//There is OnModelCreating already overwritten in IdentityDbContext
//with all details required for ASP.NET identity
base.OnModelCreating(modelBuilder);
//solution for this mapping: http://stackoverflow.com/questions/11632951/primary-key-violation-inheritance-using-ef-code-first
modelBuilder.Entity<SampleOrder>()
.HasKey(x => x.OrderId)
.Map(m =>
{
//Requires EF 6.1
m.MapInheritedProperties();
m.ToTable("SampleOrders");
})
.Property(x => x.OrderId)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
modelBuilder.Entity<HideOrder>().Map(m =>
{
//Requires EF 6.1
m.MapInheritedProperties();
m.ToTable("HideOrders");
});
modelBuilder.Entity<HideOrder>().HasKey(x => x.OrderId);
}
}
I know that it's a lot of code that I am showing, but hope these are all of the building blocks for data access layer. For some reason when I call InsertGraph method on SampleOrders I get "An entity object cannot be referenced by multiple instances of IEntityChangeTracker" exception. I looked through this code dozens of times, and cannot get why does it throw this error message
i'm not sure of my answer, but i will suggest you the following :
my version of entity Framework is a bit different so i'm not sure you'll have all required functionnality. When you do this
List<OrderItem> orderItems = new List<OrderItem>();
foreach (var oi in fullOrder.Order)
{
var color = _colorRepo.Find(oi.ColorId);
var item = new OrderItem
{
Quantity = oi.Quantity,
Color = color
};
orderItems.Add(item);
}
I think you should first detach all your oi objects then the code should be something like
List<OrderItem> orderItems = new List<OrderItem>();
foreach (var oi in fullOrder.Order)
{
//detach oi
//attach oi
var color = _colorRepo.Find(oi.ColorId);
var item = new OrderItem
{
Quantity = oi.Quantity,
Color = color
};
orderItems.Add(item);
}
The items in your orderItems contains color. Which is returned by:
_colorRepo.Find(oi.ColorId);
The colorRepo holds the entities of color.
The order in the next line is used by another repo, but in 'order' there are colors from the previous repo.
_sampleOrderRepo.InserGraph(order);
Two repo's are holding the same entity object which causes this exception.
To avoid this problem you can two things:
Create a new object of Color every time you use _colorRepo.Find():
var color = new Color()
{
id = oi.ColorId
}
or use only one repo by moving the functionality to the services
Im new to MVC and EF and in a bit of a tangle i have the following Interface:
public interface IReportDataSource
{
IQueryable<PostDetail> PostDetails { get; }
void Save();
}
this database context:
public class ReportDb: DbContext, IReportDataSource
{
public DbSet<PostDetail> PostDetails { get; set; }
public ReportDb()
: base("DefaultConnection")
{
}
void IReportDataSource.Save()
{
SaveChanges();
}
IQueryable<PostDetail> IReportDataSource.PostDetails
{
get { return PostDetails; }
}
}
and this action
[HttpPost]
public ActionResult PostDetails(PostDetailsViewModel viewModel)
{
if (ModelState.IsValid)
{
//save
// var storePost = _db.PostDetails.Single();
var pd = new PostDetail();
pd.Grade = viewModel.Grade;
pd.ContractType = viewModel.SelectedContractType;
pd.Directorate = viewModel.SelectedDirectorate;
pd.Division = viewModel.Division;
pd.HoursPerWeek = viewModel.HoursPerWeek;
pd.Length = viewModel.SelectedContractLength;
pd.LineManager = viewModel.LineManager;
pd.LineManagerContactNumber = viewModel.LineManagerContactNumber;
pd.PositionTitle = viewModel.PositionTitle;
pd.Section = viewModel.Section;
pd.SpecifyDuration = viewModel.SpecifyDuration;
pd.SpecifyEndDate = viewModel.SpecifyEndDate;
_db.Save();
return RedirectToAction("Index", "HomeController");
}
I think I need to be able to call add through my interface to add the new PostDetails object to the datacontext before calling save?
I think _db.PostDetails.Add(pd) before save could help
I'm working on a project that consists of multiple objects that I want to save to my database. I'm using a single context and a series of repository classes to access them.
When I try to save an entity, it seems to save all the virtual entities associated with it, even if that entity exists in the database already.
These are my classes:
public class Requirement
{
public int ID { get; set; }
public DateTime DateDue { get; set; }
public DateTime DateCompleted { get; set; }
public virtual Standard Standard { get; set; }
public virtual Project Project { get; set; }
}
public class Standard
{
public int ID { get; set; }
public int AgencyID { get; set; }
public string Name { get; set; }
public virtual Agency Agency { get; set; }
}
public class Project
{
public int ID { get; set; }
public bool Active { get; set; }
public virtual Agency Agency { get; set; }
public virtual Department Department { get; set; }
}
And this is the method I have for creating some data:
public class RequirementRepository
{
public static string CreateMockData()
{
StandardRepository stdRep = new StandardRepository();
ProjectRepository projRep = new ProjectRepository();
RequirementRepository reqRep = new RequirementRepository();
Project project = projRep.Find(1);
StringBuilder sb = new StringBuilder()
foreach (Standard s in stdRep.FindByAgencyID(project.Agency.ID))
{
Requirement r = new Requirement();
r.Project = project;
r.Standard = s;
r.DateCompleted = (DateTime)SqlDateTime.MaxValue;
r.DateDue = DateTime.Now.AddDays(90);
r = reqRep.Save(r);
sb.AppendLine(String.Format("Saved Requirement ID {0} with Project ID {1}<br>", r.ID, r.Project.ID));
}
return sb.ToString();
}
}
And here is associated repository code:
public class ProjectRepository
{
public Project Find(int id)
{
using (var db = new MyContext())
{
return db.Projects
.Include(p => p.Agency)
.Include(p => p.Department)
.First(p => p.ID.Equals(id));
}
}
}
public class StandardRepository
{
public List<Standard> FindByAgencyID(int agencyID)
{
using (var db = new MyContext())
{
return db.Standards.Where(r => r.AgencyID == agencyID).ToList();
}
}
}
public class RequirementRepository
{
public Requirement Save(Requirement requirement)
{
using (var db = new MyContext())
{
Requirement retVal = requirement;
if (requirement.ID.Equals(0))
{
retVal = db.Requirements.Add(requirement);
}
else
{
db.Entry(requirement).State = EntityState.Modified;
}
db.SaveChanges();
return retVal;
}
}
}
When I run this method, I expect it to insert a number of new Requirements into the database with the project ID of 1 and a standard ID of whatever standard it's on. Instead, it creates a whole new project and a whole new standard for every requirement it adds, then assigns those IDs to the requirement.
Each context keeps track of the entities loaded, modified and the entities added.
Your repositories need to look something like this....
public class StandardRepository
{
MyContext _context;
public StandardRepository(MyContext context)
{
_context = context;
}
public List<Standard> FindByAgencyID(int agencyID)
{
return _context.Standards.Where(r => r.AgencyID == agencyID).ToList();
}
}
public class RequirementRepository
{
MyContext _context;
public RequirementRepository(MyContext context)
{
_context = context;
}
public Requirement Save(Requirement requirement)
{
Requirement retVal = requirement;
if (requirement.ID.Equals(0))
{
retVal = _context.Requirements.Add(requirement);
}
else
{
_context.Entry(requirement).State = EntityState.Modified;
}
_context.SaveChanges();
return retVal;
}
}
public class RequirementRepository
{
public static string CreateMockData()
{
using(MyContext context = new MyContext())
{
StandardRepository stdRep = new StandardRepository(context);
ProjectRepository projRep = new ProjectRepository(context);
RequirementRepository reqRep = new RequirementRepository(context);
Project project = projRep.Find(1);
StringBuilder sb = new StringBuilder()
foreach (Standard s in stdRep.FindByAgencyID(project.Agency.ID))
{
Requirement r = new Requirement();
r.Project = project;
r.Standard = s;
r.DateCompleted = (DateTime)SqlDateTime.MaxValue;
r.DateDue = DateTime.Now.AddDays(90);
r = reqRep.Save(r);
sb.AppendLine(String.Format("Saved Requirement ID {0} with Project ID {1}<br>", r.ID, r.Project.ID));
}
}
return sb.ToString();
}
}
From my understanding, you shouldn't have to manually set the state of the object you have modified unless you have detached it from its context. EF keeps track of the object state.
I like to use something like this:
public abstract class EntityRepositoryBase<TEntity> : IDisposable, IEntityRepositoryBase<TEntity> where TEntity : class , IEntityWithId
{
protected EntityRepositoryBase()
{
Context = new SomeEntities();
}
public abstract ObjectSet<TEntity> EntityCollection { get; }
public SomeEntities Context { get; set; }
public TEntity GetById(int id)
{
return EntityCollection
.FirstOrDefault(x => x.Id == id);
}
public void Dispose()
{
Context.Dispose();
}
}
Then in the deriving repositories:
public class AnswerRepository : EntityRepositoryBase<AnswerEntity>, IAnswerRepository
{
public override ObjectSet<AnswerEntity> EntityCollection
{
get { return Context.AnswerEntities; }
}
}
I inject the Repositories into the relevant class using ninject but you should be able to get similar with:
using (var repo = new AnswerRepository())
{
// modifying via Context
var someEntity = repo.GetById(someId);
someEntity.Value = "1";
repo.Context.SaveChanges();
//modifying via repo
repo.Delete(anotherEntity);
}
and then doing what you need to do. The context is exposed via the interface IEntityRepositoryBase should you need to perform out-of-repository modifications and then SaveChanges() as well as any specific CRUD type methods in your repo. Once out of scope the object and the underlying connection will be closed.
I would like to know if entity framework automatically tracks changes in navigational properties for an entity and also updates those changes when the related entity is updated using SaveChanges(). I am using a generic repository, unit of work, ninject with code first entity framework 5 in MVC 4. I have a post and tags tables that has a many-to-many relationship. I have a join table for this relationship, named PostTagMap, with two columns, Post_id and Tag_id. When I add a new post with its associated tags, the new post is successfully added to the post table and the PostTagMap also saves the tags associated with this post. However, when I try to edit/update the post and its associated tags, only the scalar properties of the post gets updated and not the associated tags. I tried debugging and I find that the post entity to be updated does have the updated values for the tags navigational property but entity framework only generates the update statement for the post table and not for the PostTagMap. What should I do to update the tags navigational property for the post as well.
Following are the classes used
DBContext class
public class EFDbContext : DbContext
{
public DbSet<Post> Posts { get; set; }
public DbSet<Tag> Tags { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Post>()
.HasMany<Tag>(p => p.Tags)
.WithMany(u => u.Posts)
.Map(m =>
{
m.ToTable("PostTagMap");
m.MapLeftKey("Post_id");
m.MapRightKey("Tag_id");
});
}
}
Post class
[Table("Post")]
public class Post
{
public int Id { get; set; }
public string Title { get; set; }
public virtual ICollection<Tag> Tags { get; set; }
}
Tag class
[Table("Tag")]
public class Tag
{
public int Id { get; set; }
public string Name { get; set; }
[JsonIgnore]
public ICollection<Post> Posts { get; set; }
}
Generic repository that implements IGenericRepository
public class EFGenericRepository<T> : IGenericRepository<T> where T : class
{
private DbContext DbContext { get; set; }
private DbSet<T> DbSet { get; set; }
public virtual void Add(T newEntity)
{
DbSet.Add(newEntity);
}
public virtual void Update(T entityToUpdate)
{
DbSet.Attach(entityToUpdate);
DbContext.Entry(entityToUpdate).State = EntityState.Modified;
}
}
Unit of work that implements IUnitOfWork
public class EfUnitOfWork : IUnitOfWork
{
private EFDbContext dbContext;
private IGenericRepository<Post> postRepository;
private IGenericRepository<Tag> tagRepository;
public EfUnitOfWork()
{
this.dbContext = new EFDbContext();
}
public void Commit()
{
dbContext.SaveChanges();
}
public IGenericRepository<Post> PostRepository
{
get
{
if (this.postRepository == null)
{
this.postRepository = new EFGenericRepository<Post>(dbContext);
}
return postRepository;
}
}
public IGenericRepository<Tag> TagRepository
{
get
{
if (this.tagRepository == null)
{
this.tagRepository = new EFGenericRepository<Tag>(dbContext);
}
return tagRepository;
}
}
private bool disposed = false;
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
dbContext.Dispose();
}
}
this.disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
service layer
public class ServiceRepository : IServiceRepository
{
private readonly IUnitOfWork UoW;
public ServiceRepository(IUnitOfWork _UoW)
{
UoW = _UoW;
}
public int AddPost(Post post)
{
UoW.PostRepository.Add(post);
UoW.Commit();
return post.Id;
}
public void EditPost(Post post)
{
UoW.PostRepository.Update(post);
UoW.Commit();
}
}
admin controller
public class AdminController : Controller
{
private readonly IServiceRepository _serviceRepository;
public AdminController(IServiceRepository serviceRepository)
{
_serviceRepository = serviceRepository;
}
[HttpPost, ValidateInput(false)]
public ContentResult AddPost(Post post)
{
string json;
ModelState.Clear();
if (TryValidateModel(post))
{
var id = _serviceRepository.AddPost(post);
json = JsonConvert.SerializeObject(new
{
id = id,
success = true,
message = "Post added successfully"
});
}
else
{
json = JsonConvert.SerializeObject(new
{
id = 0,
success = false,
message = "Post was not created"
});
}
return Content(json, "application/json");
}
[HttpPost, ValidateInput(false)]
public ContentResult EditPost(Post post)
{
string json;
ModelState.Clear();
if (TryValidateModel(post))
{
_serviceRepository.EditPost(post, Tags);
json = JsonConvert.SerializeObject(new
{
id = post.Id,
success = true,
message = "Post updated successfully"
});
}
else
{
json = JsonConvert.SerializeObject(new
{
id = 0,
success = false,
message = "Failed to save the changes."
});
}
return Content(json, "application/json");
}
}
I am also using a custom model binder for Post which is as follows
public class PostModelBinder : DefaultModelBinder
{
private readonly IKernel _kernel;
public PostModelBinder(IKernel kernel)
{
_kernel = kernel;
}
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var post = (Post)base.BindModel(controllerContext, bindingContext);
var _blogRepository = _kernel.Get<IServiceRepository>();
}
var tags = bindingContext.ValueProvider.GetValue("Tags").AttemptedValue.Split(',');
if (tags.Length > 0)
{
post.Tags = new List<Tag>();
foreach (var tag in tags)
{
post.Tags.Add(_serviceRepository.Tag( int.Parse(tag.Trim()) ));
}
}
return post;
}