How to delete or update using inner join in Entity Framework? - entity-framework

I need to delete some records using inner join in Entity Framework.
For example, I have User, Role and UserRoleMapping tables:
User => Id, Name
Role => Id, Name
UserRoleMapping => Id, UserId, RoleId
Now I need to delete the users who belong to role with Id = 2.
I need to fire the query as shown below
Delete user
from User
inner join UserRoleMapping on User.Id = UserRoleMapping.UserId
where UserRoleMapping.RoleId = 2
Is this is possible in Entity Framework?

In EF you need first load entities, select items and then DeleteObject . You need do it like:
using (var context = new YourContext())
{
var item = (from user in context.User
join userRoleMapping in context.UserRoleMapping on user.Id equals userRoleMapping.UserId
where userRoleMapping.RoleId == 2
select user).ToList().ForEach(context.User.DeleteObject);
context.SaveChanges();
}
Note:
ObjectContext.DeleteObject(entity) marks the entity as Deleted in the context. (It's EntityState is Deleted after that.) If you call SaveChanges afterwards EF sends a SQL DELETE statement to the database. If no referential constraints in the database are violated the entity will be deleted, otherwise an exception is thrown
or
using (var context = new YourContext())
{
var items = (from user in context.User
join userRoleMapping in context.UserRoleMapping on user.Id equals userRoleMapping.UserId
where userRoleMapping.RoleId == 2
select user).ToList();
foreach (var item in items)
{
context.Entry(item).State = EntityState.Deleted;
}
context.SaveChanges();
}
or using ExecuteStoreCommand, here you find more
using (var context = new YourContext())
{
context.ExecuteStoreCommand("DELETE FROM USER INNER JOIN USERROLEMAPPING ON USER.ID = USERROLEMAPPING.USERID WHERE USERROLEMAPPING .ROLEID = {0}", customId);
}

Related

Get missing Ids from a list of ids

I'm trying to retrieve a list of missing Ids in database from another list of Ids in Entity Framework Core.
Is there a way to get this call in one line?
public static async Task<IEnumerable<TKey>> GetMissingIds<T, TKey>(
this IQueryable<T> db, IEnumerable<TKey> ids)
where T : BaseEntity<TKey>
{
var existingIds = await db
.AsNoTracking()
.Where(entity => ids.Contains(entity.Id))
.Select(entity => entity.Id)
.ToListAsync();
return ids.Except(existingIds);
}
EF Core supports only Contains with local collections (with small exceptions), so there is no effective way to retrieve Ids which are not present in database via LINQ Query.
Anyway there is third-party extensions which can do that linq2db.EntityFrameworkCore (note that I'm one of the creators).
Using this extension you can join local collection to LINQ query:
public static Task<IEnumerable<TKey>> GetMissingIds<T, TKey>(
this IQueryable<T> query, IEnumerable<TKey> ids, CabcellationToken cancellationToken = default)
where T : BaseEntity<TKey>
{
// we need context to retrieve options and mapping information from EF Core
var context = LinqToDBForEFTools.GetCurrentContext(query) ?? throw new InvalidOperationException();
// create linq2db connection
using var db = context.CreateLinqToDbConnection();
var resultQuery =
from id in ids.AsQueryable(db) // transform Ids to queryable
join e in query on id equals e.Id into gj
from e in gj.DefaultIfEmpty()
where e == null
select id;
// there can be collision with EF Core async extensions, so use ToListAsyncLinqToDB
return resultQuery.ToListAsyncLinqToDB(cancellationToken);
}
This is the sample of generated query:
SELECT
[id].[item]
FROM
(VALUES
(10248), (10249), (10250), (10251), (10252), (10253), (10254),
(10255), (10256), (10257), (10023)
) [id]([item])
LEFT JOIN (
SELECT
[e].[OrderID] as [e]
FROM
[Orders] [e]
) [t1] ON [id].[item] = [t1].[e]
WHERE
[t1].[e] IS NULL

Why only primitive types or enumeration types are supported in this context using EF?

In my application I try to execute a Join query using EntityFramework, Repository Pattern, It is throwing bellow error. What is the problem in the link query? Let me explain in details
Error Description
Unable to create a constant value of type 'Anonymous type'. Only
primitive types or enumeration types are supported in this context
Initialization
_repository = new GenericRepository<WeeklyEntry>();
_repositoryGroup = new GenericRepository<Group>();
_repositoryGroupMember = new GenericRepository<GroupMember>();
Fetching Logic
var groups = _repositoryGroup.GetAll().OrderBy(o => o.ID)
.Select(s => new { s.ID, s.Name }).ToList();
var groupMembers = _repositoryGroupMember.GetAll().OrderBy(o => o.ID)
.Select(s => new { s.GroupID, s.ID, s.Name })
.ToList();
Main Query [Not Working]
var results = (from we in _repository.GetAll()
join g in groups on we.GroupID equals g.ID into grpjoin
from g in grpjoin.DefaultIfEmpty()
join gm in groupMembers on we.DepositedByMemberID equals gm.ID into gmjoin
from gm in gmjoin.DefaultIfEmpty()
where gm.GroupID == g.ID
select new
{
GroupID = g.ID,
GroupName = g.Name,
MemberID = grpmresult.ID,
grpmresult.Name,
we.ID
}).ToList();
To try to achieve bellow SQL Query
select w.GroupID, g.Name, gm.Name, w.ID
from [dbo].[WeeklyEntry] as w
left outer join [dbo].[Group] as g on g.ID = w.GroupID
left outer join [dbo].[GroupMember] as gm on gm.GroupID = g.ID
AND gm.ID = w.DepositedByMemberID
order by w.GroupID
Strange Findings
If I include .ToList(); with each query like from we in _repository.GetAll().ToList() the entire query will work & give expected result without Any ERROR!!!
So if I convert each query return type to In-memory Or IEnumerable<> it is working as expected without any error but IQueryable<> query not working as expected.
New Code Snippet [Working]
var results = (from we in _repository.GetAll().ToList()
join g in groups on we.GroupID equals g.ID into grpjoin
from g in grpjoin.DefaultIfEmpty()
join gm in groupMembers on we.DepositedByMemberID equals gm.ID into gmjoin
from gm in gmjoin.DefaultIfEmpty()
where gm.GroupID == g.ID
select new {...}.ToList();
You can't join a database table with an in-memory collection (in your case, List):
Unable to create a constant value of type Only primitive types or enumeration types are supported in this context
You're converting these to in-memory collections by calling ToList:
var groups = _repositoryGroup.GetAll().OrderBy(o => o.ID)
.Select(s => new { s.ID, s.Name }).ToList();
var groupMembers = _repositoryGroupMember.GetAll().OrderBy(o => o.ID)
.Select(s => new { s.GroupID, s.ID, s.Name })
.ToList();
which you then try and join in the next bit of code.
If you simply remove those ToList calls, the join should work (you can keep the one for the final result, if you prefer it).
Remember that IEnumerable is lazy and will only actually run a SQL query if you "execute it" by iterating (usually via a foreach loop or some function like ToList).
Let me explain!
You have a GenericRepository<T> class, that is something like this:
public class GenericRepository<T>
{
MyDbContext dbContext;
public GenericRepository()
{
dbContext = new MyDbContext();
}
public IQueryable<T> GetAll()
{
// whatever
}
}
and then you have:
_repository = new GenericRepository<WeeklyEntry>();
_repositoryGroup = new GenericRepository<Group>();
_repositoryGroupMember = new GenericRepository<GroupMember>();
var groups = _repositoryGroup.GetAll().ToList(); // other query operators are irrelevant and removed
var groupMembers = _repositoryGroupMember.GetAll().ToList();
Calling ToList() runs your queries and bring the data to memory. Now you have two in-memory collection (List<T>).
When you write:
var results = from we in _repository.GetAll() // <-- this is IQueryable<T>
join g in groups // <-- this is List<T> (IEnumerable<T>)
on we.GroupID equals g.ID into grpjoin
...
you are joining an IQueryable, with an in-memory list. When running this query, EF has no way to know that your in-memory lists (groups and groupMembers) are actually queries from the database. It only sees two lists, containing some data. It has no way to translate that into SQL and hence throws an error.
To fix this, you should remove calls to ToList(). That way, you have three IQueryables that are joined together. EF can translate that into SQL, only if they are from a single DbContext. And since they are not, it throws another error, telling you exactly that.
You create an instance DbContext for each of the GenericRepository<T> instances. So, groups, groupMembers and we come from three different DbContexts.
To solve this error, you should somehow manage to use a single DbContext for all your GenericRepository<T>s.
For example:
using (var dbContext = new MyDbContext())
{
var groups = dbContext.Set<Group>();
var groupMembers = dbContext.Set<GroupMember>();
var results = from we in dbContext.Set<WeeklyEntry>()
join g in groups
on we.GroupID equals g.ID into grpjoin
...
}

JPA join by id not entity

I have created an query with criteria api that retrieves an entity by another linked entity:
public List<Booking> getBookingsByUser(User user) {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Booking> createQuery = cb.createQuery(Booking.class);
Root<Booking> booking = createQuery.from(Booking.class);
Join<Booking, UsersProjects> join = booking.join(Booking_.userProject, JoinType.INNER);
createQuery.where(cb.equal(join.get(UsersProjects_.user), user));
createQuery.select(booking);
return em.createQuery(createQuery).getResultList();
}
This is working fine. But how to rewrite this to find entities by userId (Long)?
Metamodel of User has User_.id (SingularAttribute).
User is also an Entity. And a "UsersProject" hast exactly one User and one Project.
Add one more join clause between UserProjects and User:
Join<Booking, UsersProjects> userProjectsJoin = booking.join(Booking_.userProject, JoinType.INNER);
Join<UsersProjects, User> userJoin = userProjectsJoin.join(UserProjects_.user);
createQuery.where(cb.equal(userJoin.get(User_.id), userId));

LINQ query to fetch entities that include custom formatted field of associated entities?

A bit rusty on LINQ
I want to get a single result from related tables for a given user. See schema below.
Each user has one or more roles. I want a list of usernames and a custom string that is a list of their roles in a format such as "Role1 - Role2 - Role3", where the values are the RoleNames associated with the UserRole/Role for that user.
Role
=====
RoleId
RoleCode
RoleName
UserRole
========
UserRoleId
RoleId
UserId
Users
======
UserId
UserName
Testing it out in LINQpad, I can get a list of usernames and their roles, but instead of the RoleName, I want a single field in the result to be a formatted string of ALL the users roles, as mentioned above.
Here is what I have now. How can I construct a list of the roles for each user?
from u in Users
join ur in UserRoles on u.UserId equals ur.UserKey
join r in Roles on ur.RoleKey equals r.RoleId
select new {
u.UserId,
u.UserName,
r.RoleName
}
Add group by UserName to your LINQ query, and use string.Join to format the roles separated by "-".
You can test this in LINQPad -
var Roles = new [] {new{RoleId=1,RoleCode="SU",RoleName="Super User"},new{RoleId=2,RoleCode="PU",RoleName="Power User"}};
var Users = new [] {new{UserId=1,UserName="Bit Shift"},new{UserId=2,UserName="Edward"}};
var UserRoles = new [] {new{UserRoleId=1,RoleId=1,UserId=1},new{UserRoleId=2,RoleId=2,UserId=1},new{UserRoleId=3,RoleId=2,UserId=2}};
var userRoles = from u in Users
join ur in UserRoles on u.UserId equals ur.UserId
join r in Roles on ur.RoleId equals r.RoleId
select new
{
u.UserId,
u.UserName,
r.RoleName
}
into userRole
group userRole by userRole.UserName into userGroups
select new{ UserName=userGroups.Key, Roles = string.Join(" - ", userGroups.Select(ug => ug.RoleName))};
userRoles.Dump();
More succinct version, which includes UserId+UserName in result -
var userRoles = from u in Users
join ur in UserRoles on u.UserId equals ur.UserId
join r in Roles on ur.RoleId equals r.RoleId
group r by u into userGroups
select new{ User=userGroups.Key, Roles = string.Join(" - ", userGroups.Select(r => r.RoleName))};
userRoles.Dump();
When executing against SQL database, you need to the query split into two parts, as String.Join is not supported by LINQ to SQL, like this -
var userRoleGroups = (from u in Users
join ur in UserRoles on u.UserId equals ur.UserId
join r in Roles on ur.RoleId equals r.RoleId
group r by u into userGroups select userGroups)
.ToList(); // This causes SQL to be generated and executed
var userRoles = from userGroups in userRoleGroups select(new{ User=userGroups.Key, Roles = string.Join(" - ", userGroups.Select(r => r.RoleName))});
userRoles.Dump();
Or try using Aggregate instead of String.Join, as you suggested, like this -
var userRoles = from u in Users
join ur in UserRoles on u.UserId equals ur.UserId
join r in Roles on ur.RoleId equals r.RoleId
group r by u into userGroups
select(new{
User=userGroups.Key,
Roles = userGroups.Select(s => s.RoleName).Aggregate((current, next) => current + " - " + next)});
userRoles.Dump();
Group the RoleNames over the UserNames and use String.Join to get the desired result.
from u in Users
join ur in UserRoles on u.UserId equals ur.UserKey
join r in Roles on ur.RoleKey equals r.RoleId
group r.RoleName by u.UserName into grp
select new {
UserName = grp.Key,
Roles = String.Join(" - ", grp)
};
This will not return the UserIds. If they're important for the result, you need to change the code to
.... join as above
group r.RoleName by new {u.UserId, u.UserName} into grp
select new {
grp.Key.UserName,
grp.Key.UserId,
Roles = String.Join(" - ", grp)
};
This will create a grouping key containing both UserId and UserName, so you have that available in your select.

EF - how to add Relationship between 2 tables and insert refrenceId in the masterTable?

For example I have 3 tables:
Users -- the master table
{ Id, Name }
Permissions -- details
{ Id, PermissionTitle }
UserPermissions -- is a relation table between User and its Permissions
{ UserId , PermissionId}
I have 2 users in the tbUsers ( {1,"user1"} , {2,"user2"} )
and I have 3 permissions in the tbPermissions ( {1,"perm1"} , {2,"perm2"} , {3,"perm3"} )
now I want to add perm1 and perm2 to user1. What should I do in EF?
(I don't want to create/insert any Users or Permissions, I just want to add a relationship between them in the relation table)
because of EF, I don't have UserPermissions table in my dataModel.
If you want to load entities first you can do:
using (var context = new YourContext())
{
var user1 = context.Users.Single(u => u.Id == 1);
var perm1 = context.Permissions.Single(p => p.Id == 1);
var perm1 = context.Permissions.Single(p => p.Id == 2);
user1.Permissions.Add(perm1);
user1.Permissions.Add(perm2);
context.SaveChanges();
}
If you know Ids and you don't want to load entities first you can do:
using (var context = new YourContext())
{
var user1 = new User {Id = 1};
var perm1 = new Permission {Id = 1};
var perm1 = new Permission {Id = 2};
context.Users.Attach(user1);
context.Permissions.Attach(perm1);
context.Permissions.Attach(perm2);
user1.Permissions.Add(perm1);
user1.Permissions.Add(perm2);
context.SaveChanges();
}
These two approaches can be combined - for example you can load user from DB and create dummy objects only for permissions.
Users should have a navigation property Permissions so you need to add the permission to that collection. Should look similar to this:
user.Permissions.Add(permission1);
user.Permissions.Add(permission2);
context.SaveChanges();