List<T> mapping missing in ASP.NET - c#

I have a class named Course, and a user class.
public virtual ICollection<ApplicationUser> Subscribers { get; set; }
This is what my lazy load collection of users looks like,this is a collection in the Course class.
Same goes for the User class which contains a collection of courses.
The relationship between those two is defined as it follows
modelBuilder.Entity<ApplicationUser>().HasMany(z => z.Courses).WithMany();
modelBuilder.Entity<Course>().HasMany(z => z.Subscribers).WithMany();
base.OnModelCreating(modelBuilder);
When I enter the Entity Framework tables for Course and User they do not contain any of those lists that I defined as a properties on those classes,so I cannot user them to save data in them.
Can you please tell me what the reason could be.
Sample of method
var course = db.Courses.Find(id);
var user = userManager.FindByName(User.Identity.Name);
if (!user.Courses.Contains(course))
{
user.Courses.Add(course);
db.SaveChanges();
var item = db.Courses.Where(i => i.Title == course.Title);
return Content(item.First().Title);
---> Verified that course is in the database.
}
After I redirect the user to his courses the list of courses is empty again.

Related

I need to do a query on a returned list of users from another query, by Id

Currently, I have a service that grabs user information from the User table. The users can be created by admins or an employee and all of these employees have their own Id. So with that in mind, there is a column called CreatedBy which holds the id of this admin, or the user, that of which's name I have to return. So far I've pulled the user model but now I need to create the part where I pull the user's name with the user.Id in the CreatedBy
This is what I have pulling from my database tables Users and Company and the query parameters are just a name or company name
public async Task<List<ApplicationUser>> SearchUsers(UserSearchDto userSearchDto)
{
userSearchDto.FirstName ??= string.Empty;
userSearchDto.LastName ??= string.Empty;
userSearchDto.CompanyName ??= string.Empty;
return await _locationDbContext.Users
.Include(nameof(Company))
.Where(user => user.FirstName.Contains(userSearchDto.FirstName)
&& user.LastName.Contains(userSearchDto.LastName)
&& user.Company.Company_Name.Contains(userSearchDto.CompanyName))
.ToListAsync();
}
So within this list that I am returning I'm trying to do another query to grab more user information based on the CreatedBy id's returned in the first service to bring back the name of those users with the id's in CreatedBy.
var userDtos = _mapper.Map<List<ApplicationUser>, List<UserDetailsDto>>(users);
foreach (UserDetailsDto user in userDtos)
{
user.CreatedByName = await _userService
.SearchUsers(userDtos.Where(user.Id == user.CreatedBy))
}
I feel like this is a possible solution so far but I'm not sure where or how I would pull that because this solution is giving me an error at the point where I use the ".Where" statement. Maybe if I could create another service that would return the user by Id instead and use the createdby Id to pull the name but nothing like that exists yet. The model I'd like to return is also a bit different from the model representing the Users table as ApplicationUser has the CreatedBy which is an Id but the returned model, userDetailsDto will have a name string property as well that I will try and assign here in automapper. If I can think of how I can assign the name by the Id.
CreateMap<ApplicationUser, UserDetailsDto>()
.ForMember(dest => dest.CompanyName,
opts => opts.MapFrom(src => src.Company.Company_Name));
Ideally this is something that you should be able to resolve using navigation properties. If your User table uses CreatedBy to represent the CreatedBy User ID then you could adjust your mapping to facilitate a CreatedBy navigation property:
public class User
{
public class UserId { get; set; }
// ...
public virtual User CreatedBy { get; set; }
}
Then in the mapping use a shadow property for the FK association: (in OnModelCreating or using an EntityTypeConfiguration)
EF Core
.HasOne(x => x.CreatedBy)
.WithMany()
.HasForeignHey("CreatedBy") // Property on Users table
.Required();
EF 6
.HasRequired(x => x.CreatedBy)
.WithMany()
.Map(x => x.MapKey("CreatedBy")) // Property on Users table
Alternatively if you want the CreatedBy FK accessible in the User table, map it as something like CreatedByUserId:
public class User
{
public int UserId { get; set; }
// ...
[ForeignKey("CreatedBy"), Column("CreatedBy")]
public int CreatedByUserId { get; set; }
public virtual User CreatedBy { get; set; }
}
Now when you go to search for your users, you can project your CreatedBy user ID and Name in one go.
When it comes to optional search parameters you should keep the conditionals (if/else/ null checks etc ) outside of the Linq wherever possible. This helps compose more efficient queries rather than embedding conditional logic into the SQL.
public async Task<List<ApplicationUserViewModel>> SearchUsers(UserSearchDto userSearchDto)
{
var query = _locationDbContext.Users.AsQueryable();
if (!string.IsNullOrEmpty(userSearchDto.FirstName))
query = query.Where(x => x.FirstName.Contains(userSearchDto.FirstName));
if (!string.IsNullOrEmpty(userSearchDto.LastName))
query = query.Where(x => x.LastName.Contains(userSearchDto.LastName));
if (!string.IsNullOrEmpty(userSearchDto.CompanyName))
query = query.Where(x => x.Company.Name.Contains(userSearchDto.CompanyName));
return await query.ProjectTo<ApplicationUserViewModel>(_config)
.ToListAsync();
}
Where _config reflects an automapper MapperConfiguration containing the details on how to map a User to your desired view model. If you're not leveraging Automapper you can accomplish this using Select to project the values. Other considerations there would be to consider using StartsWith instead of Contains, perhaps offering an option to perform a more expensive Contains search... Also adding things like minimum search length checks (I.e. 3+ characters) and pagination or result row limits (I.e. Take(50)) to avoid outrageous search requests from hammering your system. (I.e. searching for users with "e" in the first name)
That view model might have UserId, UserName, CompanyName, then things like CreatedByUserId, CreatedByName. To resolve the CreatedBy details you just reference u.CreatedBy.UserId and u.CreatedBy.Name either in the Automapper config or within your Select(u => new ApplicationUserViewModel { ... }) statement.

Entity Framework Insert into a many-to-many instance with already existing registers, creating duplicated registers

Well, I have this configuration:
Item has 0 or many Groups and 0 or many Users (tables GroupsItem and UsersItem)
Groups and Users are inside 0 or many Items
Groups and Users are independently being created in the application
Here's the problem: When I try to insert a new Item I have to point out what are its Groups and Users (which already exists). When it happens, the tables GroupsItem, UsersItem and Item are being correctly populated, but I'm having duplicated registers at Groups and Users.
Here is my code summarized:
Item:
public class Item {
public ICollection<Groups> GROUPS{ get; set; }
public ICollection<Users> USERS{ get; set; }
}
Groups: (Users have the same structure)
public class Groups{
public ICollection<Item> ITEM { get; set; }
}
Inserting a new Item:
public static void InsertingItem(){
Item example = new Item(){
GROUPS = AlreadyExistingGroup()
}
using (myDbContext db = new myDbContext()){
db.ITEMS.Add(example);
db.SaveChanges();
}
}
And that's it. AlreadyExistingGroup is a method that returns a List<Groups> which is populated with groups that already exist in the database, the method that brings these groups is a single function that brings one single group but it's called multiple times:
public static Groups FetchGroups(int id) {
try {
using (myDbContext db = new myDbContext ()) {
Groups group = db.GROUPS.Where(x => x.CODGROUP == id).FirstOrDefault();
return group;
}
} catch (Exception e) {
return null;
}
}
What am I doing wrong that is causing duplicate registers at Groups and Users?
Editing my answer with the correct solution we came to in comments:
The issue lies with the two different DbContexts in the code:
public static void InsertingItem(){
Item example = new Item(){
// DbContext #1 is created in this method
GROUPS = AlreadyExistingGroup();
}
// And this is DbContext #2
using (myDbContext db = new myDbContext()){
db.ITEMS.Add(example);
db.SaveChanges();
}
}
The fix is to use the same DbContext for both the lookup and the insert of a new item. Example:
public static void InsertingItem(){
using (myDbContext db = new myDbContext()){
Item example = new Item(){
// refactor the AlreadyExistingGroup method to accept a DbContext, or to move
// the code from the method here
GROUPS = AlreadyExistingGroup(dbContext) ;
}
db.ITEMS.Add(example);
db.SaveChanges();
}
}
If I'm understanding your setup correctly, I think you'd want Groups to only have one parent Item reference.
public class Groups{
public Item ITEM { get; set; } //
}
Also, and I'm not downvoting or criticizing, but just a suggestion: It's helpful to also post the model configuration as well when asking EF questions. Because... well... EF can be finicky. Aka:
modelBuilder.Entity<Group>()
.HasMaxLength(50)
.WhateverElseYouConfigure();
Based on your clarification in the comments it seems you are using untracked (unattached) Group and User entities when setting them in your new Item entity. When the Item entity is added to the Items DbSet, it is tracked as EntityState.Added. EF will propagate the Item entity's object graph and as it comes across untracked related entities (i.e. the User and Group collections you've set), it will track the formerly untracked entities as EntityState.Added as well, thus inserting new records in the data store for those entities.
To solve the problem, manually attach these related entities as EntityState.Unchanged. As an example, you can use the DbContext.AttachRange method
db.AttachRange( existingGroups );
Or per entity you can also attach via the DbContext.Entry method
db.Entry( existingGroup ); // this tracks the entity as Unchanged

Virtual Navigation Properties and Multi-Tenancy

I have a standard DbContext with code like the following:
public DbSet<Interest> Interests { get; set; }
public DbSet<User> Users { get; set; }
I've recently implemented multi-tenancy by creating a TenantContext that contains the following:
private readonly DbContext _dbContext;
private readonly Tenant _tenant;
public TenantContext(Tenant tenant)
: base("name=DefaultConnection") {
this._tenant = tenant;
this._dbContext = new DbContext();
}
public IQueryable<User> Users { get { return FilterTenant(_dbContext.Users); } }
public IQueryable<Interest> Interests { get { return FilterTenant(_dbContext.Interests); } }
private IQueryable<T> FilterTenant<T>(IQueryable<T> values) where T : class, ITenantData
{
return values.Where(x => x.TenantId == _tenant.TenantId);
}
So far, this has been working great. Whenever any of my services creates a new TenantContext, all queries directly off of that context are filtered through this FilterTenant method that guarantees I'm only returning tenant-relevant entities.
The problem that I'm encountering is my usage of navigation properties that do not take this into account:
using (var db = CreateContext()) // new TenantContext
{
return db.Users.
Include(u => u.Interests).FirstOrDefault(s => s.UserId == userId);
}
This query pulls up the tenant-specific Users, but then the Include() statement pulls in Interests for that user only - but across all tenants. So if a user has Interests across multiple Tenants, I get all of the user's Interests with the above query.
My User model has the following:
public int UserId { get; set; }
public int TenantId { get; set; }
public virtual ICollection<Interest> Interests { get; set; }
Is there any way that I can somehow modify these navigation properties to perform tenant-specific queries? Or should I go and tear out all navigation properties in favor of handwritten code?
The second option scares me because a lot of queries have nested Includes. Any input here would be fantastic.
As far as I know, there's no other way than to either use reflection or query the properties by hand.
So in your IQueryable<T> FilterTenant<T>(IQueryable<T> values) method, you'll have to inspect your type T for properties that implement your ITenantData interface.
Then you're still not there, as the properties of your root entity (User in this case) may be entities themselves, or lists of entities (think Invoice.InvoiceLines[].Item.Categories[]).
For each of the properties you found by doing this, you'll have to write a Where() clause that filters those properties.
Or you can hand-code it per property.
These checks should at least happen when creating and editing entities. You'll want to check that navigation properties referenced by an ID property (e.g. ContactModel.AddressID) that get posted to your repository (for example from an MVC site) are accessible for the currently logged on tenant. This is your mass assignment protection, which ensures a malicious user can't craft a request that would otherwise link an entity to which he has permissions (a Contact he is creating or editing) to one Address of another tenant, simply by posting a random or known AddressID.
If you trust this system, you only have to check the TenantID of the root entity when reading, because given the checks when creating and updating, all child entities are accessible for the tenant if the root entity is accessible.
Because of your description you do need to filter child entities. An example for hand-coding your example, using the technique explained found here:
public class UserRepository
{
// ctor injects _dbContext and _tenantId
public IQueryable<User> GetUsers()
{
var user = _dbContext.Users.Where(u => u.TenantId == _tenantId)
.Select(u => new User
{
Interests = u.Interests.Where(u =>
u.TenantId == _tenantId),
Other = u.Other,
};
}
}
}
But as you see, you'll have to map every property of User like that.
Just wanted to offer an alternative approach to implementing multi-tenancy, which is working really well in a current project, using EF5 and SQL 2012. Basic design is (bear with me here...):
Every table in the database has a column (ClientSid binary, default constraint = SUSER_SID()) and is never queried directly, only ever via a dedicated view
Each view is a direct select over the table with WHERE (ClientSid = SUSER_SID()) but doesn't select the ClientSid (effectively exposing the interface of the table)
EF5 model is mapped to the VIEW, not the TABLE
The connection string is varied based on the context of the tenant (user / client whatever multi-tenant partition requirement may be)
That's pretty much it - though it might be useful to share. I know it's not a direct answer to your question, but this has resulted in basically zero custom code in the C# area.

Adding detached entities to a 1-many relationship in EF 4.1 Code First

I am attempting to use EF 4.1 Code First to model a simple relationship of a User having a single Role. When I attempt to save an existing User with a new Role to a different context (using a different context to simulate a client-server round-trip), I get the following exception:
System.Data.Entity.Infrastructure.DbUpdateException: An error occurred while saving entities that do not expose foreign key properties for their relationships. The EntityEntries property will return null because a single entity cannot be identified as the source of the exception. Handling of exceptions while saving can be made easier by exposing foreign key properties in your entity types. See the InnerException for details. ---> System.Data.UpdateException: A relationship from the 'User_CurrentRole' AssociationSet is in the 'Added' state. Given multiplicity constraints, a corresponding 'User_CurrentRole_Source' must also in the 'Added' state.
What I expect is that a new Role is created and associated with the exising User.
What am I doing wrong, is this possible to achieve in EF 4.1 code first? The error message seems to suggest that it needs both the User and the Role to be in the added state, but I'm modifying an exising User, so how can that be?
Things to note: I'd like to avoid modifying the structure of the entities (eg by introducing foreign key properties visible on the entities), and in the database I'd like the User to have a foreign key pointing to Role (not the other way around). I'm also not prepared to move to Self Tracking Entities (unless there's no other way).
Here are the entities:
public class User
{
public int UserId { get; set; }
public string Name { get; set; }
public Role CurrentRole { get; set; }
}
public class Role
{
public int RoleId { get; set; }
public string Description { get; set; }
}
And here's the mapping I'm using:
public class UserRolesContext : DbContext
{
public DbSet<User> Users { get; set; }
public DbSet<Role> Roles { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<User>().HasKey(u => u.UserId);
modelBuilder.Entity<Role>().HasKey(r => r.RoleId);
modelBuilder.Entity<User>().HasRequired(u => u.CurrentRole);
}
}
I pre-populate the database with this:
public class UserInitializer : DropCreateDatabaseAlways<UserRolesContext>
{
protected override void Seed(UserRolesContext context)
{
context.Users.Add(new User() {Name = "Bob",
CurrentRole = new Role() {Description = "Builder"}});
context.SaveChanges();
}
}
And finally, here's the failing test:
[TestMethod]
public void CanModifyDetachedUserWithRoleAndReattach()
{
Database.SetInitializer<UserRolesContext>(new UserInitializer());
var context = new UserRolesContext();
// get the existing user
var user = context.Users.AsNoTracking().Include(c => c.CurrentRole).First(u => u.UserId == 1);
//modify user, and attach to a new role
user.Name = "MODIFIED_USERNAME";
user.CurrentRole = new Role() {Description = "NEW_ROLE"};
var newContext = new UserRolesContext();
newContext.Users.Attach(user);
// attachment doesn't mark it as modified, so mark it as modified manually
newContext.Entry(user).State = EntityState.Modified;
newContext.Entry(user.CurrentRole).State = EntityState.Added;
newContext.SaveChanges();
var verificationContext = new UserRolesContext();
var afterSaveUser = verificationContext.Users.Include(c => c.CurrentRole).First(u => u.UserId == 1);
Assert.AreEqual("MODIFIED_USERNAME", afterSaveUser.Name, "User should have been modified");
Assert.IsTrue(afterSaveUser.CurrentRole != null, "User used to have a role, and should have retained it");
Assert.AreEqual("NEW_ROLE", afterSaveUser.CurrentRole.Description, "User's role's description should have changed.");
}
}
}
Surely this is a scenario that's covered, I would guess it's something I'm missing in the way I've defined the model mapping?
You have broken EF state model. You mapped your entity with mandatory CurrentRole so EF knows that you cannot have existing User without the Role. You have also used independent associations (no FK property exposed on your entity). It means that relation between role and user is another tracked entry which has its state. When you assign the role to existing user the relation entry has state set to Added but it is not possible for existing User (because it must have already role assigned) unless you mark the old relation as Deleted (or unless you are working with a new user). Solving this in detached scenario is very hard and it leads to the code where you must pass information about old role during the roundtrip and manually play with state manager or with entity graph itself. Something like:
Role newRole = user.CurrentRole; // Store the new role to temp variable
user.CurrentRole = new Role { Id = oldRoleId }; // Simulate old role from passed Id
newContext.Users.Attach(user);
newCotnext.Entry(user).State = EntityState.Modified;
newContext.Roles.Add(newRole);
user.CurrentRole = newRole; // Reestablish the role so that context correctly set the state of the relation with the old role
newContext.SaveChanges();
The simplest solution is load the old state from the database and merge changes from the new state to the loaded (attached) one. This can be also avoided by exposing FK properties.
Btw. your model is not one to one but one to many where the role can be assigned to multiple users - in case of one-to-one it would be even more complicated because you will have to delete the old role prior to creating a new one.

EF 4.1 abstract collection loading problem

I have a problem regarding EF 4.1 code first.
I am trying to demonstrate with a simplified example.
Let's say we have a Country class which contains a States collection.
State collection contains schools collection.
School is an abstract class.
It has specializations of ElementarySchool and HighSchool.
HighSchool has a collection property of DrivingCourses.
DrivingCourses and all other data saves into the db successfully.
My problem is when I'm loading the Country class the DrivingCourses collection remains null. (everything else is ok)
As I understand the problem is because when ef loads and populates the HighSchool class it's not aware of the courses collection.
I am unable to add this mapping because with the fluent api's static reflecion I can only map properties of the (abstract) School class.
I'm using the default config for abstraction: Table per Hierarchy
Could someone please brighten me, if it's possible to solve my problem with EF 4.1?
Thanks in advance,
Sandor
If I understand your description correctly then your model looks roughly like this (I omit key properties and so on):
public class Country
{
public ICollection<State> States { get; set; }
}
public class State
{
public ICollection<School> Schools { get; set; }
}
public abstract class School { ... }
public class ElementarySchool : School { ... }
public class HighSchool : School
{
public ICollection<DrivingCourse> DrivingCourses { get; set; }
}
public class DrivingCourse { ... }
And you have a DbContext which includes public DbSet<Country> Countries { get; set; }.
Now you want to load all Countries (or a filtered collection of Countries) including all navigation properties (especially also the DrivingCourses).
I don't know if this is possible with a single roundtrip to the database (by eager loading all collections). A solution which will require multiple roundtrips though might be this one:
// Load all Countries including `States` and `Schools` collection
// but not the `DrivingCourses` collection
var countryList = context.Countries
.Include(c => c.States.Select(s => s.Schools))
.ToList();
// Create in-memory list of all loaded Schools of type HighSchool
IEnumerable<HighSchool> highSchoolList =
countryList.SelectMany(c =>
c.States.SelectMany(s => s.Schools.OfType<HighSchool>()));
// Explicitely load the DrivingCourses one by one
foreach (var item in highSchoolList)
context.Entry(item).Collection(h => h.DrivingCourses).Load();
Just as a first idea. It's likely that there are better solutions.
Edit
Using Load on the Countries DbSet doesn't change the problem. Load is the same as ToList() without actually returning a result, the entities are just loaded into the context. The code above could be rewritten like so:
context.Countries.Include(c => c.States.Select(s => s.Schools)).Load();
IEnumerable<HighSchool> highSchoolList =
context.Countries.Local.SelectMany(c =>
c.States.SelectMany(s => s.Schools.OfType<HighSchool>()));
foreach (var item in highSchoolList)
context.Entry(item).Collection(h => h.DrivingCourses).Load();
But this is basically the same as before and it also doesn't solve the problem to load the DrivingCourses in the first Load statement in a single DB roundtrip.

Categories