Entity Framework remove N:M relation by ids - c#

I have a Simple table like this:
User(Id, FirstName, ...) <===> Role(Id, Title, ...)
witch have N:M relation
what i want to do is to remove the relation in between them by having there Ids, so my method should be like this :
public void UnlinkUsersFromRoles(int[] roleIds, int[] userIds)
{
var myContext = new DefaultContext();
// ?? <= how to break the relation without loading unnecessary columns
}

If this is many-to-many relationship, there must be a join table in between: for these tables User and Role, let's say it's called UserRole, and is a simple join table (i.e., no other columns on that table, other than the FK ids to the other two tables):
public class UserRole
{
public int UserId { get; set; }
public int RoleId { get; set; }
}
With an explicitly defined join table such as this, UnlinkUsersFromRoles could be defined as follows:
public void UnlinkUsersFromRoles(int[] roleIds, int[] userIds)
{
using (var context = new DefaultContext())
{
foreach (var ur in context.UserRoles
.Where(u => userIds.Contains(u.UserId))
.Where(r => roleIds.Contains(r.RoleId))
.ToArray())
{
context.UserRoles.Remove(ur);
}
context.SaveChanges();
}
}
You're certainly going to wind up loading all the UserRole rows to be removed in when you call ToArray, but this is certainly better than loading in all the related User and Role rows.

Can you just do this:
var rolesToRemove = myContext.Roles.Where(r=> roleIds.Contains(r.Id)).ToArray();
foreach(var user in myContext.Users.Where(u=> userIds.Contains(u.Id)){
forearch(var var role in rolesToRemove) {
user.Roles.Remove(role);
}
}
myContext.SaveChanges();
When you say:
how to break the relation without loading unnecessary columns
You mean, the above code doesn't fullfill you requeriment?
EDIT:
If you have a explicity relationship class like UserInRoles, you should use John Castleman Answer, or just create one, map the ForeignKeys and use his solution.

Related

use Linq to form a relationship where none exists in the database

I've been using the .Net Core Linq expressions .Include and .ThenInclude to great success with my Entity Framework Core project.
However, I need to combine 2 models that are not in any type of relationship in the database.
My SQL query looks like this:
SELECT * FROM selectedEnzymes se
LEFT JOIN enzymeDefinitions ed ON se.selectedEnzymeID = ed.selectedEnzymeID
WHERE se.ecosystemID = 7
selectedEnzymes and enzymeDefinitions are both models.
But there is no relationship between them even though they both contained a selectedEnzymeID. In the database, there is no key.
So I was wondering, is there a way to use Linq to combine two models in such a way if no relationship exists?
Thanks!
You can use the LINQ Join and Select as you do in SQL.
Starting with something like this as models and list of both Classes:
public class SelectedEnzymes
{
public int SelectedEnzymeId { get; set; }
public int EcosystemId { get; set; }
public string OtherPropertySelectedEnzymes { get; set; }
}
public class EnzymeDefinitions
{
public int SelectedEnzymeId { get; set; }
public string OtherPropertyEnzymeDefinitions { get; set; }
}
List<SelectedEnzymes> selectedEnzymesList = new List<SelectedEnzymes>();
List<EnzymeDefinitions> enzymeDefinitionList = new List<EnzymeDefinitions>();
You are able to do something like this:
var query = selectedEnzymesList // table in the "FROM"
.Join(enzymeDefinitionList, // the inner join table
selected => selected.SelectedEnzymeId, // set the First Table Join parameter key
definition => definition.SelectedEnzymeId, // set the Secont Table Join parameter key
(selected, definition) => new { SelectedEnzyme = selected, EnzymeDefinition = definition }) // selection -> here you can create any kind of dynamic object or map it to a different model
.Where(selectAndDef => selectAndDef.SelectedEnzyme.EcosystemId == 7); // where statement
So I was wondering, is there a way to use Linq to combine two models
in such a way if no relationship exists?
In fact, this is similar to the method of obtaining two related tables.
You can directly use the following linq to achieve:
var data = (from se in _context.SelectedEnzymes
join ed in _context.EnzymeDefinitions
on se.SelectedEnzymeId equals ed.SelectedEnzymeId
where se.EcosystemId == 7
select new { se.Name,se.LocationId, ed.Name,ed.CountryId }).ToList();
Here is the result:

LINQ to Entities: perform joins on many-to-many relationships (code first)

I have the following model:
[Table("Experiments")]
public class Experiment
{
...
public virtual ICollection<ExperimentType> ExperimentTypes { get; set; }
public Experiment()
{
ExperimentTypes = new List<ExperimentType>();
}
}
[Table("ExperimentTypes")]
public class ExperimentType
{
...
public virtual ICollection<Experiment> Experiments { get; set; }
public ExperimentType()
{
Experiments = new List<Experiments>();
}
}
The DbSet contains:
public DbSet<Experiment> Experiments { get; set; }
public DbSet<ExperimentType> ExperimentTypes{ get; set; }
And this creates a table on SQL, called ExperimentExperimentTypes.
Now, I would like to perform a LINQ join, like:
var query =
from e in database.Experiments
join eet in database.ExperimentExperimentTypes on eet.Experiment_Id equals eet.ExperimentType_Id ...
But obviously database.ExperimentExperimentTypes in not recognized in code.
I tried a lot of things in order to tell the code that there is this table, I also tried to create the corresponding c# class, but I'm not getting any result.
How can achieve that?
So you have two tables: Experiment and ExperimentType. There is a many-to-many relation between those two: every Experiment is an experiment of zero or more ExperimentTypes; every ExperimentType is the type of zero or more Experiments.
This many-to-many can be seen in your class definitions. The virtual ICollection<...> on both sides indicates the many-to-many relationship.
In relational databases this many-to-many relation is implemented using a junction table. However, in entity framework you seldom see the junction table. It is in your database, however, you can't access it using your DbContext
But how am I going to perform a join between Experiments and
ExperimentTypes if I can't access the junction table?
Well, Pooh bear should go back to his thinking spot. You don't want to join tables, you want Experiments, each with its ExperimentTypes.
So why not just do the query using the ICollection<...>?
var experiments = myDbContext.Experiments
.Where(experiment => ...) // only if you don't want all experiments
.Select(experiment => new
{ // Select only the properties you actually plan to use
Id = experiment.Id,
Name = experiment.Name,
...
// get all or some of its ExperimentTypes
ExperimentTypes = experiment.ExperimentTypes
.Where(experimentType => ...) // only if you don't want all experiment types
.Select(experimentType => new
{
// again: select only the properties you plan to use
Id = experimentType.Id,
...
})
.ToList(),
});
Entity framework knows the many-to-many, it knows that for this a triple join with the junction table is needed, and will perform this triple join.
Internally this will be a GroupJoin, you'll get Experiments, each with their ExperimentTypes. You even get Experiments that don't have any ExperimentType yet.
If you really want the inner join, you'll have to flatten the Experiments with their ExperimentTypes. This is done using the overload of SelectMany that has a resultSelector as parameter
// flatten the Experiments with their experimentTypes
var flatInnerJoin = myDbContext.Experiments.SelectMany(experiment => experiment.ExperimentTypes,
// from each experiment and one of its experimentTypes make one new object
(experiment, experimentType) => new
{
ExperimentId = experiment.Id,
ExperimentTypeId = experimentType.Id,
...
});
})
Nota bene! This way you won't get the Experiments that have no ExperimentTypes, just as in a standard inner join.

EF code first many to many relation when no relation in DB

Suppose I have two classes model like:
public class AuthorityUser
{
public string GUID { get; set; }
public int UserID { get; set; }
public ICollection<Authority1> Authorities { get; set; }
public AuthorityUser()
{
Authorities = new HashSet<Authority1>();
}
}
public partial class Authority1
{
public virtual int AID
{
get;
set;
}
public virtual ICollection<AuthorityUser> AuthorityUsers { get; set; }
public Authority1()
{
AuthorityUsers = new HashSet<AuthorityUser>();
}
}
I am going to make Many To Many relation between them based on UserAuthorityMap connected table in DB.
so I did this to make M:N relation in OnModelCreating()
modelBuilder.Entity<AuthorityUser>().ToTable("Gainer").HasKey(x => x.UserID);
modelBuilder.Entity<Authority1>().ToTable("Authority").HasKey(x => x.AID);
modelBuilder.Entity<AuthorityUser>()
.HasMany<Authority1>(s => s.Authorities)
.WithMany(c => c.AuthorityUsers)
.Map(cs =>
{
cs.MapLeftKey("UserID");
cs.MapRightKey("AID");
cs.ToTable("UserAuthorityMap");
});
As I mentioned in title there is no relation between them in DB so the diagram in DB is like picture below :
when I run this :
dbContext.AuthorityUsers.SingleOrDefault(x => x.UserID == 65);
the related Authorities won't be loaded from DB.
so should I use HasDatabaseGeneratedOption(DatabaseGeneratedOption.None) to make it right or something else?
Since Authorities navigation property is not virtual, lazy loading has been turned off and thus you have 2 options left to load them.
Option 1: Eager Loading
dbContext.AuthorityUsers.Include(x => x.Authorities).SingleOrDefault(x => x.UserID == 65);
Note: Include is an extension method in the System.Data.Entity namespace so make sure you are using that namespace.
Option 2: Explicit Loading
var users = dbContext.AuthorityUsers.SingleOrDefault(x => x.UserID == 65);
dbContext.Entry(users).Collection(p => p.Authorities).Load();
Please see this article for more details.
If you followed the Entity Framework Code-First conventions you wouldn't have this problem.
If you really need to use non-conventional names for your tables and your primary keys, then indeed your two ModelBuilder statements for AuthorityUser and Authority will do what you want.
However, to make your many-to-many relationship easier, reconsider your method, and make your life easier by following the entity-framework conventions for many-to-many relation
In your case this would lead to two changes:
Make AuthorityUser.Authorities virtual
Let your classes represent your tables: let it be simple POCOs: no HashSet, no Constructor.
The reason to make your table classes simple POCOs, is because the class represents a table in a database. This table has no HashSet, and if you don't need it, why limit yourself to a HashSet? (See later)
In your case the proper many-to-many without the need tell the model builder that you configured a many-to-many would be:
class AuthorityUser
{
// Primary Key (reconsider: Id)
public int UserID { get; set; }
// an AuthorityUser belongs to zero or more Authorities (many-to-many)
public virtual ICollection<Authority> Authorities { get; set; }
... // other properties
}
class Authority
{
// primary key (reconsider: Id)
public int AID {get; set;}
// an Authority has zero or more AuthorityUsers (many-to-many)
public virtual ICollection<AuthorityUser> AuthorityUsers { get; set; }
... // other users
}
class MyDbContext : DbContext
{
public DbSet<AuthorityUser> AuthorityUsers {get; set;}
public DbSet<Authority> Authorities {get; set;}
}
You already understood that you need some Model Building to inform entity framework about your non-conventional primary keys and table names.
But removing the HashSet and declaring both ICollections in the many-to-many is enough for entity framework to understand that a many-to-many is intended. You don't need to do some model building for this. Enityt Framework will create a junction table and use it whenever needed.
When using the many-to-many you won't do a join with the junction table. Instead you think in collections:
Give me all AuthorityUsers that have xxx with their Authorities that have yyy
var result = dbContext.AuthorityUsers
.Where(authorityUser => xxx)
.Select(authorityUser => new
{
// take only the properties from authorityuser you'll need:
UserId = authorityUser.UserId,
GUID = authorityUser.GUID,
// take all authorities from this authorityUser that have yyy
Authorities = authorityUser.Authorities
.Where(authority => yyy)
.Select(authority => new
{
// take only the authority properties you'll use:
AID = authority.AID,
...
})
.ToList(),
});
}
Entity Framework knows that this needs two joins with the junction table, and perform the proper SQL statement for you.
The query: give me all Authorities that ... with all their AuthorityUsers which ... is similar.
Is your hashset needed?
No, in all your queries, entity framework will replace the HashSet by its own virtual ICollection<...>.
Your HashSet would only be useful if you'd add a new Authority with its AuthorityUsers. Without HashSet this would be like:
Authority addedAuthority = myDbContext.Authorieties.Add(new Authority()
{
GUID = ...
... // other properties
// this Authority has the following AuthorityUsers:
AuthorityUsers = new List<AuthorityUsers>()
{
new AuthorityUser() {...},
new AuthorityUser() {...},
...
},
});
Instead of a List you couls assign any ICollection, like an array, or even from a Dictionary:
Dictionary<int, AuthorityUser> authorityUsers = ...
Authority addedAuthority = myDbContext.Authorieties.Add(new Authority()
{
...
// this Authority has the following AuthorityUsers:
AuthorityUsers = authorityUsers.Values,
});
So you see that removing the HashSet give you more freedom to provide the ICollection: Better reusability. Less code, which makes it better understandable when someone else needs to maintain it. Besides it is a waste of processing power to create a HashSet that is most of the time not used.

Left join with linq

I have a Activities model as follows
public class Activity
{
// this is email
public string CreatedBy {set;get;}
// Relationship
public ApplicationUser User{set;get;}
}
Then I have the User model:
public class ApplicationUser
{
// ID from Identity
public string Id{set;get;}
public string Email {set;get;}
}
Of course I have the corresponding tables in the database.
What i need to find out is the users who didnt do any activity.
While the code below works, it is not efficient and times out. Because I have 500K activity in the Activities table.
var userz = _db.Users.AsNoTracking();
var groupedUsers = _db.Activities.AsNoTracking().GroupBy(x => x.CreatedBy).Select(group => new { CreatedBy = group.Key, Count = 1 }).Select(x=> x.CreatedBy);
var result = userz.Where(x => groupedUsers.Contains(x.Email) == false);
I tried the same Query for Navigation property, which is indexed, ie: User above. Yet the query times out.
Is there a more efficient solution for this using left join?
You should be better of with foreign keys but if this is really how your classes look you could try
_db.Users.Where(u => !_db.Activities.Any(u => a.ApplicationUser == u));

Accessing a record from an association set

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.

Categories