i have in the database typical tables like user, usergroup, and they have relations.
in my domain-model i want that if you request users, you will get the users and each user has the property belongsto, which means these are the groups he belongs to. in the property i want to have the list of groups (typesafe as groups)
the same should be done on the other hand, that means each group should know which users belong to the group.
i have this classes (example):
public class User
{
int Id { get; set; }
string Name { get; set; }
List<Usergroup> BelongsTo { get; set; }
User()
{
BelongsTo = new List<Usergroup>();
}
}
public class Usergroup
{
int Id { get; set; }
string Name { get; set; }
List<User> Users { get; set; }
Usergroup()
{
Users = new List<User>();
}
}
internal class Relation
{
int userId;
int groupId;
}
now, whats the best way to implement it, and whats the fastest way to get all objects filled.
btw: i'm using subsonic to get all the database-records (for each table one select). and there are at least 1500 user and 30 groups.
my current code is based on foreach over the groups, relations.where for the groups, and then users.singleordefault to find the user and then add the user to the group and the group to the user. but it takes appx 30secs to do this.
my client-app is a webapp, so i store the results after all in a cached repository, thats why i'm doing it in this way!!
my current code is based on foreach over the groups, relations.where for the groups, and then users.singleordefault to find the user and then add the user to the group and the group to the user.
I don't understand what you're saying here.
it takes appx 30secs to do this
Whatever it is you're doing seems too slow, given there's only 1500 users and 30 groups.
now, whats the best way to implement it, and whats the fastest way to get all objects filled.
If you wanted ...
Either, all users, with the groups for each user
Or, all groups, with the users for each group
... then I might suggest selecting joined records from the database.
However, because ...
You want all data (joined in both directions)
You only have 1500 records
... then I suggest that you select everything as simply as you can, and join in it your application.
//select users
Dictionary<int, User> users = new Dictionary<int, User>();
foreach (User user in selectUsersFromDatabase())
{
users.Add(user.Id, user);
}
//select groups
Dictionary<int, Group> groups = new Dictionary<int, Group>();
foreach (Group group in selectGroupsFromDatabase())
{
groups.Add(group.Id, group);
}
//select relations
//and join groups to users
//and join users to groups
foreach (Relation relation in selectRelationsFromDatabase())
{
//find user in dictionary
User user = users[relation.userId];
//find group in dictionary
Group group = groups[relation.groupId];
//add group to user and add user to group
user.BelongsTo.Add(group);
group.Users.Add(user);
}
The best thing is to only retrieve the entity that was requested by the client but allow that entity to expose a method for retrieving all its related entities from the database. You shouldn't attempt to pull everything back at once from the database as you are not certain if the client even needs this data.
Do something like this:
public class User
{
int Id { get; set; }
string Name { get; set; }
public List<Usergroup> GetUserGroups()
{
// go and get the user groups
}
}
If you wanted to you could create a property that would hold this value as a member of the User class and use the GetUserGroups method to lazy-load the value for you.
Related
What I'm trying to solve for:
Cycle through a List that I have where:
If the list contains duplicate entries of "CompanyName", then grab the "UserEmail" properties for each user that belongs to the same "CompanyName" and append the email addresses together as One record for the Company (in a separate list object), so that the resulting List that I make would look equivalent to:
myList[0].CompanyName = "Company1" // Company1 is found in two separate records in my original List object.
myList[0].UserEmails = "user1#fake.com;user2#fake.com"
The model for my current List looks like:
CompanyName
UserEmail
UserPersonas (Looking for only users with a specific "Admin" string listed in their account properties)
The model for the resulting List should look like:
CompanyName
UserEmails
I'm querying an external program, getting all company names that have a UserPersonas that contains "Admin" (among their other entries) into one List object.
One company can have several users that have a "UserPersonas" that contains "Admin", so I want to only have one record per company with the semicolon appended email addresses as an entry in that CompanyName's record (as its own List object.)
Can I use another LINQ transaction to accomplish what I'm going for?
Below is a screenshot of what my current output from my List object looks like
var getDupes = bUsers.GroupBy(t => new { t.CompanyName, t.UserEmail }).ToList();
I'm not sure it 'll give you an explanation how it works internally, but nevertheless...
Assuming we have a class
public class SomeClass
{
public string CompanyName { get; set; }
public string UserEmails { get; set; }
}
we can do the next grouping and combining:
var result = list.GroupBy(e => e.CompanyName)
.Select(e => new SomeClass
{
CompanyName = e.Key,
UserEmails = string.Join("; ", e.Select(e => e.UserEmail).ToList())
});
I have a Conversations collection, containing Conversation documents like so (simplified to get to the point):
public class Conversation
{
public ObjectId Id { get; set; }
public Dictionary<ObjectId, DateTime> Members { get; set; }
}
The Dictionary Members is mapping the Member Ids to their entry date in the conversation.
Then, in the Conversations collection, if, for a particular User, I want to query all the Conversations he's in, what I'm doing is the following:
public List<Conversation> GetConversations(in User user)
{
Query = Builder.Eq(doc => doc.Members.ContainsKey(user.Id), true);
return Find(Query).ToList();
}
The question being, is this the right way to do it?
If it is, why? How does MongoDB process this kind of query (which at
first glance looks to be complex and demanding lot of computing
power)?
If it's not how'd you do it better?
Query = Builder.Eq(doc => doc.Members.ContainsKey(user.Id), true);
Looks like a collection scan.
Is it possible for you to track conversations in every Member document?
It can be achieved with array or subcollection.
I have ViewModel
UserGroup.cs
public class UserGroup
{
public User User { get; set; }
public List<Group> Groups { get; set; }
}
And I want to have the User and all related Groups. The problem is that the query returns duplicates of all users if there is more than 1 group related to him (and It's expected of course). The first thing that comes to my mind is to fetch all the users with single query and then foreach user to get the groups related to him and push it in the list. But I'm looking for a better way (if there is) to do that.
The relationship is made with junction table.
SQL Query
SELECT u.UserName, ug.GroupName FROM auth.UserUserGroup uug
INNER JOIN [auth].[User] u ON u.UserId = uug.UserId
INNER JOIN auth.UserGroup ug ON ug.UserGroupId = uug.UserGroupId
I'm sure someone will have a more elegant solution, but I've used Linq to construct a tiered object a number of times
var dbRecords = repository.Get(userId); // However you are getting your records, do that here
var result = dbRecords.GroupBy(x => x.UserId)
.Select(g => new UserGroup
{
User = new User { UserName = g.First().UserName },
Groups = g.Select(y => new Group { GroupName = y.GroupName }
}
);
I've tried to use your object names. Hopefully I didn't make any mistakes
I think I'm having a senior moment, but I also think I have not run into this situation before. I have two columns in my MVC5 Identity 2.1 Users table.
UserId | BannedBy (and also an IsBanned bool)
Both fields are userid guid strings. However, BannedBy refers to a different user in the same Users table.
When I display my view of banned users (a table and each row is one banned user), I don't want to show the BannedBy guid, I want to show the related UserName for that BannedBy guid. However, I can't seem to figure out what I need to do.
I've tried a ViewModel and method approach:
public ActionResult BannedUsers()
{
var bannedUsers = db.Users.Where(d => d.IsBanned);
var model = new BannedUsersViewModel
{
BannedUsers = bannedUsers,
BannedByUserName = GetUserName(bannedUsers.BannedBy)
};
return View(model);
}
Then like an outer approach to my viewmodel:
var model = new BannedUsersViewModel
{
BannedUsers = bannedUsers
};
model.BannedByUserName = GetUserName(model.bannedUsers.BannedBy);
However, it seems I can't use the bannedUsers.BannedBy (I also tried all that above with a capital B... BannedUsers.BannedBy) data before it's actually been rendered? And now I've scrapped the viewmodel and am trying to do like a related data join on my query:
db.Users.Join(d => d.BannedBy == d.UserId).Where(d => d.IsBanned);
(I'm sure this is way off, I'm just trying to give you an idea)
Does anyone know the proper way of doing this? I was also thinking about calling a method from my view, but seems like that would be breaking the MVC rules?
Thank you.
Update: Here is the GetUserName method:
public string GetUserName(string userId)
{
var result = db.Users.Find(userId);
return result.UserName;
}
Update #2: Here is the BannedUsersViewModel:
public class BannedUsersViewModel
{
public IEnumerable<ApplicationUser> BannedUsers { get; set; }
public string BannedByUserName { get; set; }
}
Update #3: A pic:
I am going to take a stab at this. We can modify it as needed (unless I am completely off base, in which case, I will delete this and we will all pretend it never happened). Does this get you in the ballpark:
public ActionResult BannedUsers()
{
var bannedUsers =
db.Users
.Where(d => d.IsBanned)
.Join(
db.Users,
bannee => bannee.BannedBy,
banner => banner.UserId,
(bannee, banner) => new BannedUser()
{
BannedByUserName = banner.UserName,
BannedUser = bannee
})
.AsEnumerable();
var model = new BannedUsersViewModel
{
BannedUsers = bannedUsers
};
return View(model);
}
public class BannedUsersViewModel
{
public IEnumerable<BannedUser> BannedUsers { get; set; }
}
public class BannedUser
{
public ApplicationUser BannedUser { get; set; }
public string BannedByUserName { get; set; }
}
The idea is that we get all of the banned users, join them to all of the users that banned those users and then group by the user that banned them. You end up with a collection of objects that have the user that banned other users and the users they banned.
So far, I went with #lazy's comment in doing a self join. I'm not sure how good I feel about it though. So essentially, I added this to my IdentityModels.cs under public class ApplicationUser : IdentityUser:
[ForeignKey("BannedBy")]
public virtual ApplicationUser BannedByUser { get; set; }
Then in my controller, changed my original query to a list:
var bannedUsers = db.Users.Where(d => d.IsBanned).ToList();
return View(bannedUsers);
(If I don't convert to list, it complains about having more than one data reader open.) Then in my View in my foreach loop:
#Html.DisplayFor(modelItem => item.BannedByUser.UserName)
And boom:
I'm a little worried of the performance impact?...especially for a page that isn't used that often and is not really that important. Is there an impact if the page isn't being called (like with the Index that was created and such)? I'm also a little leery since there seems to be some magic happening with Identity.
Anyway, I'm still open to other ideas...or thoughts about this one. Thanks again for everyone's help.
I am developing a web application with ASP.net MVC and I have a database which I'm connecting with an ADO.NET Entity Framework.
In this database I have a table Group with GroupId as a primary key, another table UserInfo with UserId as its primary key and another table GroupUser which is not considered as an Entity but rather as an Association Set since it is used as a mean to have a many to many relationship between Group and User.
GroupUser contains GroupId and UserId as a composite key and both are foreign keys to the respective tables.
These are the Group and User classes generated (regarding this relationship)
// Group
public Group()
{
this.UserInfo1 = new HashSet<UserInfo>();
}
public virtual UserInfo UserInfo { get; set; }
public virtual ICollection<UserInfo> UserInfo1 { get; set; }
// UserInfo
public UserInfo()
{
this.Group = new HashSet<Group>();
this.Group1 = new HashSet<Group>();
}
public virtual ICollection<Group> Group { get; set; }
public virtual ICollection<Group> Group1 { get; set; }
To add a record to this GroupUser table I am doing this
int ownerId = Convert.ToInt32(WebSecurity.CurrentUserId);
group.UserInfo1.Add(conn.UserInfo.Find(ownerId));
However I am stuck on how to find a record in this table. How can I check if a particular user belongs to this group by having groupId and userId provided here?
Group group = conn.Group.Find(id);
int userId = Convert.ToInt32(WebSecurity.CurrentUserId);
Thanks for any help :)
With the starting point you have provided in order to test if the user is in that group you can use:
Group group = conn.Group.Find(id);
int userId = Convert.ToInt32(WebSecurity.CurrentUserId);
bool isUserInGroup = group.UserInfo1.Any(u => u.UserId == userId);
It will work because when you access group.UserInfo1 (with the Any extension method in this case) Entity Framework will run a second query (the first one was Find) to load all related UserInfo entities of the given group into the group.UserInfo1 collection. This query is based in lazy loading which is enabled by default if the navigation collection is declared as virtual (which it is in your example). After loading the collection the Any call is a check in memory (no database query here anymore) if the group.UserInfo1 collection contains at least one entity that fulfills the supplied condition, i.e. contains a user with that userId.
However, this is not the best solution because - as said - it will cause two queries (Find and lazy loading of the collection). Actually you can test if the user is in the group by a single database query alone and you don't even need to load any entities for that test, just directly return the bool result from the database:
int userId = Convert.ToInt32(WebSecurity.CurrentUserId);
bool isUserInGroup = conn.Group
.Any(g => g.GroupId == id && g.UserInfo1.Any(u => u.UserId == userId));
The result will be false if the group with the id does not exist or if it doesn't have a related user with userId.