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
Related
I want to saw that I do not care what is in the Find clause. Just give me null.
Here is the test
[Fact]
public void VerifyIndexViewType()
{
// Arrange
var mockUserProvider = new Mock<IUserProvider>();
mockUserProvider.Setup(x => x.GetUserId()).Returns("any-value-here");
var mockUnitOfWork = new Mock<IUnitOfWork>();
//how would I just return an object or null for example.. this doesnt work
// mockUnitOfWork.Setup(x => x.UserProfileDataRepository.Find(u => u.ApplicationUserId == "any value here")).Returns((IRepository<UserProfileData>)null);
var controller = new ProfileController(mockUnitOfWork.Object, mockUserProvider.Object);
// Act
var result = controller.Update();
// Assert
Assert.IsType<ViewResult>(result);
}
For the following controller and action
public class ProfileController : BaseController
{
private IUnitOfWork _unitOfWork;
private readonly IUserProvider _requestUserProvider;
public ProfileController(IUnitOfWork unitOfWork,
IUserProvider requestUserProvider)
: base(unitOfWork, requestUserProvider)
{
_unitOfWork = unitOfWork;
_requestUserProvider = requestUserProvider;
}
public IActionResult Update()
{
//this is easy
string userId = _requestUserProvider.GetUserId();
//how do I do the setup in moq for this?
IEnumerable<UserProfileData> userProfileQuestions = _unitOfWork.UserProfileDataRepository.Find(x => x.ApplicationUserId == userId);
if (userProfileQuestions != null)
{
ProfileViewModel profileViewModel = new ProfileViewModel();
profileViewModel.UserProfileData = userProfileQuestions.FirstOrDefault();
return View(profileViewModel);
}
return View("Error", "Home");
}
EDIT 1: MY IUnitOfWork and implementation of the method
public interface IUnitOfWork
{
IRepository<ApplicationUser> ApplicationUserRepository { get; }
IRepository<RefMedicalSpecialty> RefMedicalSpecialtyRepository { get; }
IRepository<RefProgramDetailData> RefProgramDetailDataRepository { get; }
IRepository<RefProgramProfileData> ProgramProfileDataRepository { get; }
IRepository<UserProgram> UserProgramRepository { get; }
IRepository<UserFeedback> UserFeedbackRepository { get; }
IRepository<UserAction> UserActionRepository { get; }
IRepository<UserProfileData> UserProfileDataRepository { get; }
IRepository<RawCredential> RawCredentialRepository { get; }
IRepository<RefProgramCharacteristic> RefProgramCharacteristicRepository { get; }
IRepository<UserProgramRefProgramCharacteristic> UserProgramRefProgramCharacteristicRepository { get; }
void Commit();
void RejectChanges();
void Dispose();
}
public IRepository<UserProfileData> UserProfileDataRepository =>
new Repository<UserProfileData>(_retContext);
Declare the Method Find as virtual
public virtual YourType Find(Expression<Func<YourClass, bool>> yourfunc)
and the mock as:
mockUnitOfWork.Setup(x => x.UserProfileDataRepository.Find(It.IsAny<Expression<Func<YourClass, bool>>>()).Returns(DefineYourReturn);
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.
Platform
.NET 4.5 and Entity Framework 6.
Question
I have the following code to execute a Migration:
//The following function just returns an object of the Configuration() class
//generated by code migrations
var migratorConfig = currentMigrationProvider.CreateDbMigrationConfiguration();
var dbMigrator = new System.Data.Entity.Migrations.DbMigrator(migratorConfig);
dbMigrator.Update();
The problem is that Update() function tries to create an instance of my DbContext class and for a few good reasons I need to manually create the context and feed it to dbMigrator. Is that possible? How?
Yes it is possible to control migrations and the context.
This is important if you are managing the connection.
The example shows connecting to 2 different Dbs.
It is a little convoluted. So i built a little test project to demo how.
using System;
using System.Data.Common;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Data.Entity.Migrations;
namespace Ef6Test {
public class Program {
public static void Main(string[] args) {
ExecDb1();
ExecDB2();
ExecDbCtx3();
}
private static void ExecDb1() {
Database.SetInitializer(new MigrateDatabaseToLatestVersion<Ef6Ctx, Ef6MigConf>());
WhichDb.DbName = "HACKDB1";
WhichDb.ConnType = ConnType.CtxViaDbConn;
var sqlConn = GetSqlConn4DBName(WhichDb.DbName);
var context = new Ef6Ctx(sqlConn);
context.Database.Initialize(true);
Console.WriteLine(WhichDb.DbName, context.Database.Exists() );
AddJunk(context);
//sqlConn.Close(); //?? whatever other considerations, dispose of context etc...
}
private static void ExecDB2() {
// yes other its default again reset this !!!!
WhichDb.DbName = "HACKDB2";
WhichDb.ConnType = ConnType.CtxViaDbConn;
var sqlConn2 = GetSqlConn4DBName(WhichDb.DbName);
var context2 = new Ef6Ctx2(sqlConn2);
Console.WriteLine(context2.Database.Exists());
Database.SetInitializer(new MigrateDatabaseToLatestVersion<Ef6Ctx2, Ef6MigConf2>());
context2.Database.Initialize(true);
Console.WriteLine(WhichDb.DbName, context2.Database.Exists());
AddJunk(context2);
}
private static void ExecDbCtx3() {
Database.SetInitializer(new MigrateDatabaseToLatestVersion<Ef6Ctx3, Ef6MigConf3>());
// Database.SetInitializer(new CreateDatabaseIfNotExists<Ef6Ctx3>());
// Database.SetInitializer(null);
WhichDb.ConnectionName = "AppCfgName";
WhichDb.ConnType = ConnType.CtxViaConnectionName;
var context3 = new Ef6Ctx3(WhichDb.ConnectionName);
context3.Database.Initialize(true);
Console.WriteLine(WhichDb.ConnectionName, context3.Database.Exists());
AddJunk(context3);
}
public static class WhichDb {
public static string DbName { get; set; }
public static string ConnectionName { get; set; }
public static ConnType ConnType { get; set; }
}
public enum ConnType {
CtxViaDbConn,
CtxViaConnectionName
}
private static void AddJunk(DbContext context) {
var poco = new pocotest();
poco.f1 = DateTime.Now.ToString();
// poco.f2 = "Did somebody step on a duck?"; //comment in for second run
context.Set<pocotest>().Add(poco);
context.SaveChanges();
}
public static DbConnection GetSqlConn4DBName(string dbName) {
var sqlConnFact = new SqlConnectionFactory(
"Data Source=localhost; Integrated Security=True; MultipleActiveResultSets=True");
var sqlConn = sqlConnFact.CreateConnection(dbName);
return sqlConn;
}
}
public class MigrationsContextFactory : IDbContextFactory<Ef6Ctx> {
public Ef6Ctx Create() {
switch (Program.WhichDb.ConnType) {
case Program.ConnType.CtxViaDbConn:
var sqlConn = Program.GetSqlConn4DBName(Program.WhichDb.DbName); // NASTY but it works
return new Ef6Ctx(sqlConn);
case Program.ConnType.CtxViaConnectionName:
return new Ef6Ctx(Program.WhichDb.ConnectionName);
default:
throw new ArgumentOutOfRangeException();
}
}
}
public class MigrationsContextFactory2 : IDbContextFactory<Ef6Ctx2> {
public Ef6Ctx2 Create() {
switch (Program.WhichDb.ConnType) {
case Program.ConnType.CtxViaDbConn:
var sqlConn = Program.GetSqlConn4DBName(Program.WhichDb.DbName); // NASTY but it works
return new Ef6Ctx2(sqlConn);
case Program.ConnType.CtxViaConnectionName:
return new Ef6Ctx2(Program.WhichDb.ConnectionName);
default:
throw new ArgumentOutOfRangeException();
}
}
}
public class MigrationsContextFactory3 : IDbContextFactory<Ef6Ctx3> {
public Ef6Ctx3 Create() {
switch (Program.WhichDb.ConnType) {
case Program.ConnType.CtxViaDbConn:
var sqlConn = Program.GetSqlConn4DBName(Program.WhichDb.DbName); // NASTY but it works
return new Ef6Ctx3(sqlConn);
case Program.ConnType.CtxViaConnectionName:
return new Ef6Ctx3(Program.WhichDb.ConnectionName);
default:
throw new ArgumentOutOfRangeException();
}
}
}
public class Ef6MigConf : DbMigrationsConfiguration<Ef6Ctx> {
public Ef6MigConf() {
AutomaticMigrationsEnabled = true;
AutomaticMigrationDataLossAllowed = true;
}
}
public class Ef6MigConf2 : DbMigrationsConfiguration<Ef6Ctx2> {
public Ef6MigConf2() {
AutomaticMigrationsEnabled = true;
AutomaticMigrationDataLossAllowed = true;
}
}
public class Ef6MigConf3 : DbMigrationsConfiguration<Ef6Ctx3> {
public Ef6MigConf3() {
AutomaticMigrationsEnabled = true;
AutomaticMigrationDataLossAllowed = true;
}
}
public class pocotest {
public int Id { get; set; }
public string f1 { get; set; }
public string f2 { get; set; } // comment in for second run
public string f2a { get; set; } // comment in for second run
public string f3 { get; set; } // comment in for second run
public string f5 { get; set; } // comment in for second run
public string f6b { get; set; } // comment in for second run
}
public class Ef6Ctx : DbContext {
public Ef6Ctx(DbConnection dbConn) : base(dbConn, true) { }
public Ef6Ctx(string connectionName) : base(connectionName) { }
public DbSet<pocotest> poco1s { get; set; }
}
public class Ef6Ctx2 : DbContext {
public Ef6Ctx2(DbConnection dbConn) : base(dbConn, true) { }
public Ef6Ctx2(string connectionName) : base(connectionName) { }
public DbSet<pocotest> poco1s { get; set; }
}
}
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();
}
}
I have the following abstract class:
public abstract class TemplateBase
{
public abstract string TemplateName { get; }
public string RuntimeTypeName { get { return GetType().FullName; } }
public abstract List<AreaContainer> TemplateAreas { get; }
}
then these 2 inherited classes:
public class SingleColumnTemplate : TemplateBase
{
public override string TemplateName { get { return "Single column"; } }
public AreaContainer CenterColumn { get; private set; }
public SingleColumnTemplate()
{
this.CenterColumn = new AreaContainer("Middle");
}
private List<AreaContainer> templateAreas;
public override List<AreaContainer> TemplateAreas
{
get
{
if (this.templateAreas == null)
{
this.templateAreas = new List<AreaContainer>() { this.CenterColumn };
}
return this.templateAreas;
}
}
}
and
public class TwoColumnTemplate : TemplateBase
{
public override string TemplateName { get { return "Two column"; } }
public AreaContainer LeftColumn { get; private set; }
public AreaContainer RightColumn { get; private set; }
public TwoColumnTemplate()
{
LeftColumn = new AreaContainer("Left");
RightColumn = new AreaContainer("Right");
}
private List<AreaContainer> templateAreas;
public override List<AreaContainer> TemplateAreas
{
get
{
if (this.templateAreas == null)
{
this.templateAreas = new List<AreaContainer>() { this.LeftColumn, this.RightColumn };
}
return this.templateAreas;
}
}
}
I also have this class that is my model for editing:
public class ContentPage
{
public virtual int ContentPageId { get; set; }
public virtual string Title { get; set; }
public TemplateBase Template { get; set; }
}
Question:
for my ActionResults I have the following:
[HttpGet]
public ActionResult Edit()
{
var row = new ContentPage();
var template = new TwoColumnTemplate();
// Areas
HtmlArea html_left = new HtmlArea();
html_left.HtmlContent = "left area html content";
HtmlArea html_right = new HtmlArea();
html_right.HtmlContent = "right area html content";
template.LeftColumn.Areas.Add(html_left);
template.RightColumn.Areas.Add(html_right);
row.Template = template;
return View(row);
}
[HttpPost]
[ValidateInput(false)]
public ActionResult Edit(ContentPage row)
{
// Here i could loop through List -TemplateAreas and save each template Area to Db. I guess that would work
return this.View(row);
}
Question:
For HttpGet- how would I load row Template from the database? since it could be SingleColumnClass or TwoColumnClass.
how would my ViewModel look like to solve this?
thanks
You can write your own Model Binder that is responsible for binding TemplateBase. You will still need to have a way of knowing (in the model binder) which type you will be using a runtime, but you can always delegate that to a factory or service locator of some sort. I did a quick google search and here is a blog post I found that gives you some information for making a model binder for a similar scenario:
http://weblogs.asp.net/bhaskarghosh/archive/2009/07/08/7143564.aspx
EDIT: The blog leaves out how you tell MVC about your model binder. When the application starts, you can add your model binder to System.Web.Mvc.ModelBinders.Binders
HTH
You need to know the template type in you controller, so you can pass a parameter from the view to the controller, indicating the type (SingleColumn or TwoColumn). You could do this witn a Enum:
public enum TemplateType
{
SingleColumn,
TwoColumn
}
[HttpGet]
public ActionResult Edit(TemplateType templateType)
{
var row = new ContentPage();
TemplateBase template;
if (templateType == TemplateType.SingleColumn)
{
template = new SingleColumnTemplate();
}
else
{
template = new TwoColumnTemplate();
}
...
return View(row);
}
When you create the action link from your view you can specify:
<%= Html.ActionLink("Edit",
"Edit",
"YouController",
new
{
// singlecolumn or twocolumn
// depending on your concrete view
TemplateType = TemplateType.xxx
},
null);
I wonder if you could do something like this?
[HttpGet]
public ActionResult Edit(TemplateType templateType)
{
var row = new ContentPage();
TemplateBase template = (TemplateBase)Activator.CreateInstance(templateType);
...
return View(row);
}
templateType would have to be the exact name of your inherited classes (you can ignore case)