In EF, there is no object that represents the bridge table. But I need to delete/add record to the bridge table so I want to create a class that represents the bridge table
For example:
User: userID, name
Group: groupID, name
tblUserGroup: userID, groupID (bridge table)
I have the following:
public class EFDbContext : DbContext
{
public DbSet<User> User { get; set; }
public DbSet<Group> Group { get; set; }
public DbSet<GroupUser> tblGroupUser { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<User>().ToTable("User");
modelBuilder.Entity<Group>().ToTable("Group");
modelBuilder.Entity<GroupUser>().HasKey(a => new { a.UserID, a.GroupID });
modelBuilder.Entity<Group>()
.HasMany(u => u.User)
.WithMany(g => g.Group)
.Map(m =>
{
m.MapLeftKey("GroupID");
m.MapRightKey("NetworkUserID");
m.ToTable("tblGroupUser");
});
}
}
When I want to use the bridge table
context.tblGroupUser...(do sth)
The error says
Invalid object name 'dbo.GroupUsers'.
I think its saying that it doesn't know which table GroupUser map to
But when I add
modelBuilder.Entity<GroupUser>().ToTable("tblGroupUser");
The error change to
Each EntitySet must refer to a unique schema and table.
Now it doesn't allow me to map tblGroupUser 2 times. How can I fix this problem
Other info
public class EFGroupkUser
{
private EFDbContext context = new EFDbContext();
public IQueryable<GroupUser> tblGroupUser
{
get { return context.tblGroupUser; }
}
}
If your problem is only about add/delete from the junction table, you can simply do it and you don't need to tweak mappings of the model:
// ...
User u = db.Users.Find(id);
foreach (var g in u.Groups.ToList())
db.tblUserGroups.Remove(g);
Collection<GroupUser> gusers = new Collection<GroupUser>();
foreach (var item in model.GroupUsers)
{
tblGroupUser guo = new tblGroupUser();
guo.UserID = item.UserID;
guo.GroupID = item.GroupID;
gusers.Add(guo);
}
u.Groups = gusers;
db.SaveChanges();
You can perform any add/delete you want to a junction table like the above code. If your problem is just that, let the EF does its job by conventions and remove OnModelCreating
This question may be duplicate from this question.
If you want to add a group to a specific user.
var myUser = dbContext.Users.Find(id);
var myGroup= db.Groups.Single(group => group.GroupId == groupId);
myUser.Groups.Add(myGroup);
This will insert a record to tblUserGroup table.
You can do the vise versa too.
If you want to add a user to a specific group.
myGroup.Users.Add(myUser);
But if you really want tblUserGroup table to be generated in the edmx, just add an aditional column (Let's say IsActive) to the tblUserGroup table and then update the edmx. Now you can use the tblUserGroup class.
Related
I've been scratching my head and reading tons of posts but couldn't figure this out.
I have a Person table (see following). Entities in that table can have Children and Parents, who also are in the same table.
public class Person
{
public int ID { get; set; }
public int Name { get; set; }
public virtual ICollection<Person> Children { get; set; }
public virtual ICollection<Person> Parents { get; set; }
}
My model builder in DataContext is like this :
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Person>()
.HasMany(c => c.Children)
.WithMany(p => p.Parents)
.Map(m=>
{
m.ToTable("ParentChild");
m.MapLeftKey("ParentID");
m.MapRightKey("ChildID");
});
}
This works fine and automatically creates a table named ParentChild with ParentID and ChildID.
Now the thing is, I want to delete a Person who has children, but without deleting the children. So I guess I need to also delete the corresponding line in my ParentChild table (which is auto-generated), but don't know how to do that.
Note : I've also tried to explicitly create the ParentChild table, but this doesn't work, it's rejected when doing add-migration.
To give you more information, here is the error I get when I try to delete one of the row in Person :
The DELETE statement conflicted with the REFERENCE constraint
"FK_dbo.PersonPerson_dbo.Person_Person_ID". The conflict occurred
in database "MyDatabase", table "dbo.ParentChild", column 'ParentID'.
(By the way, I don't know why it talks about a PersonPerson database since I give my auto-generated relation database the name ParentChild. But even if I don't give it a name, it doesn't work).
Last thing, here's the Remove() stub in PersonController (I left it as it was originally made by Scaffolding) :
public ActionResult Delete(int id)
{
try
{
Person person = db.Persons.Find(id);
db.Persons.Remove(person);
db.SaveChanges();
}
catch (RetryLimitExceededException dex)
{
return RedirectToAction("Delete", new { id = id, saveChangesError = true });
}
return RedirectToAction("Index");
}
I hope someone can help me!
Thanks in advance for your help and cheers,
Marius
OK, I finally managed to figure this out.
The solution is modifying the Delete() stub in PersonController, like this :
using (var context = new DataContext())
{
Person person= context.Persons.Find(id);
foreach (var child in person.Children.ToList())
{
person.Children.Remove(child);
}
foreach (var parent in person.Parents.ToList())
{
person.Parents.Remove(parent);
}
context.Persons.Remove(patient);
context.SaveChanges();
}
This way, the Delete() method deletes references to Parents and Children, and reference only. And as it does it at the same time when I call SaveChanges, it avoids trouble with ForeignKey constraints!
Hope this helps someone.
Cheers!
I have problem with updating entites that have many-to many relationship. Below my User and category class:
public class User : IEntity
{
[Key]
public virtual long Id { get; set; }
private ICollection<Category> _availableCategories;
public virtual ICollection<Category> AvailableCategories
{
get { return _availableCategories ?? (_availableCategories = new List<Category>()); }
set { _availableCategories = value; }
}
}
public class Category : IEntity
{
[Key]
public long Id { get; set; }
/// <summary>
/// Full name or description of a category
/// </summary>
[StringLength(255)]
public string FullName { get; set; }
}
This is code snippet from my repository
public override void Edit(User user)
{
var dbUser = _context.Users.Include(x => x.AvailableCategories)
.Single(x => x.Id == user.Id);
var categories = _context.Categories;
dbUser.AvailableCategories.Clear();
foreach (var cat in user.AvailableCategories)
{
dbUser.AvailableCategories.Add(cat);
}
_context.Entry(dbUser).State = EntityState.Modified;
}
However the categories don't get updated. What EF does is insert empty rows into category table and sets relations to this new rows with user.
How can I update User so that I change only categories that already exist in the database?
User that I pass to Edit method has AvailableCategories with only Ids set (rest of properties are empty).
When you're doing something like posting back M2M relationships, you either must post the full object, as in every single property on those objects, or simply post a list of ids and then use those to query the associated objects back from the database. Otherwise, Entity Framework understands your purpose to be to update the properties on the objects as well, in this case with empty values.
Obviously the first option is quite unwieldy, so the second way is the preferred and standard way. Generally, for this, you'd want to use a view model so you could have a property like the following, that you would post into:
public List<long> SelectedCategories { get; set; }
But, if you insist on using the entity directly, you can get much the same result by simply doing:
var selectedCategories = user.AvailableCategories.Select(m => m.Id)
Once you have the ids:
var newAvailableCategories = _context.Categories.Where(m => selectedCategories.Contains(m.Id));
And then finally set that on your user:
dbUser.AvailableCategories = newAvailableCategories;
I notice you are also adding the user.AvailableCategories directly into dbUser.AvailableCategories. I've noticed when binding back complex objects from an MVC view that DB Entities are no longer attached to the DbContext. If you look at the entity, you can verify by checking dbContext.Entry(cat).State is "detached" (or something unexpected) I believe.
You must query those entities back out of the dbContext (possibly by using the returned cat.Id's). Or otherwise manually set the entities as "unchanged". And then add those "non-detached" items into dbUser.AvailableCategories. Please see Chris's answer as it shows with specific code how to get this done.
Also, I might use a linking entity. Possibly something like this:
public class UserCategory
{
public User User {get;set;}
public Category Category {get;set;}
}
And add it to DB context. Also, drop the linking lists in your current User and Category class. This way you can manipulate the UserCategory class (and DbSet) to manage your many-to-many relationship.
I am using C#, MVC3, EF5, SQL Server 2008 R2.
I have an intersection table ie
Lecturer -< LecturerCourse >- Course
The list of Lecturers are populated.
When I add a course, it would be neat to have a list of Lecturers that I could select from, that teach the course in question. When I save the new Course record, this multiselect also should save its data back to the "LecturerCourse" table via Model Binding.
I am using EF5.
Can you recommended a simple and standard approach to solving CRUD for a join, ie "LecturerCourse", table? I have looked online, but some of the approaches seem very complicated.
Many thanks.
Alright, it's going to be a long one. To allow this to happen in "one page" (through POST, or you could use Ajax, technically), you need a combination of a Get and Post version of the method and to construct your view model correctly. Below are the classes that I will use for demonstration purposes:
public class NewCourse
{
[Required]
public string Name { get; set; }
// And your other properties
public int[] LecturerIds { get; set; }
}
public class ViewLecturer
{
public int Id { get; set; }
public int Name { get; set; }
}
public class NewCourseViewModel
{
public NewCourse Course { get; set; }
public IEnumerable<ViewLecturer> Lecturers { get; set; }
}
NewCourseViewModel will be the model for the View (see below). ViewLecturer will give you a lighter mapping between your available Lecturer and the information required to Add to them.
As for the Controller:
public class CourseController : Controller, IDisposable
{
private Lazy<YourContext> lazyContext =
new Lazy<YourContext>(() => new YourContext());
private YourContext Context
{
get { return lazyContext.Value; }
}
public ActionResult New()
{
var model = new NewCourseViewModel {
Course = new NewCourse(),
Lecturers = Context.Lecturers
.Select(l => new ViewLecturer { Id = l.Id, Name = l.Name })
};
return View(model);
}
[HttpPost]
public ActionResult New(NewCourse course)
{
if(ModelState.IsValid)
{
var lecturers = course.Lecturers
.Select(l => new Lecturer { Id = l.Id })
.ToList();
foreach(var lecturer in lecturers)
Context.Lecturers.Attach(lecturer);
var newCourse = new Course {
Name = course.Name,
// ... and the rest of the mapping
Lecturer = lecturers
};
context.Courses.Add(newCourse);
context.SaveChanges();
// Could have to handle DbUpdateException if you want
return RedirectToAction(...);
}
return View(new NewCourseViewModel {
Course = course,
Lecturers = Context.Lecturers
.Select(l => new ViewLecturer { Id = l.Id, Name = l.Name })
});
}
public void Dispose()
{
if(lazyContext.IsValueCreated)
lazyContext.Value.Dispose();
}
}
Your first New method will give you the entry point for your Course creation page. The rest of the validation and actual adding will be done through the [HttpPost]overload. As for your View (that should be in the ~/Views/Course/New.cshtml):
#model NewCourseViewModel
// ... Then when you are ready to begin the form
#using(Html.BeginForm("New", "Course", FormMethod.Post))
{
// Your List of Lecturers
#Html.ListBoxFor(m => m.Course.LecturerIds,
new MultiSelectList(
Model.Lecturers,
"Id",
"Name",
m.Course.LecturerIds ?? new int[0]
))
// Your Other Model binding
}
When the submit button will be pressed, the action matched will be the New(NewCourse course). The names are important because of the way the HtmlHelpers generate their Ids. Because we are only included one property of the whole view model, it will match the parameter name course based on the view model's Course property. You will get a list of Ids for the Lecturers which you will be able to use to attach to the DbContext and add directly to the new Course model (Entity Framework will do the rest). In cases where there was a problem, we can get back the list of lecturers and re-use the same NewCourse in the view model.
Now this is example is very basic but it should give you a good starting point as to how you can structure your view model.
I am using EF4 CTP5 to try to persist a POCO object that is split among two tables, the link being the ContactID. When i save a contact, i want the core contact information saved in one table (Contacts), and the link to the user who owns the contact saved in another (UserToContacts). I have the custom mapping defined below, but when I SaveChanges, i get the following error:
A value shared across entities or associations is generated in more than one location. Check that mapping does not split an EntityKey to multiple store-generated columns.
Any ideas would be greatly appreciated!
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
/// Perform Custom Mapping
modelBuilder.Entity<Contact>()
.Map(mc =>
{
mc.Properties(p => new
{
p.ContactID,
p.FirstName,
p.MiddleName,
p.LastName
});
mc.ToTable("Contacts");
})
.Map(mc =>
{
mc.Properties(p => new
{
p.ContactID,
p.UserID
});
mc.ToTable("UserToContacts");
});
}
This is a bug that EF team have fixed it in their code base after CTP5 was released. Entity splitting should only result in identity being used on one of the tables but CTP5 configures it for all tables (if you look into your tables you'll see that ContactID is configured as an identity column in both).
The workaround for now is to not using identity with table splitting at all:
public class Contact
{
[DatabaseGenerated(DatabaseGenerationOption.None)]
public int ContactID { get; set; }
public string FirstName { get; set; }
public string MiddleName { get; set; }
public string LastName { get; set; }
public int UserID { get; set; }
}
Which means you are responsible to provide valid PKs when creating a new Contact object.
Just don't specify the ID, it will be added automatically
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
/// Perform Custom Mapping
modelBuilder.Entity<Contact>()
.Map(mc =>
{
mc.Properties(p => new
{
p.FirstName,
p.MiddleName,
p.LastName
});
mc.ToTable("Contacts");
})
.Map(mc =>
{
mc.Properties(p => new
{
p.UserID
});
mc.ToTable("UserToContacts");
});
}
I'm using EF4 CTP5 code first approach but am having trouble getting it to work. I have a class called "Company" and a database table called "CompanyTable". I want to map the Company class to the CompanyTable table, so have code like this:
[Table(Name = "CompanyTable")]
public class Company
{
[Key]
[Column(Name = "CompanyIdNumber", DbType = "int")]
public int CompanyNumber { get; set; }
[Column(Name = "CompanyName", DbType = "varchar")]
public string CompanyName { get; set; }
}
I then call it like so:
var db = new Users();
var companies = (from c in db.Companies
select c).ToList();
However it errors out:
Invalid object name 'dbo.Companies'.
It's obviously not respecting the Table attribute on the class, even though it says here that Table attribute is supported. Also it's pluralizing the name it's searching for (Companies instead of Company.) How do I map the class to the table name?
on you class that inherits from DbContext, override the OnModelCreating method
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Company>().ToTable("dbo.CompanyTable");
}
Forgot to add a reference to the ctp5 dll to my schemas project, it was using System.Data.Linq.Mapping instead.