Deep loading data with EF6 and LINQ - c#

I am implementing an ASP.NET MVC 5 web app using ASP.NET Identity 2 and Troy Goode's PagedList. I need to display UserRole data in the following format in a JqGrid:
User Name | Role Name
This is my UserRole class:
public class UserRole : Microsoft.AspNet.Identity.EntityFramework.IdentityUserRole<int>
{
[ForeignKey("UserId")]
public virtual User User { get; set; }
[ForeignKey("RoleId")]
public virtual Role Role { get; set; }
}
This is my repository method
public virtual IQueryable<UserRole> GetAsQueryable(Expression<Func<UserRole, bool>> where)
{
return _dataContext.UserRoles.Where(where).AsQueryable();
}
This my Controller method (the GetAsPagedList method simply orders the input list, applies PagedList's .ToPagedList method to it and returns a subset of it):
public ActionResult GridData(MvcJqGrid.GridSettings gridSettings)
{
IQueryable<UserRole> items = _repository.GetAsQueryable();
IPagedList<T> pagedOrderedItems = PagingHelper<UserRole>.GetAsPagedList(
items,
gridSettings.SortOrder, gridSettings.SortColumn,
gridSettings.PageIndex, gridSettings.PageSize);
var jsonData = new
{
total = pagedOrderedItems.TotalItemCount / gridSettings.PageSize + 1,
page = gridSettings.PageIndex,
records = pagedOrderedItems.TotalItemCount,
rows = (
from c in pagedOrderedItems
select new
{
id = c.UserId + '-' + c.RoleId,
cell = new[]
{
"Edit",
"Details",
// TODO: something like this:
// [UserName] = c.User.UserName
// [RoleName] = c.Role.Name
c.Role.Name
}
}).ToArray()
};
return Json(jsonData, JsonRequestBehavior.AllowGet);
}
I can'.t figure out where and how I should deep load data from the tables linked through foreign keys to my table (I.E. Users.UserName and Roles.Name) - could you please help?

_dataContext.UserRoles.Include("Role").Include("User").Where(where).AsQueryable();

Related

Dapper does not properly map rows selected with LEFT JOIN (splitOn)

I have two models Vendors and Users. Each user may or may not have only one Vendor.
Vendors may have several Users.
Models:
public class AppUser
{
public int Id { get; set; }
public string? FullName { get; set; }
public string? Position { get; set; }
public int StatusId { get; set; }
public int VendorId { get; set; }
}
public class Vendor
{
public int VendorId { get; set; }
public string? VendorCode { get; set; }
public string? VendorName { get; set; }
public int StatusId { get; set; }
public List<AppUser> CompanyUsers { get; set; }
}
SQL Tables:
I need to select all Vendors with related Users (if such exists).
My query:
SELECT
v.VendorId
,v.VendorCode
,v.VendorName
,v.Status StatusId
,(CASE
WHEN v.Status = 0 THEN 'Draft'
WHEN v.Status = 1 THEN 'Open'
WHEN v.Status = 2 THEN 'Closed'
WHEN v.Status = 3 THEN 'Blacklisted'
END) StatusName
,au.Id
,au.FullName
,au.Position
,au.StatusId
,(CASE
WHEN au.StatusId = 0 THEN 'Draft'
WHEN au.StatusId = 1 THEN 'Open'
WHEN au.StatusId = 2 THEN 'Closed'
END) StatusName
FROM Procurement.Vendors v
LEFT JOIN Config.AppUser au
ON v.VendorId = au.VendorId
and the result:
Because only Vendor with Id 20 has users, it appears 3 times, which is expected behavior. Now I want to use Dapper's splitOn function to map all users under vendor 20. I split by user's Id column.
public async Task<IEnumerable<Vendor>?> GetAllVendors(int businessUnitId)
{
var currentUser = await _appUserService.GetCurrentUserAsync();
var p = new DynamicParameters();
p.Add("#UserId", currentUser.Id, DbType.Int32, ParameterDirection.Input);
p.Add("#BusinessUnitId", businessUnitId, DbType.Int32, ParameterDirection.Input);
using IDbConnection cn = new SqlConnection(_sqlDataAccess.ConnectionString);
return await cn.QueryAsync<Vendor, AppUser, Vendor>("dbo.SP_ZZZTest",
(vendor, user) =>
{
if (vendor.CompanyUsers == null && user != null) { vendor.CompanyUsers = new(); };
if (user != null) { vendor.CompanyUsers.Add(user); };
return vendor;
},
param: p,
splitOn: "Id",
commandType: CommandType.StoredProcedure);
}
And here is the result I get:
As a result, Dapper did not map Users under a single Vendor. But instead mapped each user as a List of users with a single item duplicating Vendor's data on 3 rows.
What did I wrong?
Yes, this is a common "problem". But it is really simple to solve once you understand the process behind the lambda call.
The lambda expression receives the three records created by the query and before calling the lambda, Dapper splits each record in two at the point of the splitOn configuration. In this process a new Vendor and new AppUser instance will be created for each row processed.
So the Vendor instance received at the second/third call is not the same instance of the first/second call. Dapper doesn't have this kind of processing logic (and I think it is right to avoid it from a performance point of view). So the code above, adds each AppUser to three different instances of Vendor.
It is up to you to 'discover' that the Vendor received contains the same data of a previous call. But it is easy to solve if there is some kind of unique key that identifies a Vendor (the PK of the record)
So this "problem" can be solved using a Dictionary where the key is the PK of the Vendor and you store each unique Vendor data passed by Dapper under that dictionary key. Then you could check if the Vendor data received is already in the Dictionary and use the dictionary instance to add the AppUser.
Dictionary<int, Vendor> result = new Dictionary<int, Vendor>();
.....
using IDbConnection cn = new SqlConnection(_sqlDataAccess.ConnectionString);
_ = await cn.QueryAsync<Vendor, AppUser, Vendor>("dbo.SP_ZZZTest",
(vendor, user) =>
{
if(!result.ContainsKey(vendor.vendorId))
{
vendor.CompanyUsers = new();
result.Add(vendor.vendorId, vendor);
}
Vendor current = result[vendor.vendorId];
if (user != null)
current.CompanyUsers.Add(user);
return vendor;
},
param: p,
splitOn: "Id",
commandType: CommandType.StoredProcedure);
// A VERY IMPORTANT POINT...
// You want to return the Vendors stored in the Values of the
// Dictionary, not the Vendors returned by the QueryAsync call
return result.Values;

Entity Framework is trying to insert into wrong table?

I've built a test application with Entity Framework to simulate a database that contains friends lists.
I want the database to store the user's ID's and when I retrieve them (the "AcceptedFriends") I want Entity Framework to also return the friends "usermodel".
But every time I try to add 2 users as friends to the "AcceptedFriends" table it
throws an exception:
" Violation of PRIMARY KEY constraint 'PK_Users'. Cannot insert duplicate key in object 'dbo.Users'. The duplicate key value is (GUID value of a user's ID) "
Some attempted solutions:
Solution 1
Attempting to create 2 lists of the same friend list (received, sent) but that defeats the purpose of what I am trying to achieve.
Solution 2
Here are the code files:
"Users Model"
public class Users
{
#region Private fields
#endregion
#region Public properties
public string Username { get; set; }
public string ID { get; set; }
public string PasswordHash { get; set; }
public virtual List<AcceptedFriends> AcceptedFriendsList { get; set; }
// public virtual List<PendingFriends> PendingFriendsList { get; set; }
// public virtual List<RemovedFriends> RemovedFriendsList { get; set; }
#endregion
}
"Accepted Friends model"
public class AcceptedFriends
{
#region Public properties
public string RelationKey { get; set; }
public string RequestSenderID { get; set; }
public string RequestReceiverID { get; set; }
public virtual List<Messages> ChatList { get; set; }
public Users RequestSender { get; set; }
public Users RequestReceiver { get; set; }
#endregion
}
"Database model creation"
#region Users table
// Create primary key in Users table
modelBuilder.Entity<Users>().HasKey(property => property.ID);
// Map Username to be unique
modelBuilder.Entity<Users>().HasIndex(property => property.Username).IsUnique();
// Create a one to many relation with AcceptedFriends table
modelBuilder.Entity<Users>()
.HasMany(property => property.AcceptedFriendsList)
.WithOne(property => property.RequestReceiver)
.HasForeignKey(property => property.RequestReceiverID);
#endregion
#region Accepted friends table
// Create key for AcceptedFriends
modelBuilder.Entity<AcceptedFriends>().HasKey(property => property.RelationKey);
#endregion
Edit
Here is how I am inserting the friends
public static void AddFriends(AcceptedFriends friends)
{
using(Context context = ConnectToDatabase())
{
context.AcceptedFriends.Add(friends);
context.SaveChanges();
};
}
Edit 2
Here is where I add the friends/users
Plus I've noticed another odd behaviour When I add new users to the friends table
without adding them to the users table first it adds them both to the friends table and users table.
Console.WriteLine("Connecting to database");
DB.ConnectToDatabase();
Console.WriteLine("Connected to database successfully");
List<Users> userList = new List<Users>(DB.GetUsersList());
List<AcceptedFriends> friendsCount = new List<AcceptedFriends>(DB.GetAcceptedFriends());
if(userList.Count != 2)
{
DB.AddUser(new Users()
{
Username = "User1",
PasswordHash = "PasswordHash",
});
DB.AddUser(new Users()
{
Username = "User2",
PasswordHash = "PasswordHash",
});
userList = new List<Users>(DB.GetUsersList());
};
if(friendsCount.Count < 1)
{
Users user1 = userList[0];
Users user2 = userList[1];
DB.AddFriends(new AcceptedFriends()
{
RequestReceiver = user2,
RequestSender = user1,
});
};
Console.WriteLine("Server is great success!");
Console.ReadLine();
Edit 3
I might have found a solution.
It does return the models both for the user and friends,
But I can't accept this as a solution yet because it feels to hackey(?) for me
(Thanks to #wertzui, You helped me to get to this solution)
Basically everytime a new context is created it sets up the the friends and users to return thier usermodels
/// <summary>
/// Gets the friends user models
/// </summary>
/// <param name="context"> The database context that was created </param>
private static void SetupFriends(Context context)
{
// For every "AcceptedFriend"
foreach(AcceptedFriends friend in context.AcceptedFriends)
{
// Get sender and receiver usermodels
// by matching ID's
Users sender = context.Users.FirstOrDefault(user => user.ID == friend.RequestSenderID);
Users receiver = context.Users.FirstOrDefault(user => user.ID == friend.RequestReceiverID);
sender.AcceptedFriendsList.Add(friend);
receiver.AcceptedFriendsList.Add(friend);
friend.RequestSender = sender;
friend.RequestReceiver = receiver;
};
}
When you create new User Instances in your new AcceptFriends {...} Code, you are not setting their Id, so they keep their default which is 0. Now Entity Framework thinks, that you want to create a new Friendship with 2 new Users. Instead you should populate them with the Users, you created earlier.
if(friendsCount.Count < 1)
{
Users user1 = userList[0];
Users user2 = userList[1];
DB.AddFriends(new AcceptedFriends()
{
RequestReceiver = user1,
RequestSender = user2,
});
}

Entity Framework performing INSERT where it should not be

I have the following Entity Data models, simplified for brevity;
public abstract class Entity<T> : BaseEntity, IEntity<T>
{
[Key]
public virtual T Id { get; set; }
}
public class User : Entity<int>
{
public List<Category> Categories { get; set; }
}
public class Category : Entity<int>
{
public string Description { get; set; }
}
public class List : Entity<int>
{
public Category Category { get; set; }
}
These are accessed from the DbContext using a DbContext exposed by either a generic service, or a more customised implementation to provide business logic.
When I publish the database and add the following code to the Seed() method, all is well and the data looks good directly in the database.
var user = new User
{
Email = "",
Categories = new List<Category>
{
new Category
{
Description = "Category 1",
},
new Category
{
Description = "Category 2",
}
}
};
context.Users.AddOrUpdate(u => u.Email, user);
var list = new List()
{
Id = 1,
Description = "Test List",
UserId = 1,
Category = user.Categories.FirstOrDefault()
};
context.Lists.AddOrUpdate(u =>u.Id, list);
Please note that the User owns the categories and you can (should only be able to) create them by accessing the Categories Property.
This gives me;
I am using these objects in my controller as such;
public ActionResult Edit(int id)
{
var categories = _usersService.GetUser(User.Id).Categories;
categories.Insert(0, new Category {Description = "", Id = 0});
var list = _listsService.GetList(id);
var viewModel = new EditViewModel
{
Id = list.Id,
Reference = list.Reference,
Description = list.Description,
CategoryId = list.Category?.Id ?? 0,
Categories = new SelectList(categories.AsEnumerable(), "Id", "Description")
};
return View(viewModel);
}
In the above test, I am using the List inserted during the Seed and I can see the List does indeed have a Category, and the values are correct.
For information, I am using the following ViewModel. I have been investigation methods to be able to 'select' the User.Categories from a DropDown and this appeared to work the best at present.
public class EditViewModel
{
[Required]
public int Id { get; set; }
[Required]
public string Description { get; set; }
public IEnumerable<SelectListItem> Categories { get; set; }
[ScaffoldColumn(false)]
public int CategoryId { get; set; }
[ScaffoldColumn(false)]
public Guid Reference { get; set; }
}
The populated ViewModel looks like this;
and finally, the POST method;
[HttpPost]
public ActionResult Edit(EditViewModel model)
{
if (ModelState.IsValid)
{
var categories = _usersService.GetUser(User.Id).Categories;
var list = _listsService.GetList(model.Id);
list.Description = model.Description;
list.Category = categories.FirstOrDefault(x => x.Id == model.CategoryId);
_listsService.Update(list);
categories.Insert(0, new Category { Description = "", Id = 0 });
model.Categories = new SelectList(categories.AsEnumerable(), "Id", "Description");
return View(model);
}
return View(model);
}
So, in the following scenarios, this is what happens. For clarity, each time I am doing this, I go back to the Lists Index and GET Edit again;
Select '' from the Dropdown - NO Categories INSERT,UPDATE on Lists table only, setting [Category_Id] = NULL - Correct
SELECT 'Category 1' from DropDown. INSERT categories, UPDATE lists - NOT Correct
The code being used to update the List is;
public void Update(T entity)
{
if (entity == null) throw new ArgumentNullException(nameof(entity));
_context.Entry(entity).State = EntityState.Modified;
_context.SaveChanges();
}
Now, I know this is something I am doing, but being new to EF, I have no idea.
The problem was down to how the values were being set.
I needed to set the Foreign Key and assign the value to this.
public class List : Entity<int>
{
public int CategoryId { get; set; }
[ForeignKey("CategoryId")
public Category Category { get; set; }
}
and the value was then set as
var list = _listsService.GetList(model.Id);
list.Description = model.Description;
list.CategoryId = model.CategoryId;
list.Category = null;
_listsService.Update(list);
After this, when getting the list from the repository, both the Category and CategoryID would be populated correctly.
The issue was down the setting the Entity as modified, this internally indicated that the Category was 'new' when in fact it was not. You could also 'attach' and existing category to the entity/context but decided the method above was better.
A slightly better approach to the above would be to create a new 'UpdateList' method which could be called rather than the generic update. This method would perform the setting of the relevant properties outside of the controller method.
I am not sure but possible you must write your update method as follow:
public void Update(T entity)
{
if (entity == null) throw new ArgumentNullException(nameof(entity));
var item = _collection.Find(item.Id);
if (item == null) throw new ArgumentNullException(nameof(item ));
_context.Entry(item).CurrentValues.SetValues(entity);
_context.SaveChanges();
}
The problem can be in view, of you don't have the I'd the EF will add new record instead of update the existing record.
In general: Watch out for existing classes when you name your custom class (like List):
public class List : Entity<int>
{
public Category Category { get; set; }
}
Be shoure that you use the right class in the right namespace.
var user = new User
{
Email = "",
Categories = new YourOwnNamespace.List<Category>
{
new Category
{
Description = "Category 1",
},
new Category
{
Description = "Category 2",
}
}
};
Avoid naming classes and properties to existing names. Beter change List class in e.g. 'MyList'.

List of values as a user attribute

I'm using identity 2.1.0 in ASP.NET MVC 5 application, and I have pages for admin to work (create/edit/delete user) with some custom user properties I defined. However I need to store a list of values in one field. How can I achieve this by using identity, and how to show this on a web page ?
To save such values you can extend your ApplicationUser, in case of multiple values you can do this via an n:m relationship:
First create a table to store the country values in (also add to your DbContext as e.g. public DbSet<Country> Countries { get; set; }):
public class Country
{
[Key]
public int Id { get; set; } // or e.g. "string Code" to save e.g. "us"
public string Name { get; set; }
public List<ApplicationUsers> Users { get; set; }
}
then you can also add a list of Country to your ApplicationUser:
public class ApplicationUser : IdentityUser<KEY>
{
// ...
public List<Country> Countries { get; set; }
}
and finally to update the countries of a user something like the following:
var user = // get user
var countryToAdd = db.Countries.FirstOrDefault(c => c.Name == countryName) ??
new Country() { Name = countryName };
if (user.Countries == null)
user.Countries = new List<Country>() { countryToAdd };
else if (!user.Countries.Contains(countryToAdd))
user.Countries.Add(countryToAdd);
db.SaveChanges();
And to get all users from one country:
var country = db.Countries.Include(c => c.Users)
.FirstOrDefault(c => c.Name == countryName);
if (country != null)
{
var users = country.Users;
}

Fluent hHibernate One-To-Many set value in blank

first of all, sorry for my bad English
I have the following entities
public class User
{
public virtual int Id { get; set; }
public virtual Application Application { get; set; }
public User ()
{
Application = new Application ();
}
}
UserMap
public class UserMap : ClassMap<User>
{
public UserMap ()
{
Table ("Users");
Id (p => p.Id);
References (x => x.Application).Cascade.SaveUpdate ();
}
}
Application
public class Application
{
public virtual int Id { get; set; }
public virtual string ApplicationName { get; set; }
}
ApplicationMap
public ApplicationMap ()
{
Table ("Applications");
Id (x => x.Id);
Map (x => x.ApplicationName);
}
I received this json
{
"Application": 1,
}
and save the object this way
var user = new User();
user.Application.Id = Cast.To<int>(userModel.Application);
userService.Add(user);
userService.Commit();
Correctly records the data in the table "users" but left blank, the "ApplicationName" table field "application"
I think the error is in this line (user.Application.Id = Cast.To (userModel.Application);)
because I did not set  "ApplicationName" field
  but if I get the id as a parameter, I will need to get the application object by id, and assign the user object?.
thank you very much
You are right, conversion from ID into Entity (Applicaton) will require call to data layer and its operation GetById().
session.Load<Application>(id)
In cases, that we can be sure, that the passed Application ID exists, NHibernate has a dedicated way how to convert ID into its ENTITY. It is a Load() method, which does NOT hit the DB, just creates a proxy with provided ID, and the operation will succeed:
var user = new User();
var applicationId = Cast.To<int>(userModel.Application);
// behind is session.Load<Aplication>(applicaitonId)
var application = applicationService.Load(applicationId);
user.Application = application;
userService.Add(user);
session.Get<Application>(id)
The alternative is a Get() method, which always loads the instance by ID, i.e. hits the DB. If the ID does not match any ID, null is returned. Advantage in this scenario is, that we can even change the referenced ApplicaitonName (if application exists)
var user = new User();
var applicationId = Cast.To<int>(userModel.Application);
// behind is session.Get<Aplication>(applicaitonId)
var application = applicationService.GetById(applicationId);
if(application !== null)
{
application.ApplicationName = ... // here we can even change that;
user.Application = application;
}
cascade for new.
In case we would recieve brand new object
{
"Application": { "ApplicaitonName" : ... }
}
We can create one, and because of Cascading setting above, it will work as well
user.Application = new Appliation
{
ApplicationName = ...,
}

Categories