I have two models:
public partial class User
{
public int UserID { get; set; }
public string FullName { get; set;}
public int GroupID { get; set; }
public virtual Group Group {get; set;}
}
public partial class Group
{
public int GroupID { get; set;}
public string GroupName { get; set;}
}
Whenever I create a new user in the UI, I will also assign the group to the user through the dropdown list. I will retrieve the group entity from the selected value in the dropdown and assign it to the navigation property when creating the user entity.
groupEntity = { GroupID: 1, GroupName: ABCD }; // group entity retrieved from dropdown
manager.createEntity('User' {
FullName: 'Boon',
Group: groupEntity
});
The following returns me an error.
An Entity cannot be attached to an entity in another EntityManager. One of the two entities must be detached first.
So I detached the entity from the breeze manager that retrieves it and it returns me another error when creating.
Uncaught Error: Cannot attach this entity because the EntityType (Group:#Data) and MetadataStore associated with this entity does not match this EntityManager's MetadataStore.
Entity Framework Code
readonly EFContextProvider<EFEntities> _context = new EFContextProvider<EFEntities>();
[HttpGet]
public string Metadata()
{
return _context.Metadata();
}
[HttpGet]
public IQueryable<Data.User> GetUsers()
{
return _context.Context.Users
.Include("Group");
}
[HttpPost]
public SaveResult SaveChanges(JObject user)
{
return _context.SaveChanges(user);
}
Any advice on how do I assign the navigation property to a preset values when adding a new entity?
Related
I am building a blazor server app using a Syncfusion DataGrid and am having problems updating ForeignKeys that have Navigation Properties. I'm using .Net 6 with Entity Framework Core.
The steps to describe the problem are as follows:
Blazor Page with DataGrid is loaded and initialised with Control Account Data
A record is edited by the user using a Foreign Key Combo Box bound to OBSID_FK. The value is changed from 5 to 10. At this point, the navigation property OBSID is out of date and reflects the old record from OBSID_FK = 5. (Note that the record is not attached to the context at this point as it's in the grid, so change tracker does not detect the change.)
User presses save and retrieve changed records from the grid, ready to save
Create a new context, attach the changed record to the context and save
The problem comes when attaching the disconnected changed record to the context. EFCore detects that there is a mismatch between the FK and Navigation property and automatically resets the FK to from 10 to the original value 5.
My question is: Is it possible to tell EFCore to use the FK value,
rather than the NavigationProperty when resolving conflicts on attaching
disconnected entities to the Context?
Following are selected code extracts.
public partial class ControlAccount
{
[Key]
public Guid GUID {get; set;}
public int ID {get; set;}
public string? Code {get; set;}
public string? Description {get; set;}
public int? OBSID_FK {get; set;}
[ForeignKey("OBSID_FK")] public virtual OBSID? OBSID {get; set;}
}
public partial class OBSID
{
[Key]
public int ID { get; set; }
public string? Code { get; set; }
public string? Description { get; set; }
public string? Comments { get; set; }
}
public class Context :DbContext
{
public Context() : base(GetOptions())
{
}
private static DbContextOptions GetOptions()
{
var options = new DbContextOptionsBuilder().UseSqlServer(XXXXXXXXXXXXX).Options;
return options;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseLazyLoadingProxies();
}
public DbSet<ControlAccount> ControlAccounts { get; set; }
public DbSet<OBSID> OBSID { get; set; }
}
public class ControlAccountPageTest
{
public void Test()
{
//Retrieve record from db for loading into blazor page
var db = new Context();
var controlAccount = db.ControlAccounts.Include(x => x.OBSID).FirstOrDefault(); //ensure that proxy is loaded
//simulate changing a foreign key value in a drop down box
controlAccount.OBSID_FK = 10; //Change from 5
//simulate retrieving changed record from grid, adding to Context and Saving
using (var db1 = new Context())
{
db1.Update(controlAccount); //***PROBLEM OCCURRS HERE. FOREIGN KEY OBSID_FK = 10 IS OUT OF SYNC WITH NAVIGATION PROPERTY OBSID. EFCORE AUTOMATICALLY RESETS OBSID_FK TO THE NAVIGATION PROPERTY VALUE OF 5
db1.SaveChanges();
}
}
}
I am using ASP.NET Identity 2.2.0 with ASP.NET MVC 5.2.3 and Entity Framework 6.1.2.
I added a new property and its corresponding table to my database using ASP.NET Identity with Code First like so:
public class ApplicationUser
{
[ForeignKey("UserTypeId")]
public UserType Type { get; set;}
public int UserTypeId { get; set;}
}
public class UserType
{
[Key]
public int Id { get; set;}
public string Name { get; set; }
}
Now, from some action, when I call:
var user = UserManager.FindByNameAsync(userName);
It does get the user with the correct UserTypeId because that is a primitive, but it does not get the UserType property of the ApplicationUser class.
If I were not using this abstraction, I would either call LoadProperty<T> or the Include method in Entity Framework to include the navigational property or relation named Type (of type UserType) on the ApplicationUser class.
How do I do that with ASP.NET Identity's UserManager? I suspect the only way would be to override this method in my custom UserManager derived class and do it myself?
With Entity Framework lazy loading, you need to ensure that your navigation properties are marked as virtual.
public class ApplicationUser
{
[ForeignKey("UserTypeId")]
public virtual UserType Type { get; set;}
public int UserTypeId { get; set;}
}
Alternatively if you are unable/don't want to use lazy loading, then you can still use your context as you would any other entity:
var user = context.Users.Include(u => u.Type).Single(u => u.UserName == userName);
I'm creating a simple application for university where a student can make some type of request which is then processed by an employee of particular speciality.
I would like to use default MVC5 identity system and to extend the ApplicationUser class using TPH pattern. So I added the common properties to the ApplicationUser:
public class ApplicationUser : IdentityUser
{
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
[Required]
public string Email { get; set; }
}
then I created two classes which inherits the ApplicationUser:
public class Student : ApplicationUser
{
public string PersonalNumber { get; set; }
public bool Monitor { get; set; }
public virtual Group Group { get; set; }
public virtual ICollection<Request> Requests { get; set; }
}
public class Employee : ApplicationUser
{
public virtual EmployeeSpeciality EmployeeSpeciality { get; set; }
public virtual ICollection<Request> Requests { get; set; }
}
what I currently want is to make both types of users register as a base Identity and to keep both in a single table as in the inheritance example on asp.net
As I thought, it would be enough to initialize user var in AccountController which is then passes to the UserManager as a Student or as an Employee. But after trying to register as a Student i'm getting this exception:
Exception Details: System.Data.SqlClient.SqlException: Invalid column name 'PersonalNumber'.
Invalid column name 'Monitor'.
Invalid column name 'EmployeeSpeciality_Id'.
Invalid column name 'Group_Id'.
My context class:
public class EntityContext : IdentityDbContext
{
public EntityContext()
: base("DbConnection")
{
}
public DbSet<ApplicationUser> ApplicationUsers { get; set; }
public DbSet<Student> Students { get; set; }
public DbSet<Employee> Employees { get; set; }
...
}
and a part of controller's action:
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new Student()
{
UserName = model.UserName,
FirstName = model.FirstName,
LastName = model.LastName,
Email = model.Email
};
var result = await UserManager.CreateAsync(user, model.Password);
I tried setting ApplicationClass to an abstract class, but no luck. Any help would be appreciated.
UPDATE: The problem wasn't in the code itself. I simply haven't dropped (or updated) the database after making these changes to the model. After it everything works just fine.
#Dragonheart: I tried this repro and it would work fine if you remove the DBSet declarations in you context class. The IdentityDbContext would handle you TPH pattern and add a Discriminator column in the table to differentiate the child classes.
As ApplicationUser is inherited from IdentityUser, remove it from your DbContext class. On the other hand, there is no need to create an abstract class (you can create if you prevent from creating a user except from Student or Employee classes. For more information you might have a look at Table-per-Hierarchy.
For Register part, try something like that:
Student user = new Student
{
UserName = model.UserName,
FirstName = model.FirstName,
LastName = model.LastName,
Email = model.Email
};
var result = UserManager.Create(user, model.Password);
Hope this helps...
I have two models defined as follows:
public class Division
{
public int DivisionID { get; set; }
[Required]
public string DivisionName { get; set; }
public virtual Employee Contact{ set;get; }
public virtual ICollection<Employee> Employees { get; set; }
}
public class Employee
{
public int EmployeeID{ get; set; }
public string Name{ get; set; }
public virtual Division Division{set;get;}
}
Entity framework sets a field in division table called employee_employeeid, how can I create dropdown for employees for the contact attribute in the division table.
Here is what I have tried but nothing is being sent to the database.
ViewBag.contact = new SelectList(db.Employees,"EmployeeID","Name");
In the view I have:
#Html.DropDownList("contact",String.Empty)
Is there a naming convention I have to use?
Edit
POST action:
[HttpPost]
public ActionResult Edit(Division division)
{
if (ModelState.IsValid)
{
db.Entry(division).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.employeeid = new SelectList(
db.Employees, "EmployeeID", "EmployeeFirstName", division.employee);
return View(division);
}
I would recommend actually using ViewData instead of the ViewBag. In your controller have the following:
var employees = db.Employees.Select(e => new DropDownItem{ Text = e.Name, Value = e.EmployeeID });
ViewData["Employees"] = employees;
Then, in your view, have the following to display it:
#Html.DropDownList("Contact", ((IEnumerable<DropDownItem>)(ViewData["Employees"])))
I would suggest that you expose a foreign key property in your model. It makes the binding to the dropdown list and the later update much easier. Your Division model would look like this:
public class Division
{
public int DivisionID { get; set; }
[Required]
public string DivisionName { get; set; }
[ForeignKey("Contact")]
public int ContactId { set;get; }
public virtual Employee Contact { set;get; }
public virtual ICollection<Employee> Employees { get; set; }
}
Then in the Edit GET action you fill the ViewBag as you did:
ViewBag.Contacts = new SelectList(
db.Employees, "EmployeeID", "Name", division.ContactId);
You have a strongly typed view with the Division as model:
#model MyNamespace.Division
In this view you can bind the dropdown list to the ContactId property:
#Html.DropDownListFor(model => model.ContactId, ViewBag.Contacts)
Your POST action can be similar to your current version:
[HttpPost]
public ActionResult Edit(Division division)
{
if (ModelState.IsValid)
{
db.Entry(division).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.Contacts = new SelectList(
db.Employees, "EmployeeID", "Name", division.ContactId);
return View(division);
}
Note, that it is in many cases the better practice (especially for security reasons) to use a special ViewModel instead of a database entity for your views. You could then incorporate the Contacts collection into your ViewModel (instead of using the ViewBag). To update the entity in the database you would load it and write the changed properties from the ViewModel to the entity and then save it.
Although the link tables which facilitate a many-to-many relationship are usually hidden by EF, I have an instance where I think I need to create (and manage) one myself:
I have the following entities:
public class TemplateField
{
public int Id
{
get;
set;
}
[Required]
public string Name
{
get;
set;
}
}
public class TemplateFieldInstance
{
public int Id
{
get;
set;
}
public bool IsRequired
{
get;
set;
}
[Required]
public virtual TemplateField Field
{
get;
set;
}
[Required]
public virtual Template Template
{
get;
set;
}
}
public class Template
{
public int Id
{
get;
set;
}
public string Name
{
get;
set;
}
public virtual ICollection<TemplateFieldInstance> Instances
{
get;
set;
}
}
So essentially; a Template can have many TemplateField and a TemplateField can have many Template.
I believe I could just add a navigation property in the form of a collection of Template items on the TemplateField entity and have EF manage the link entity, but I need to store some additional information around the relationship, hence the IsRequired property on TemplateFieldInstance.
The actual issue I'm having is when updating a Template. I'm using code similar to the following:
var template = ... // The updated template.
using (var context = new ExampleContext())
{
// LoadedTemplates is just Templates with an Include for the child Instances.
var currentTemplate = context.LoadedTemplates.Single(t => t.Id == template.Id);
currentTemplate.Instances = template.Instances;
context.Entry(currentTemplate).CurrentValues.SetValues(template);
context.SaveChanges();
}
However; if I try and update a Template to - for example - remove one of the TemplateFieldInstance entities, it this throws an exception (with an inner exception) which states:
A relationship from the 'TemplateFieldInstance_Template'
AssociationSet is in the 'Deleted' state. Given multiplicity
constraints, a corresponding 'TemplateFieldInstance_Template_Source'
must also in the 'Deleted' state.
After doing some research, it sounds like this is because EF has essentially marked the TemplateFieldInstance foreign key to the Template as being null and then tried to save it, which would violate the Required constraint.
I'm very new to Entity Framework, so this is all a bit of a journey of discovery for me, so I'm fully anticipating there being errors in my approach or how I'm doing the update!
Thanks in advance.
You must map the relationships in your model as two one-to-many relationships. The additional field in the link table makes it impossible to create a many-to-many relationship. I would also recommend to use a composite key in your "link entity" TemplateFieldInstance where both components are foreign keys to the other entities. This ensures in the database that you can only have one row for a unique combination of a template field and a template and comes closest to the idea of a "many-to-many link table with additional data":
public class TemplateField
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
public virtual ICollection<TemplateFieldInstance> Instances { get; set; }
}
public class TemplateFieldInstance
{
[Key, Column(Order = 0)]
public int FieldId { get; set; }
[Key, Column(Order = 1)]
public int TemplateId { get; set; }
public bool IsRequired { get; set; }
public virtual TemplateField Field { get; set; }
public virtual Template Template { get; set; }
}
public class Template
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<TemplateFieldInstance> Instances { get; set; }
}
EF naming conventions will detect the FK relations in this model if you use the property names above.
More details about such a model type are here: https://stackoverflow.com/a/7053393/270591
Your approach to update the template is not correct: context.Entry(currentTemplate).CurrentValues.SetValues(template); will only update the scalar fields of the template, not the navigation properties nor will it add or remove any new or deleted child entities of the parent entity. Unfortunately updating detached object graphs doesn't work that easy and you have to write a lot more code, something like this:
var template = ... // The updated template.
using (var context = new ExampleContext())
{
// LoadedTemplates is just Templates with an Include for the child Instances.
var currentTemplate = context.LoadedTemplates
.Single(t => t.Id == template.Id);
context.Entry(currentTemplate).CurrentValues.SetValues(template);
foreach (var currentInstance in currentTemplate.Instances.ToList())
if (!template.Instances.Any(i => i.Id == currentInstance.Id))
context.TemplateFieldInstances.Remove(currentInstance); // DELETE
foreach (var instance in template.Instances)
{
var currentInstance = currentTemplate.Instances
.SingleOrDefault(i => i.Id == instance.Id);
if (currentInstance != null)
context.Entry(currentInstance).CurrentValues.SetValues(instance);
// UPDATE
else
currentTemplate.Instances.Add(instance); // INSERT
}
context.SaveChanges();
}
A similar example with more comments what is happening is here: https://stackoverflow.com/a/5540956/270591