Insert a entity that contains a collection navigation property - insert

I have two classes:
public class Student
{
public int StudentID { get; set; }
public List<Course> Courses { get; set; }
}
public class Course
{
public int CourseID { get; set; }
public string Name { get; set; }
}
And a context:
public class DBContext: DbContext
{
public DbSet<Student> Students { get; set; }
public DbSet<Course> Courses { get; set; }
public DBContext(DbContextOptions<DBContext> options) : base(options) { }
}
Courses and students can exist independently of each other. In other words, I can create a Course first, then create a Student and associate them with the course.
For example:
var options = new DbContextOptionsBuilder<DBContext>().UseSqlite("Data Source=.\\test.db;");
Course course = null;
using(var context = new DBContext(options.Options))
{
context.Database.EnsureCreated();
}
using (var context = new DBContext(options.Options))
{
course = context.Courses.Add(new Course { Name = "Algebra" }).Entity;
context.SaveChanges();
}
using (var context = new DBContext(options.Options))
{
var student = context.Students.Add(new Student { Courses = new List<Course> { course } });
context.SaveChanges();
}
However, in the last context scope, I get an unique constraint error. I know that some people say that I need to do everything within the same context. But this will be nearly impossible when using, for example, a WebAPI microservice architecture (StudentService and CourseService).
So how do I handle this?

The problem here is that in the last using block context doesn't know that course instance has been saved already in a previous operation.
I did a small test and I saw that course doesn't have the assigned Id from the database after SaveChanges is called in second using block.
In general the most common way to perform this is to fetch the entity from the same context where we need to save the data. So this will do the trick.
Course course = new Course { Name = "Algebra" };
using (var context = new DBContext(options.Options))
{
context.Database.EnsureCreated();
}
using (var context = new DBContext(options.Options))
{
context.Courses.Add(course);
context.SaveChanges();
}
using (var context = new DBContext(options.Options))
{
var courseFromDb = context.Courses.Single(c => c.CourseID == course.CourseID);
var student = context.Students.Add(new Student { Courses = new List<Course> {courseFromDb}});
context.SaveChanges();
}

Related

Retrieving information from derived child object collections using LINQ

I have been trying to get a list of all Workflows that have Offices contained in a certain List by Office Id. I can easily get all of the Workflows that have SingleWorkflowSteps because they have only one Office, but have been unable to understand how I would successfully get those contained in a MultiWorkflowStep. All workflow steps have either a SingleWorkflowStep or a MultiWorkflowStep that contains two or more SingleWorkflowSteps. At the time I designed this, it seemed like a logical way to do this but atlas my LINQ-fu is not as good as I thought it was. Can someone please point me in the right directions. Code listed below:
var OfficesToFind = new List<int> (new int[] { 1,3,5,7,9,10,11,12} );
public class Workflow
{
public Workflow()
{
WorkflowSteps = new List<WorkflowStepBase>();
}
public int Id { get; set; }
public virtual ICollection<WorkflowStepBase> WorkflowSteps { get; set; }
}
public abstract class WorkflowStepBase
{
public int Id { get; set; }
public int StatusId { get; set; }
public virtual Workflow Workflow { get; set; }
public virtual Status Status { get; set; }
}
public class MultiWorkflowStep : WorkflowStepBase
{
public MultiWorkflowStep()
{
ChildSteps = new List<SingleWorkflowStep>();
}
public virtual ICollection<SingleWorkflowStep> ChildSteps { get; set; }
}
public class SingleWorkflowStep : WorkflowStepBase
{
public int? ParentStepId { get; set; }
public int OfficeId { get; set; }
public virtual MultiWorkflowStep ParentStep { get; set; }
public virtual Office Office { get; set; }
}
public class Office
{
public int Id { get; set; }
public string Name { get; set; }
}
public class WorkflowService : IWorkflowService<Workflow>
{
private readonly IRepository<Workflow> _workflowService;
private readonly IRepository<SingleWorkflowStep> _singleStepService;
private readonly IRepository<MultiWorkflowStep> _multiStepService;
public WorkflowService(IUnitOfWork uow)
{
_workflowService = uow.GetRepository<Workflow>();
_singleStepService = uow.GetRepository<SingleWorkflowStep>();
_multiStepSercice = uow.GetRepository<MultiWorkflowStep>();
}
// ~ ------- Other CRUD methods here -------- ~
public IEnumerable<Workflow> GetWorkflowFilter(List<int> statuses, List<int> offices...)
{
var query = _workflowService.GetIQueryable(); // returns an IQueryable of dbset
if(statuses.Any())
{
query = query.Where(q => statuses.Contains(q.StatusId));
}
if(offices.Any())
{
// Get all active single steps and the ones that contain the offices
singleSteps = _singleStepService
.Where(s => s.StatusId == (int)Enumerations.StepStatus.ACTIVE)
.Where(s => offices.Contains(s.OfficeId));
// Get all of the parent Workflows for the singleSteps
var workflows = singleSteps.Select(w => w.Workflow);
// Update the query with the limited scope
query = query.Where(q => q.Workflow.Contains(q));
}
return query.ToList();
}
}
OK, after a good night sleep, being all bright-eyed and bushy-tailed, I figured out my own problem. First the updated code was all wrong. Because each derived WorkflowStep has access to the Workflow and each MultiWorkflowStep contains a list of SingleWorkflowSteps - when I get the list of all SingleWorkflowSteps (which would include all from MultiWorkflowStep(s)), I simply needed to get a list of all of the parent Workflows of the SingleWorkflowSteps. Next I updated my query to limit the Workflows that were contained in the new Workflow list and here is the correct code for the GetWorkflowFilter method:
...
if(offices.Any())
{
// Get all active single steps and the ones that contain the offices
singleSteps = _singleStepService.Where(s => s.StatusId == (int)Enumerations.StepStatus.ACTIVE).Where(s => offices.Contains(s.OfficeId));
// Get all of the parent Workflows for the singleSteps
var workflows = singleSteps.Select(w => w.Workflow);
// Update the query with the limited scope
query = query.Where(q => q.Workflow.Contains(q));
}
return query.ToList();
}

Generating Checkbox Lists with MVC3 using a complex viewmodel and a cross table

I've inherited my first MVC project and it involves using MVC3 on top of Linq to SQL. I've been trying to find a way to generate a check box list based on a many to many relationship involving a cross table.
I have a systemFailureType table that maps to a SystemFailureProblem table via a cross table.
Here is my designer layout for the tables:
here my view model
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using XNet.Repository.Model;
namespace XNet.WebUI.Hotel.ViewModel
{
public class CheckFacilityVM
{
public int FacilityID { get; set; }
public string facilityName { get; set; }
public bool facilityAvailable { get; set; }
public virtual Facility facility { get; set; }
public virtual HotelFacility hotelfacility { get; set; }
}
}
here my controller
public ActionResult Facility()
{
ViewBag.hotel = _hotelService.GetByID(1).HotelName;
var model = db.Facilities
.Select(htl => new CheckFacilityVM
{
FacilityID = htl.FacilityID,
facilityName = htl.FacilityName,
facilityAvailable = htl.IsActive,
})
.ToList();
return View(model);
}
and here my constructor
public Facility ShowRoomFacility(int HotelID)
{
var x = (from d in db.Facilities
where d.FacilityID == HotelID
select d).FirstOrDefault();
return x;
}
how can i make this.....
I'll provide you a simple, more common example that you can adapt for your purposes - Users and Roles (a user can be assigned to many roles and likewise a role can have many users).
Assume we have an "Update User" form where we want to set the roles the user belongs to.
Here's what the controller/view model would look like:
public class UsersController : Controller {
[HttpGet]
public ActionResult Update(int id) {
var user = db.Users.Find(id);
var model = new UsersUpdateModel {
Name = user.Name,
SelectedRoles = user.Roles.Select(r => r.Id).ToList(),
Roles = GetRolesSelectList()
};
return View(model);
}
[HttpPost]
public ActionResult Update(UsersUpdateModel model) {
var user = db.Users.Find(model.Id);
var roles = db.Roles.ToList();
foreach (var role in roles) {
if (model.SelectedRoles.Contains(role.Id)) {
user.AddRole(role);
}
else {
user.RemoveRole(role);
}
}
}
public SelectList GetRolesSelectList() {
var roles = db.Roles.OrderBy(r => r.Name).ToList();
return new SelectList(roles, "Id", "Name");
}
}
public class UsersUpdateModel {
public int Id { get; set; }
public string Name { get; set; }
public IEnumerable<int> SelectedRoles { get; set; }
public SelectList Roles { get; set; }
}
Essentially you need to add a property to your view model to hold the available roles (in this example, "Roles") and one to hold the selected roles (in this example, "SelectedRoles").
In your POST action you can then load all the roles and, if the Id exists in UsersUpdateModel.SelectedRoles you add the role to the user, otherwise you remove it.
I tend to encapsulate the process of adding/removing the Role (or whatever collection it may be) in the side that owns the relationship - for example, User.AddRole would probably check to see if the role already exists to prevent adding it twice:
public void AddRole(Role role) {
var exists = this.Roles.FirstOrDefault(r => r.Id == role.Id);
if (exists == null) {
Roles.Add(role);
}
}
Finally to create the Checkbox list you can use the helper I created here. It would look something like:
#Html.CheckBoxListFor(model => model.SelectedRoles, Model.Roles)
That should give you enough to go on. Note that the code was written in notepad so is probably not copy/pastable.

Linq trowing Exceptions

I create a project where I use EF with LINQ and Model first. So, based on my edmx I created my Database and also my classes.
So I got some problems. I created a Click to test if my code is working.
protected void btnSearch_Click(object sender, EventArgs e)
{
ZUser Zusr = new ZUser();
List<ZUser> lst = Zusr.ListAll();
// Zusr.Id = 1;
string test = "";
foreach (ZUser item in lst)
{
test = item.Name;
}
lblName.Text = test;
}
So in my ZUser Class (Controller) I did the following code:
[Serializable]
public class ZUser : User
{
String connString = ConfigurationManager.ConnectionStrings["ConnectionString"].ToString();
public List<ZUser> ListAll()
{
List<ZUser> lstUser = new List<ZUser>();
using (DataContext db = new DataContext(connString))
{
Table<User> Users = db.GetTable<User>();
var query =
from usr in Users
where usr.Name == "Test"
select usr;
foreach (ZUser usr in query)
lstUser.Add(usr);
}
return lstUser;
}
}
And my Model (Class generated by my edmx)
namespace System.Model
{
//[Table]
public partial class User
{
public int Codigo { get; set; }
public string Name { get; set; }
public string LastName { get; set; }
public string Password { get; set; }
public DateTime Created { get; set; }
public DateTime LastLogin { get; set; }
}
}
Problems
If I don't let the [Table] in my Model class (I added that) I got this error. I'm not sure if this is the right way to correct it.
The type '{0}' is not mapped as a Table.
After "fixing" the problem from above. I got this new one in my foreach (ZUser usr in query).
The member '{0}.{1}' has no supported translation to SQL.
I don't know how to fix or create a workaround this one.
Amazing, this feature of linq!
Really intresting!
After some searches on msdn and test it in application,
maybe you miss the Column attribute over all single class members:
[Column(CanBeNull = false, DbType = "int")]
And maybe you must uncomment the Table attribute on top of User declaration
Hope this help!

Having issue while trying to pass two model to the same view at a time in mvc 3

I'm trying to create my profile type page for my simple blog site. I have two simple model class like this:
public class UserInfoModel
{
public string UserName { get; set; }
public string Email { get; set; }
public string Password { get; set; }
public string ConfirmPassword { get; set; }
}
public class NewPost
{
public string PostTitle { get; set; }
public string PostStory { get; set; }
}
I have created a joint model class of user & post to pass to view like this:
public class UserPostModel
{
public UserInfoModel User { get; set; }
public NewPost Post { get; set; }
}
The methods I wrote to retrieve the user & post info are like this:
public int GetUserID(string _UserName)
{
using (var context = new TourBlogEntities1())
{
var UserID = from s in context.UserInfoes
where s.UserName == _UserName
select s.UserID;
return UserID.Single();
}
}
public UserInfo GetUserDetails(int _UserID)
{
using (var context = new TourBlogEntities1())
{
var UserDetails = (from s in context.UserInfoes
where s.UserID == _UserID
select s).Single();
return UserDetails;
}
}
public Post GetUserPosts(int _UserID)
{
using (var context = new TourBlogEntities1())
{
var entity = (from s in context.Posts
where s.UserID == _UserID
select s).Single();
return entity;
}
}
And finally I'm calling all my method from my controller action like this:
[Authorize]
public ActionResult MyProfile()
{
var Business = new Business();
var UserID=Business.GetUserID(User.Identity.Name);
var UserEntity=Business.GetUserDetails(UserID);
var PostEntity=Business.GetUserPosts(UserID);
var model = new UserPostModel();
model.User.UserName = UserEntity.UserName; // problem showing here
model.User.Email = UserEntity.Email;
model.Post.PostTitle = PostEntity.PostTitle;
model.Post.PostStory = PostEntity.PostStory;
return View("MyProfile",model);
}
A run time error showing like " object is not referenced to a object type or null object". I worked ok in a very similar way while passing single model. Whats I'm doing wrong here?
Modified your UserPostModel
public class UserPostModel
{
public UserPostModel()
{
User = new UserInfoModel();
Post = new Post();
}
public UserInfoModel User { get; set; }
public NewPost Post { get; set; }
}
NOTE: check each value before set to model it should not be null.

What am i doing wrong? Entries are not getting logged into database

Here is Index:
It goes through folder and should add entries to database. But when i run it no entries are getting added. Is something wrong with this code?
(Basically code goes through couple folders gets image file and songs under the folder and add to database, but it is not working.)
public ActionResult Index()
{
DemoDb db = new DemoDb();
var movies = new List<SongModel>();
MovieModel movie = new MovieModel();
SongModel song = new SongModel();
//Function to get all the folders present in that particular location,Use
var folders = Directory.GetDirectories(Server.MapPath("~/Content/themes/base/songs"));
foreach (var folder in folders)
{
movie.MovieName = new DirectoryInfo(folder).Name;
string[] files = Directory.GetFiles(folder);
string img = string.Empty;
var list = new List<string>();
foreach (var file in files)
{
if (Path.GetExtension(file) == ".jpg" ||
Path.GetExtension(file) == ".png")
{
movie.Image = Path.Combine(Server.MapPath("~/Content/themes/base/songs"), file);
}
else
{
song.MovieId = movie.MovieId;
song.Song = Path.Combine(Server.MapPath("~/Content/themes/base/songs"), file);
}
}
db.movies.Add(movie);
db.songs.Add(song);
db.SaveChanges();
}
return View();
}
Here are classes and database design too:
public class MovieModel
{
[Key]
public int MovieId { get; set; }
public string MovieName { get; set; }
public int SongId { get; set; }
public string Image { get; set; }
}
public class SongModel
{
[Key]
public int SongId { get; set; }
public int MovieId { get; set; }
public string Song { get; set; }
}
You are creating only one movie and song above.
For every folder you overwrite the values in a movie object but never add a new object to your context, you only try to re-add your existing movie.
Also I wouldn't do this on an index HttpGet method. By the http spec a repeated GET call shouldn't change the system state each time (hence it should be idempotent)

Resources