Filter related entities or navigation property where there is a join table in Entity Framework 6 - c#

I have three entities Role, RoleUser and User
I want to select each Compulsory Role and load the related User's where the RoleUser.RecordID in the join table is equal to a given value.
Using the UOW and GenericReposiity Get(...) method I would call ...
.Get(role => role.Compulsory == true, null, "RoleUser.User") to select all the compulsory role's and load all User for the navigation RoleUser.User.
How can I filter them, is it possible using the implemented Get() method?
Entities
public class Role
{
public int RoleID { get; set; }
public bool Compulsory { get; set; }
public virtual IList<RoleUser> RoleUser { get; set; }
}
public class RoleUser
{
public string UserID { get; set; }
public virtual User User { get; set; }
public Guid RecordID { get; set; }
public virtual Record Record { get; set; }
public int RoleID { get; set; }
public virtual Role Role { get; set; }
}
public class User
{
public string userID { get; set; }
}
Get
public virtual IList<TEntity> Get(
Expression<Func<TEntity, bool>> filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
string includeProperties = "")
{
IQueryable<TEntity> query = dbSet;
if (filter != null)
{
query = query.Where(filter);
}
foreach (var includeProperty in includeProperties.Split
(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
query = query.Include(includeProperty);
}
if (orderBy != null)
{
return orderBy(query).ToList();
}
else
{
return query.ToList();
}
}

No , you cant filter the RoleUser from this Get method. if you want to filter RoleUser you want to project them into another list.
You can filter your Role based on the RoleUser table.
Include will always bring full data in the table unless you use IQuerable Projection.
var rolesWithAnyUserName = UnitOfWork.Role
.Get(role => role.Compulsory == true && role.RoleUser
.Any(g=>g.RoleUserName=="Name"), null, "RoleUser.User")
This will only filter the Role based on the RoleUser filter
If you need to filter Include tables write the query where you can send IQuerable to the database
Try something like this , this will filter both Role and RoleUser
var result = DataContext.Role.Where(role => role.Compulsory == true )
.Include(gl => gl.RoleUser)
.Select(x => new
{
role = x,
roleUsers= x.RoleUser.Where(g => g.Id == 1),
}).ToList();

Related

How to make Entity Framework only filter data when query fields are not null?

I have an mvc .net core application where the user is displayed some data, and can filter that data based on some input that he/her gives.
If no filters/constraints are given, then the while set of entities should jsut be returned.
I found this example, and found the second answer to be quite neat, with regards to what I want to do.
So I added this thing at the bottom of my controller:
public static class QueryableEx
{
public static IQueryable<T> Where<T>(
this IQueryable<T> #this,
bool condition,
Expression<Func<T, bool>> #where)
{
return condition ? #this.Where(#where) : #this;
}
}
And then made this controller action which filters by one of three possible inputs:
[HttpPost]
public IActionResult query(QueryModel query)
{
List<CustomerModel> customers = new List<CustomerModel>();
var db = new DemoContext();
customers = db.Customers
.Where(query.Name != null, x => x.Name == query.Name)
.Where(query.Surname != null, x => x.Surname == query.Surname)
.Where(query.Age != null, x => x.Age == query.Age)
.ToList();
return View("Index", customers);
}
This works like a charm, If I input a certain name, then I only get the results with that name and vice versa.
There is an issue though. If all of the input fields are null, then everything is filtered out. I want the opposite to happen, if no filters have been entered, then just return everything.
How do I do this? Making sure that no filtering happens if all the input fields are empty?
EDIT
By request, I here is the model I use for queries
public class QueryModel
{
public string Name {get;set; }
public string Surname { get; set; }
public uint Age { get; set; }
}
And here is the customer one:
public class CustomerModel
{
public int Id{get;set; }
[Required]
public string Name {get;set; }
[Required]
public string Surname { get; set; }
[Required]
[Range(18,110)]
public uint Age { get; set; }
[Required]
public virtual AdressModel Adress { get; set; }
[Required]
public DateTime Created { get; set; }
[Required]
public virtual List<PurchaseModel> purchases { get; set; }
}
Your model parameters are not nullable, so I suspect that you'll end up looking for customers with Age equal Zero, hence no results.
Try:
customers = db.Customers
.Where(query.Name != default, x => x.Name == query.Name)
.Where(query.Surname != default, x => x.Surname == query.Surname)
.Where(query.Age != default, x => x.Age == query.Age)
.ToList();
Change 'null' to 'default' in each case.

Get objects whose property does not exist in enumerable

Multiple answers have led me to the following 2 solutions, but both of them do not seem to be working correctly.
What I have are 2 objects
public class DatabaseAssignment : AuditableEntity
{
public Guid Id { get; set; }
public string User_Id { get; set; }
public Guid Database_Id { get; set; }
}
public class Database : AuditableEntity
{
public Guid Id { get; set; }
public string Name { get; set; }
public string Server { get; set; }
public bool IsActive { get; set; }
public Guid DatabaseClientId { get; set; }
}
Now, the front-end will return all selected Database objects (as IEnumerable) for a given user. I am grabbing all current DatabaseAssignments from the database for the given user and comparing them to the databases by the Database.ID property. My goal is to find the DatabaseAssignments that I can remove from the database. However, my solutions keep returning all DatabaseAssignments to be removed.
if (databases != null)
{
var unitOfWork = new UnitOfWork(_context);
var userDatabaseAssignments = unitOfWork.DatabaseAssignments.GetAll().Where(d => d.User_Id == user.Id);
//var assignmentsToRemove = userDatabaseAssignments.Where(ud => databases.Any(d => d.Id != ud.Database_Id));
var assignmentsToRemove = userDatabaseAssignments.Select(ud => userDatabaseAssignments.FirstOrDefault()).Where(d1 => databases.All(d2 => d2.Id != d1.Database_Id));
var assignmentsToAdd = databases.Select(d => new DatabaseAssignment { User_Id = user.Id, Database_Id = d.Id }).Where(ar => assignmentsToRemove.All(a => a.Database_Id != ar.Database_Id));
if (assignmentsToRemove.Any())
{
unitOfWork.DatabaseAssignments.RemoveRange(assignmentsToRemove);
}
if (assignmentsToAdd.Any())
{
unitOfWork.DatabaseAssignments.AddRange(assignmentsToAdd);
}
unitOfWork.SaveChanges();
}
I think u are looking for an Except extension, have a look at this link
LINQ: Select where object does not contain items from list
Or other way is with contains see below Fiddler link :
https://dotnetfiddle.net/lKyI2F

MVC Core Repository Query Data all at Once

We have Customer transactional table with multiple lookup tables with foreign keys. We want to see the 3 table joined together.
How do I make sure Service call is conducted one? I don't want to grab All unnecessary customer transactions, and then filter in memory. I want to make one service call to find customer transaction > 50 in one sql query.
Repository:
CustomerTransaction GetTransactionbyCustomerId(int customerid)
{
var result = ct.CustomerTransaction.Where(x => x.CustomerTransactionId == customerid).ToList()
return result;
}
Service Call:
void GetByCustomerTransactionGreaterthan50(int id)
{
var newdata = CustomerTransaction.GetByCustomerTransactionId();
nt.newdata.Where(x => x.PurchaseAmount > 50).ToList()
return newdata;
}
Models:
public class CustomerTransaction
{
public int CustomerTransactionId{ get; set; },
public int ProductTypeId {get; set; }, //joins to ProductTypeTable
public int StatusID {get; set; }, //joins to StatusTypeTable
public string DateOfPurchase{ get; set; },
public int PurchaseAmount { get; set; },
}
public class ProductType
{
public int ProductTypeId{ get; set; }
public string ProductName { get; set; },
public string ProductDescription { get; set; },
}
public class StatusType
{
public int StatusId{ get; set; }
public string StatusName{ get; set; },
public string Description{ get; set; },
}
You can make a Select projection before the ToList() this will make sure that your sql statement that EF generate include just fields you are projecting too.
Something like:
var result = ct.CustomerTransaction.Where(x => x.CustomerTransactionId == customerid).Select(x=> new CustomerTransaction()).ToList()
and to filter you can include your filter in the where condition:
ct.CustomerTransaction.Where(x => x.CustomerTransactionId == customerid && x.PurchaseAmount > 50)
Or make your GetTransactionbyCustomerId return a Query and filter as you did in the service call
Option 1
You will need to adjust your repository in order to be able to add to your query in Service Layer:
Repository:
IQueryable<CustomerTransaction> QueryTransactionbyCustomerId(int customerid)
{
var result = ct.CustomerTransaction.Where(x => x.CustomerTransactionId == customerid);
return result;
}
Option 2
Or to create another method in your data access layer:
Repository:
List<CustomerTransaction> GetTransactionByCustomerIdAndAmount(int customerid, int amount)
{
var result = ct.CustomerTransaction.Where(x => x.CustomerTransactionId == customerid && x.PurchaseAmount > amount).ToList()
return result;
}
Based on your architecture, you should pick one of these options.

Joins with Dapper return null

I have the following classes:
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public string Department { get; set; }
public List<Event> Events { get; set; }
}
public class Event
{
public int Id { get; set; }
public int UserId { get; set; }
public string EventText { get; set; }
public string StartTime { get; set; }
public string EndTime { get; set; }
public string Day { get; set; }
public string ColorIdentifier { get; set; }
public int Week { get; set; }
}
I'm trying to get all the users and their events with Dapper like this:
var sql = "SELECT u.Id, e.UserId, e.EventText FROM cpc.PLANNING_TOOL_USERS u LEFT JOIN cpc.PLANNING_TOOL_EVENTS e ON u.Id=e.UserId";
var result = SqlMapper.Query<User, Event, User>(connection, sql, (u, e) =>
{
if (u.Events == null)
u.Events = new List<Event>();
u.Events.Add(e);
return u;
}, splitOn: "Id, UserId");
The Id for the user is returned back, but the list of events is not populated. I have looked at many examples here on Stack Overflow regarding this, but I can't see what I'm doing wrong.
To omit the situation that SQL returns no data I have just mocked two user rows with SQL union.
User with Id=1 and one Event, and User with Id=2 and two Events.
SqlMapper.Query returns flat results that are best for 1 to 1 relation. You have one user to many events relation, so some helper storage needed to maintain that relation as a mapping thru the results. I have used .NET dictionary for that.
My code sample below:
// introducing temporary storage
var usersDictionary = new Dictionary<int, User>();
var sql = #"SELECT 1 Id, 1 UserId, 'EventText1' EventText
union SELECT 2 Id, 2 UserId, 'EventText2' EventText
union SELECT 2 Id, 2 UserId, 'Another EventText2' EventText";
var result = SqlMapper.Query<User, Event, User>(connection, sql, (u, e) =>
{
if (!usersDictionary.ContainsKey(u.Id))
usersDictionary.Add(u.Id, u);
var cachedUser = usersDictionary[u.Id];
if (cachedUser.Events == null)
cachedUser.Events = new List<Event>();
cachedUser.Events.Add(e);
return cachedUser;
}, splitOn: "UserId");
// we are not really interested in `result` here
// we are more interested in the `usersDictionary`
var users = usersDictionary.Values.AsList();
Assert.AreEqual(2, users.Count);
Assert.AreEqual(1, users[0].Id);
CollectionAssert.IsNotEmpty(users[0].Events);
Assert.AreEqual(1, users[0].Events.Count);
Assert.AreEqual("EventText1", users[0].Events[0].EventText);
Assert.AreEqual(2, users[1].Events.Count);
I hope that helped you solving your mapping issue and events being null.

How to delete record in Many to Many table in EF without deleting the entities it self ?

I have two entities Users and Roles
public partial class User
{
public int Id { get; set; }
public string FirstName { get; set; }
public string SecondName { get; set; }
public virtual ICollection<Role> Roles { get; set; }
}
public partial class Role
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public virtual ICollection<User> Users { get; set; }
}
And i have table(RoleUser) in Database contain only userid and role id.
I want to modify the user ,Delete the rows that exist in RoleUser Table and insert new record.
When i used this following code it delete the rows in RoleUser Table and also the roles itself
public void Update(User usr)
{
var existingParent = _context.Users
.Where(p => p.Id == usr.Id)
.Include(p => p.Roles)
.SingleOrDefault();
if (existingParent != null)
{
// Update parent
_context.Entry(existingParent).CurrentValues.SetValues(usr);
// Delete children
foreach (var existingChild in existingParent.Roles.ToList())
{
if (!usr.Roles.Any(c => c.Id == existingChild.Id))
_context.Roles.Remove(existingChild);
}
}
}
The question is how to delete the records that exist in RoleUser Table and insert new records without deleting the entities itself??
This is code for remove
_context.Users.find(userId).Roles.Remove(_context.Roles.find(roleId));
_context.SaveChange();
And this is code for add
_context.Users.find(userId).Roles.Add(_context.Roles.find(roleId));
_context.SaveChange();
It will save userid and roleid to table many-to-many
Hope help you
You need to do this to remove a record only in Many to Many table. Let me know if it works for you.
IObjectContextAdapter contextAdapter = (IObjectContextAdapter)_context;
ObjectStateManager stateManager = contextAdapter.ObjectContext.ObjectStateManager;
stateManager.ChangeRelationshipState(existingParent, existingChild, "Roles", EntityState.Deleted);
_context.SaveChanges();
To add new values:
_context.Entry(existingParent).Collection("Roles").CurrentValue = values;
Where values is your list of data to add (should be IEnumerable or ICollection, so List<Role> is ok).
values must contains object linked to database.
foreach (Role entry in newValues) {
values.Add(_context.Set(typeof(Role)).Find(entry.Id));
}

Categories