I have an MVC site using code first migrations which contains an Object called "Organisation" as below:
public class Organisation
{
public int Id { get; set; }
public string Name { get; set; }
public string Code { get; set; }
public virtual ICollection<Contact> Contacts { get; set; }
public virtual ICollection<Location> Locations { get; set; }
public UserPermissions Permissions { get; set; }
}
And I am trying to implement user permissions for each of the areas on my site. As a result the Permissions property has been newly added above and is an object of type UserPermissions:
public class UserPermissions
{
public PermissionLevel Contacts { get; set; }
public PermissionLevel Messages { get; set; }
public PermissionLevel Groups { get; set; }
public PermissionLevel Data { get; set; }
}
Where PermissionLevels is an enum defined as:
public enum PermissionLevel
{
Locked = 0,
View = 1,
Administrator = 2
}
I feel the structure of this implementation is fine and upon adding the migration EF creates a column in my dbo.Organisations table for each permission type (Permissions_Contacts, Permissions_Messages etc.).
The database however already has many Organisations and I wondered if there was a way of imposing a default. If I updated my database now all Permissions would be 0 (Locked), however I'd like the default to be different for each Permissions category e.g. Administrator rights for Contacts/Messages/Groups and Locked rights for Data because Data will be set up as and when a user requests it.
I will soon add functionality to the Admin tier of the site where the Organisations are created, and make the selection of UserPermissions mandatory for all areas, but I'd prefer not to have to go back through and manually change all permissions of the existing Organisations to be my defaults.
Is there a way of automatically imposing these defaults for the existing organisations within the database?
Could you just set your default permissions in the constructor of your Organisation or UserPermissions object?
Something like:
public UserPermissions()
{
Contacts = PermissionLevel.Locked;
// etc
}
or
public Organisation()
{
this.Permissions = new UserPermissions();
if (this.Id == 0) // new object not yet persisted
{
this.Permissions.Contacts = PermissionLevel.Locked;
// etc
}
}
Based on comments:
For filling the existing data that is now out of sync with your model, you'll want to update your migration script to populate the new properties in your "up" script (the "down" script should not need modification I wouldn't think).
Basically in your up script you can either write update sql statements, or iterate through the objects via the context to manipulate their values in a more strongly typed manner.
Here's some information on getting started with migration scripts:
http://www.asp.net/mvc/overview/getting-started/getting-started-with-ef-using-mvc/migrations-and-deployment-with-the-entity-framework-in-an-asp-net-mvc-application
I think your Up method could be modified to have something like this near the end:
using (MyContext context = new MyContext)
{
var orgs = context.Organisation;
foreach (Organization org in orgs)
{
org.Permissions = new UserPermissions()
{
Contacts = PermissionLevel.Locked,
// etc
}
}
context.SaveChanges();
}
Related
I have this weird behaviour when it comes to creating a new users from scratch(registration) and creating new ones from a logged in user that doesn't depend on the DB underneath(in this case I'm using postgreSQL).
But first here's the relationship:
Now both at registration time and logged-in time I do use UserManager for the creation of the users.
First I create a new UserSetting, SaveChanges() and follow by assigning to the AppUser property UserSettingId the value of the Id of the new created entity.
Here's where it fails depending if you are already a registered and logged-in user or you are a new registering user, the code doesn't change:
var userSettings = await _userService.UserSettings.AddAsync(new UserSettings
{
LanguageId = langId,
ThemeId = themeId
});
//wrapper to the Context.SaveChangesAsync()
//failure point!
if (!await _userService.SaveChangesAsync<bool>())
And the exception error(at the creation of a new user from an already logged-in user) talks about a PK_LanguageId already existing as if I was creating a new Language entity but, as you can see, I'm just assigning just a default langId to the new UserSetting. Here's the exception screenshot:
(sorry for the italian, it does auto-translate)
Short translation: duplicated key for Languages, as if it was trying to insert also a new Language entity.
And at the Startup the registered service(_userService) is transient.
And here's the model classes and their configuration in OnModelCreating:
public class AppUser : IdentityUser
{
public long UserSettingsId { get; set; }
public virtual UserSettings UserSettings { get; set; }
//...other properties
}
public class UserSettings
{
public long Id { get; set; }
public int LanguageId { get; set; }
public virtual Language Language { get; set; }
public int ThemeId { get; set; }
public virtual Theme Theme { get; set; }
public virtual AppUser AppUser { get; set; }
}
public class Theme
{
[Key]
public int Id { get; set; }
[StringLength(10)]
public string Type { get; set; }
}
public class Language
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
[MaxLength(2)]
public string Code { get; set; }
}
builder.Entity<AppUser>().HasKey(u => u.Id);
builder.Entity<AppUser>().HasOne(u => u.UserSettings).WithOne(us => us.AppUser);
builder.Entity<UserSettings>().HasKey(us => us.Id);
builder.Entity<UserSettings>().Property(us => us.Id).ValueGeneratedOnAdd();
builder.Entity<UserSettings>().HasOne(us => us.Language);
builder.Entity<UserSettings>().HasOne(us => us.Theme);
What I'm doing wrong? Why it does behave so differently? Is there a different way to create users from an already logged-in user?
The problem might be that you are adding object to the database context which marks it as added. Because database context isn't aware of existence Language and Theme objects it is trying to insert all of them.
It may be issue with postgreSQL provider. You might want to check opened issues.
I'd suggest to try loading Language object from database first and then try to add new UserSettings.
Found the source of the behaviour, it's the damn UserManager.GetUserAsync(), which I user to get the currently logged-in user with added UserSettings later on to propagate the "Admin"'s settings to the new creating account.
GetUserAsync(User) does return an entity that does keep being tracked(instead of, as I thought, being like AsNoTracking()). So consequently when I did fill it's UserSetting I did in fact added a new UserSetting with new sub classes properties!
Sooo, never expect UserManager give you the user without tracking!
Thanks for everyone involved with the effort.
I have asp.net web api application. I have the table Companies in the databse which have two fields: id and description. Recently I've updated the database and added a new column called CustomerID. After that when I am trying to call getCompanies
private readonly BackendContext _context;
public CompaniesController(BackendContext context)
{
_context = context;
}
// GET: api/Companies
[HttpGet]
public IEnumerable<Company> GetCompanies()
{
return _context.Companies;
}
I get
I think the controller tries to return the old companies model but can't achieve it because it doesnt exist now but I don't know how to fix this though the controller should return the updated model. Maybe I should somehow rebuild the app to make it use the updated version?
Additional code:
Context
public class BackendContext : Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityDbContext<IdentityUser>//DbContext
{
public BackendContext(DbContextOptions<BackendContext> options) : base(options) { }
public DbSet<Company> Companies { get; set; }
public DbSet<CompanyToProduct> CompanyToProducts { get; set; }
public DbSet<Product> Products { get; set; }
public DbSet<Customer> Customers { get; set; }
public DbSet<Vendor> Vendors { get; set; }
public DbSet<VendorToProduct> VendorToProducts { get; set; }
public DbSet<Invoice> Invoices { get; set; }
public DbSet<InvoiceItem> InvoiceItems { get; set; }
}
Model
public class Company
{
public int ID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public int CustomerID { get; set; }
public virtual Customer Customer { get; set; }
public virtual ICollection<CompanyToProduct> CompaniesToProducts { get; set; }
public virtual ICollection<Invoice> Invoices { get; set; }
}
UPDATE
I've added some values to the table and I got the response of the first company:
[{"id":1,"name":"Google","description":"free food","customerID":6,"customer":null,"companiesToProducts":null,"invoices":null}
BUT I also got the fields which is not specified in the table: customer, companiesToProducts,invoices. Invoices and companiesToProducts are tables in my database and I don't know what is customer referred to. I should also mention that these tables are connected by foreign key.
UPDATE
Error:
Based on the comments on the question above, it sounds like the related tables are all trying to serialize and the overall process is failing likely due to circular references in the object graph. This comment above in particular hints at a solution:
I want to return only the data about companies but the controller also returns another fields like customer, companiesToProducts,invoices
While it's convenient to just return directly from the data context, this has the added side-effect of coupling the API with the database (and with the data access framework, which appears to be the issue here). In API design in general it's always a good idea to explicitly define the "shape" of that API. The fields to return, etc.
Project your result into an explicitly defined shape and return only what you want to return:
var result = _context.Companies
.Select(c => new
{
c.ID,
c.Name,
c.Description,
c.CustomerID
})
.ToList();
This defines specifically what you want to return, fetches only that information from the backing data, materializes it into an in-memory list, and finally then returns it through the API.
There is a potential downside to this, however. Because now we also need to change the return type of your API method. There are a couple options there, such as returning a generic response object or creating a view model which closely approximates your already existing model and starts to feel like duplication.
As with just about anything, it's a balance. Too far in any one direction and that direction starts to become a problem. Personally I often go the route of defining a view model to return:
public class CompanyViewModel
{
public int ID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public int CustomerID { get; set; }
}
and returning that:
return _context.Companies
.Select(c => new CompanyViewModel
{
ID = c.ID,
Name = c.Name,
Description = c.Description,
CustomID = c.CustomerID
})
.ToList();
But the reason I normally do this is because I normally work in an environment where the web application is just one application attached to a common shared business domain, so the view models don't feel like code duplication. They're in a separate project, often take a different shape than the backing data objects, etc. But if your domain models are already in your web project and that's the only project you have, there's a strong desire to want to return those.
Another option when that's the case could be to universally set your JSON serialization to ignore circular references:
services.AddMvc()
.AddJsonOptions(
options => options.SerializerSettings.ReferenceLoopHandling
= Newtonsoft.Json.ReferenceLoopHandling.Ignore );
But do keep in mind that this still couples your API to your DB models. Maybe that's okay in this project, but if you ever add a column to your DB that you don't want users to see then it becomes an issue. As with anything, you have options.
I am using EF6 and Mapster in an ASP.Net MVC project. In my Edit post controller I am trying to save the results of a data entry form. On the form there is a multiselect list box. The selections in the multiselect list box are getting passed along properly but I am not understanding the correct way to save the selected items in the multiselect list to the db. Using the code as I have it below the DisplayName property is saved but the Teams are not being saved. (the Teams table has a many to many relationship to the myRecord table)
The controller code to save the changes:
var myRecord = TypeAdapter.Adapt<MyRecord>(myRecordViewModel);
myRecord.Teams = db.Teams.Where(a => myRecordViewModel .SelectedTeamIDs.Contains(a.TeamID)).ToList();
myRecord.DisplayName = myRecordViewModel.Name;
db.Entry(myRecord).State = EntityState.Modified;
db.SaveChanges();
The myRecord class:
public class MyRecord
{
[Key]
public int Id { get; set; }
public string DisplayName { get; set; }
public virtual ICollection<Team> Teams { get; set; }
}
The Team class:
public partial class Team
{
public int TeamID { get; set; }
public string TeamName { get; set; }
public virtual ICollection<MyRecord> MyRecords{ get; set; }
}
How should this be saved to the database?
Setting the State to Modified (which attaches the entity to the context and marks all primitive properties as modified) does work for simple scenarios, but not when you have related data.
Here is the sequence of operations which does additional database trip, but allows EF change tracker to correctly identify the database operations needed to apply the changes:
(1) Load the original entity from the database, including the collection
(2) Update the master information
(3) Replace the collection with the new data
(4) Save
var myRecord = TypeAdapter.Adapt<MyRecord>(myRecordViewModel);
myRecord.DisplayName = myRecordViewModel.Name;
// (1)
var dbRecord = db.MyRecords.Include(x => x.Teams).FirstOrDefault(x => x.Id == myRecord.Id);
// (2)
db.Entry(dbRecord).CurrentValues.SetValues(myRecord);
// (3)
dbRecord.Teams = db.Teams.Where(a => myRecordViewModel .SelectedTeamIDs.Contains(a.TeamID)).ToList();
// (4)
db.SaveChanges();
I am working with WebAPI, EF Code First and have a problem concerning many-to-many relationships:
I am working with a custom user inherited from "IdentityUser" who can have many Projects.
These Projects can now have multiple users. In Addition to this I want to store additional fields in a mapping table.
public class MyCustomUser : IdentityUser
{
// blah blah...
public virtual ICollection<Project> Projects { get; set; }
}
public class Project
{
public int Id { get; set; }
public string Title { get; set; }
public virtual ICollection<MyCustomUser> Users { get; set; }
}
public class Users2Projects
{
public int UserId { get; set; }
public int ProjectId { get; set; }
public virtual MyCustomUser User { get; set; }
public virtual Project Project { get; set; }
public string AdditionalField1 {get;set;}
public string AdditionalField1 {get;set;}
}
The question is: How do I have to set the relations (FluentAPI) to insert a Project for a User which is already there? Because the user can use the application no matter if he has a Project or not.
So the User CAN have a Project, but a Project has to have at least one User. I am getting the User through the Owin "UserManager" "FindById" which returns "MyCustomUser". Creating a new Users2Project-Class, adding the new Project to it, adding the user to it fails because EF wants to store a new User. How can I solve this?
Kind Regards
You don't need to set anything with FluentAPI when you explicitly declare your many-to-many relationship entity. But you should remove the following lines from User and Project, respectively:
public virtual ICollection<Project> Projects { get; set; }
public virtual ICollection<MyCustomUser> Users { get; set; }
and add on both entities:
public virtual ICollection<Users2Projects> Users2Projects { get; set; }
To add a new Project for an existing User you can:
var relation = new Users2Projects() {
User = existingUser, // reference retrieved from Context.
Project = new Project() { Title = "Project Alpha" }
}
Context.Users2Projects.Add(relation);
Context.SaveChanges();
As far as I know, it is not possible to force the Project to have at least one User using a explicit relationship class on Entity Framework, so you would have to check that programmatically upon inserting a Project into the Context:
if (project.Users2Projects.Count() == 0)
throw new Exception("Project must have at least one user.")
Context.Projects.Add(project);
Context.SaveChanges();
and upon deleting an User you do the check before:
var projectsToDelete = new List<Project>();
foreach (var relationship in user.Users2Projects)
{
if (relationship.Project.Users2Projects.Count() <= 1)
projectsToDelete.Add(relationship.Project);
}
Context.MyCustomUsers.Remove(user);
Context.Projects.RemoveRange(projectsToDelete);
Context.SaveChanges();
You are clearly missing a conceptual entity here although you actually already modeled it a bit as your Users2Projects mapping table. You may want to untangle a few concepts around the bounded contexts that exist in your application, and look again at your requirements
User administration (i.e. administration of users that are allowed to access your system) seems to be a different concern than project administration:
Because the user can use the application no matter if he has a Project or not.
In project administration, for the assignment of a user to a project, it appears there is additional information specific to the project that needs to be captured as part of the ProjectMemberAssignment. Your Project appears to be the aggregate root for this bounded context, and should be made responsible for managing it's member assignments:
public class Project
{
public virtual int Id { get; private set; }
public string Title { get; set; }
public void AssignMember(int userId, string someField, string someOtherField)
{
if (MemberAssignments.Any(x => x.UserId == userId))
throw new InvalidOperationException("User already assigned");
MemberAssignments.Add(new ProjectMemberAssignment(Id, userId, someField, someOtherField));
}
// Other behavior methods.
public virtual ICollection<ProjectMemberAssignment> MemberAssignments
{
get;
set;
}
}
Note that you can get the projects for a user using a simple query, without the need to maintain a many-to-many relationship in your model:
var user = ...;
var projectsForUser =
from project in db.Projects
where project.MemberAssignments.Any(x => x.userId == user.UserId)
select project;
I am using VS 2010 with Entity Framework 5 code first and C# and have a web application (hence disconnected entities). I am used to working with SQL queries directly but am very new to EF and code first.
I have two classes:
public class User
{
public int UserID {get; set;}
public string UserName { get; set; }
public bool IsSuspended { get; set; }
public int UnitID { get; set; }
public virtual MyTrust MyTrusts { get; set; }
}
public class MyTrust
{
public int MyTrustID { get; set; }
public string MyTrustName { get; set; }
public string Region { get; set; }
public bool DoNotUse { get; set; }
}
and my DbContext class contains:
public DbSet<MyTrust> MyTrust { get; set; }
public DbSet<User> Users { get; set; }
modelBuilder.Entity<User>()
.HasRequired(m => m.MyTrust);
The MyTrust entity will not be changed
There are three scenarios I am interested in:
Adding a user with an existing MyTrust
Updating a user with no change to the trust
Updating a user with a change to the trust
When the website returns the data the MyTrust object has only the MyTrustID set. When I update/add the user the MyTrust record is also updated.
CLARIFICATION The relationship in the User object is NOT updated; the actual MyTrust object is updated with the data returned from the website; as most fields are empty this is corrupting the object AND not achieving the required update of the User record.
In fact, the problem seems to boil down to the fact that the wrong end of the relationship is being updated.
I have looked at some many examples I cannot see a simple solution.
Can anyone please suggest a straightforward pattern for this (it was so easy in the SQL days).
UPDATE
I resolved this by adding specific keys to the User and MyTrust classes.
public int NHSTrustID { get; set; }
and a matching key in the MyTrust class.
In retrospect the question was wrong. I wasn't after patterns but the solution to a specific problem.
I've given some examples below - I've done them from memory but hopefully will give you a good starting point:
Adding a user with an existing MyTrust
using(var context = new MyDbContext()){
context.Entry(myUser).State = EntityState.Added
context.Entry(myUser.MyTrusts).State = EntityState.Modified;
context.Entry(myUser.MyTrusts).Property(x => x.MyTrustName).IsModified = false;
context.Entry(myUser.MyTrusts).Property(x => x.Region).IsModified = false;
context.Entry(myUser.MyTrusts).Property(x => x.DoNotUse).IsModified = false;
context.SaveChanges();
}
Updating a user with no change to trusts:
using(var context = new MyDbContext()){
context.Entry(myUser).State = EntityState.Modified
context.Entry(myUser.MyTrusts).State = EntityState.Unchanged;
context.SaveChanges();
}
Updating a user with a change to trusts:
using(var context = new MyDbContext()){
context.Entry(myUser).State = EntityState.Modified
context.Entry(myUser.MyTrusts).State = EntityState.Modified;
context.SaveChanges();
}