I'm using entity framework 2.2.
I have a very basic setup: User, UserRole, Role.
Ideally, I would like to do operations like these: User.Roles.Where(...). But I can't.
I have to pollute the application logic with data structure details, so I have to do something like this: User.UserRoles.Where(x => x.Role...).
Is there any way to accomplish this?
I've tried to define an expression for finding the users with a specific role:
public static Expression<Func<User, bool>> HasRole(string role)
{
return user => user.UserRoles.Any(x => x.Role.Name == role);
}
And call it like this:
var admins = await _context.Users
.Where(User.HasRole("Admin"))
.ToListAsync();
So that I don't need to know about the UserRole table.
I am aware that there's an open issue on github, but I was thinking what workarounds we could use for now?
I've seen another approach on the internet:
public IEnumerable<Role> Roles => this.userRoles.Select(x => x.Role);
It would've been perfect if it could be automatically translated into Expression somehow, but I need to Include UserRoles before I can use user.Roles, which basically, doesn't solve my problem.
Related
So I have this piece of code which I think can be improved but I don't know how
IList<User> users = await _unitOfWork.UserRepository.SelectAllAsync();
users = users.Where(x => x.Field == MyField).ToList();
foreach (var user in users)
{
user.Active = IsActive;
await _unitOfWork.UserRepository.UpdateAsync(user);
}
What I want to achieve is:
1) get all entries
2) filter them
3) on the filtered list, update their active status
How can I improve this code, both performance-wise and clean-wise?
Thanks.
No idea what's inside your _unitOfWork object, but the code seems to imply you are implementing the repository pattern. EF Core already implements that, so you are not achieving anything by adding another repository on top of it.
If you use a DbContext directly, you can improve your code as follows...
IList<User> users = await _ctx.Users
.Where(x => x.Field == MyField)
.ToListAsync();
foreach (var user in users)
{
user.Active = IsActive;
}
await _ctx.SaveChangesAsync()
This has two advantages over your code:
Your code pulls all users out of the repository and then filters in memory. Depending on the number of users, this can be inefficient. My code only pulls out the users that you want.
Your code calls the database for every user. As my code is just updating the local context, it only needs to call the database once, when all the updates are done.
Another option to consider is using EF Core 7.0 (EF7). EF7 targets .NET 6, so you can use it with either .NET 6 or .NET 7.
With EF7, you now have the ability to use bulk updates. This allows you to update your selected entities with a single command.
So your code could be written as follows:
await _ctx.Users
.Where(u => u.Field == MyField)
.ExecuteUpdateAsync(s => s.SetProperty(s => s.Active, s => IsActive));
The advantage of this approach is that your entities are not loaded into memory, so you can avoid having to use change tracking for updates. Depending on how many users you need to update, this can be much more efficient.
That being said, carefully consider using this method if you're relying upon change tracking in the rest of your app. None of your tracked entities will be kept in sync by default. The performance advantages of this approach are minimal if you're only updating a few users at a time.
Your code is very memory and computationally inefficient. There are two reasons why.
Your code seems to use a repository pattern. My answer is based on that assumption.
First, the most problematic part is that you get all entries and then you select the records that match the where condition. With this pattern, you scan all records two times, but there is no need for that. Try to add a where clause in your parameters in your repository like below:
public async Task<List<T>> SelectAllAsync(Expression<Func<T, bool>>? filter = null)
{
IQueryable<T> query = dbSet;
if (filter != null)
{
// now you can put your where clause here
query = query.Where(filter);
}
return await query.ToListAsync();
}
And when calling it you can simply do the following:
IList<User> users = await _unitOfWork.UserRepository.SelectAllAsync(filter: x => x.Field == MyField);
Using this method, you only scan your database once and you don't need to filter the result again.
Second, if you use this User table a lot to get all the Users based on the IsActive Column, add an index for it since it is inefficient to call this code more than necessary.
I have a project that is using Entity framework and AspNet Identity.
One of the parameters of the IdentityUser is a deactivated field.
Within the app, if I wanted to get the users, I would do this:
var users = Context.Users //etc....
However, I do not want this query to return any deactivated users. I know I could do this
var users = Context.Users.Where(x => x.Deactivated != true);
However, I am reluctant to do this, as I am sure that in time someone will forget to add this where clause.
Is there a way for entity to do this automatically to all context queries? I have found this:
https://learn.microsoft.com/en-us/ef/core/querying/filters
But I do not have EF core and cannot upgrade to it.
I know I could make a wrapper function and call this, but I am trying to find a better solution...
I often use a combination of XXXStore members and XXX members where the XXXStore is the DBSet and the XXX is a query with AsNoTracking(). I then use the XXX members in queries responding to GETs and the XXXStore only when I want to update the database. E.g.
public DbSet<User> UserStore { get; set; }
public IQueryable<User> Users => UserStore.AsNoTracking();
This gives the advantage of not tracking entities that won't be updated, but with a DRY approach that means not adding the AsNoTracking() verbosely all over the place.
This approach can easily be combined with a where clause:
public DbSet<User> UserStore { get; set; }
public IQueryable<User> Users => UserStore.AsNoTracking().Where(u => !u.Deactivated);
Filters can also work, but an advantage to this approach is that it's easy to make an exception when you really do need to access a deactivated user, too.
You can use Global Filters:
protected override void OnModelCreating(ModelBuilder modelBuilder){
modelBuilder.Entity<Blog>().Property<string>("TenantId").HasField("_tenantId");
// Configure entity filters
modelBuilder.Entity<Blog>().HasQueryFilter(b => EF.Property<string>(b, "TenantId") == _tenantId);
modelBuilder.Entity<Post>().HasQueryFilter(p => !p.IsDeleted);
}
I need to dynamically filter data from the particular table depending on user's permissions. E.g. "normal" user can see only records assigned to him but admin can see all. I'm using ninject to create DB context per request and create context by passing additional user info to a constructor. Then I apply dynamic filtering (EF6) from EntityFramework-Plus extensions:
public MyDbContext(bool isAdmin, string userId) : this()
{
if (!isAdmin)
{
this.Filter<MyTable>(table => table.Where(...));
}
}
This solution works as expected i.e. calling methods like:
ctx.MyTable.Where(...)
Results in extra join declared in filter.
But behaves oddly when I'm using method Find(). I'm using SqlServer profiler to see what happens under the hood:
I Create context as restricted (non-admin user) - calling Find() will result in extra WHERE statement corresponding to filter's lambda expression
Then I create the context as admin (separate request) - calling Find() will result in the same SQL expression (i expect no extra SQL clauses).
AFAIK this has something to do with query caching since adding extra line to constructor seems to solve the problem:
public MyDbContext(bool isAdmin, string userId) : this()
{
// this "solves" the problem
QueryFilterManager.ClearQueryCache(this);
if (!isAdmin)
{
this.Filter<MyTable>(table => table.Where(...));
}
}
That looks like a big overkill and it doesn't bring me any closer to understanding the problem. So here are my questions:
Why this problem does not affect Where() but affects Find()?
Is there any cleaner way to solve this issue? I've read about Dynamic Filters library but it's no good for me as it works only in code first model (DB first here).
Are there better concepts of filtering data basing on per-request data (like userId in my example)?
UPDATE
This is what my lambda expression looks like:
private Func<IQueryable<MyTable>, IQueryable<MyTable>> GetFilter(string userId)
{
return t => t
.Where(c.DataScopes.Any(
x => x.AspNetGroups.Any(
ang => ang.AspNetUsers.Any(
anu => anu.Id == userId))));
}
AspNetGroups is my custom table to group users. Data persmissions are assigned to users group.
I started using Entity Framework 6 and learning Domain Driven Design.
To my understanding, according to DDD principles, business logic should reside in the domain layer, in entities and value objects.
In my ApplicationUser class, I want to have a method that says whether or not the user is currently active in the chat:
public bool IsActiveInChat()
{
return this.ConnectedToChat &&
(DateTime.Now - this.LastChatActivity).TotalMinutes < 10;
}
The purpose of the method is to filter which users to display in the chat members list.
This is the query in ApplicationUserManager, which is the Repository:
public List<ApplicationUser> GetUsersConnectedToChat()
{
List<ApplicationUser> users = Users
.Where(u => u.IsActiveInChat())
.ToList();
return users;
}
When I run this code, I get the error:
LINQ to Entities does not recognize the method 'Boolean
IsActiveInChat()' method, and this method cannot be translated into a
store expression.
I can easily make this work by transferring the logic into the Repository, but from what I read about DDD, the business logic should belong in the entities and not in the repositories.
This is a working version of the same query (without the method in ApplicationUser):
public List<ApplicationUser> GetUsersConnectedToChat()
{
List<ApplicationUser> users = Users
.Where(u => u.ConnectedToChat &&
DbFunctions.DiffSeconds(DateTime.Now, u.LastChatActivity) < 10)
.ToList();
return users;
}
So, my question: is this a known issue with Entity Framework?
Am I misunderstanding something?
Is there a way to bypass this issue and keep the business logic inside the User entity?
P.S. - The question brought up by marc_s doesn't answer my question. Firstly, the answer given there suggests using AsEnumerable, which fetches the entire table into memory and is bad performance wise. It also doesn't answer my question of what is the Domain Driven Design approach to this issue, which appears to be quite common and something I would encounter a lot in my project.
This is a common problem.
If your filtering logic is stored in C# functions, then the sql server does not know anything about it.
You have three options.
Pull all the data down from SQL and perform your filtering locally
(which,as you point out, can lead to bad performance).
Move your filtering logic to SQL
Attempt to write your filtering
logic in a manner that can be passed to and understood by sql.
By 3, I mean you can re-write your IsActiveInChat function like
Expression<Func<ApplicationUser, bool>> IsActiveInChat = r =>
(r.ConnectedToChat.Value && (DateTime.Now - r.LastChatActivity).TotalMinutes < 10);
or possibly
Expression<Func<ApplicationUser, bool>> IsActiveInChat = r =>
(r.ConnectedToChat &&
DbFunctions.DiffSeconds(DateTime.Now, r.LastChatActivity) < 10)
Then you can use this function as
public List<ApplicationUser> GetUsersConnectedToChat()
{
List<ApplicationUser> users = Users
.Where(IsActiveInChat)
.ToList();
return users;
}
The problem with this method is that you are limited to functions that SQL can handle. For example, you will get a NotSupportedException if you try
Expression<Func<ApplicationUser, bool>> isJan = r =>
(r.LastChatActivity.ToSting("MMM") == "JAN";
I have a simple databasescheme: User, Account. User has 1-to-many relationship with Account.
I have generated a ado.net entity data model, and I can create users and accounts, and even link them together. In the database the account.user_id is correctly filled, so theoretically I should be able to acces User.Account.ToList() in C# through entity.
However, When I try to acces User.Account.ToList() I get zero results.
User user = db.User.First(U => U.id == 1);
List<Account> accounts = user.Account.ToList(); ##count = 0...
When I add the following code before the previous code it suddenly gives me the correct count 2.
Account account1 = db.Account.First(A => A.id == 1);
Account account2 = db.Account.First(A => A.id == 2);
User user = db.User.First(U => U.id == 1);
List<Account> accounts = user.Account.ToList(); ##count = 2...??
What am I missing here??
You should use the ObjectQuery.Include method for this. Your method works also but results in an additional query.
In your example you would get
User user = db.User.Include("Account").First(u => u.id == 1);
You have to figure out whether the string "Account" is correct. Usually it should be prefixed with something like MyEntities. This depends on the namespace of your entities but with a little trial and error you should be able to figure this out.
Yes, that's a common problem when starting to use the Entity framework - neither parent nor child relationships are lazy loaded so you have to load them explicitly. If you are going to share the object context around between classes / methods you might want to make a check to see if the relationship is already loaded:
e.g.
if(!user.Account.IsLoaded)
user.Account.Load();
You can make this easier with a simple extension method:
public static class EntityExtensions
{
public static void EnsureLoaded(this RelatedEnd relatedEnd)
{
if (!relatedEnd.IsLoaded)
relatedEnd.Load();
}
}
using this makes your load call shorter again:
user.Account.EnsureLoaded();
And as it uses a RelatedEnd, which is common to parent and child relationships in the entity framework, you can use this for parent reference relationships too - e.g.
account.UserReference.EnsureLoaded();
As rwwilden says, if you are always going to load the child objects with the parent in this case, you might want to use an Include to make the call more efficient and avoid an extra roundtrip to the database.
I guess my knowledge is a bit small of the framework. :)
You need to explicitly load the related accounts first.
user.Account.Load();
Now It does display correctly.