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);
}
Related
I'm looking for a work around for an EntityFramework Core Bug. I'm trying to write a query which filters on itself.
Disclaimer: I'm doing something a bit more complex than filtering by an explicit userId, I am just using this with a hard-coded value for simplicity, as the exact implementation isn't relevant to my question.
protected override void OnModelCreating(ModelBuilder modelBuilder) =>
modelBuilder.Entity<ConversationSubscription>()
.HasQueryFilter(x => x.Conversation.ConversationSubscriptions
.Select(c => c.UserId).Contains(315)
);
Since the Query Filter is attempting to access the entity in which it's filtering, it ends up in and endless loop. Since we're working with the ModelBuilder and not the DbSet, there is no way to mark it as IgnoreQueryFilters.
Given this, I tried to use the current context to filter itself:
modelBuilder.Entity<ConversationSubscription>().HasQueryFilter(x =>
this.Set<ConversationSubscription().AsNoTracking().IgnoreQueryFilters()
.Where(cs => cs.ConversationId == x.ConversationId)
.Select(c => c.UserId)
.Contains(315)
);
However, this throws an InvalidOperationException, most likely because we're attempting to use the context before OnModelCreating has finished.
I feel like there is a way I can hack around this if I can somehow select the ConversationSubsriptions into an Anonymous Type, such that they're unfiltered.
Edit: I tried to hack around this using an anonymous type, but no luck.
modelBuilder.Entity<ConversationSubscription>().HasQueryFilter(c =>
x => x.Conversation.Messages.Select(m => new {
Value = m.Conversation.ConversationSubscriptions.Distinct()
.Select(cs => cs.UserId).Contains(c.Variable(this._userId))
}).FirstOrDefault().Value
);
Query filters initially didn't support accessing navigation properties or db sets. Looks like EF Core 3.0 removed these limitations (probably because of the new Single SQL statement per LINQ query mode), with the following restrictions/bugs:
AsNoTracking() and AsTracking() - not supported, which makes sense, since the query filter is always translated to SQL.
Include / ThenInclude - allowed, but ignored by the same reason.
IgnoreQueryFilters - not supported. This could be considered as bug since it could have been be used to resolve the next cases.
Cross referencing filters (e.g. entity A filter uses entity B and entity B filter uses entity A) via either navigation properties or db sets - cause StackOverflowException because filters are trying to use each other. This is a bug.
Self referencing filter via navigation properties - same bug as #4, should be like #6.
Self referencing filter via db sets - supported(!), always ignored in filter subquery.
With all that being said, luckily your case is supported by #6, i.e. your second attempt with just unsupported AsNoTracking() and IgnoreQueryFilters() removed:
modelBuilder.Entity<ConversationSubscription>().HasQueryFilter(x =>
this.Set<ConversationSubscription()
.Where(cs => cs.ConversationId == x.ConversationId)
.Select(c => c.UserId)
.Contains(315));
Maybe I'm missing something obvious here Johnny, so if I'm way off the mark, my apologies.
But I feel like you're doing this:
From conversation subscriptions, join conversations, then join conversation subscriptions where user id = 315
While you could/should be doing this:
From conversation subscriptions where user id = 315, join conversations.
I fail to see what the round trip in your query is necessary for, since it looks like you are querying from ConversationSubscription. In such a query, a simple include will return only conversations and conversationsubscriptions that a user has access to. Is that not what you want to return?
var result = context.ConversationSubscriptions.Include(c => c.Conversation).ToList();
private int _userId = 315;
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<ConversationSubscription>()
.HasQueryFilter(x => x.UserId.Contains(_userId));
}
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.
I am trying to find out what is causing this error, I have listed some of the relevant areas of my code that should hopefully help answer my problem.
The recipe entity's members collection is as shown below:
public virtual IList<Member> Members { get; set; }
and here is the Recipes collection on the member entity:
public virtual IList<Recipe> Recipes { get; set; }
I do the below when creating my DbContext in order to make a many-to-many relationship in a separate table
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// have to specify these mappings using the EF Fluent API otherwise I end up with
// the foreign key fields being placed inside the Recipe and Member tables, which wouldn't
// give a many-to-many relationship
modelBuilder.Entity<Recipe>()
.HasMany(r => r.Members)
.WithMany(m => m.Recipes)
.Map(x => {
x.ToTable("Cookbooks"); // using a mapping table for a many-to-many relationship
x.MapLeftKey("RecipeId");
x.MapRightKey("MemberId");
});
modelBuilder.Entity<Recipe>()
.HasRequired(x => x.Author)
.WithMany()
.WillCascadeOnDelete(false);
}
I also seed my database when the model changes and all I have had to do is add to a recipe's member collection and it seems to be able to sort the rest out for me and place the relevant keys in my cookbook relationship table.
This is some of the code in my recipe controller action that performs the work:
var memberEntity = memberRepository.Find((int)memberId);
var recipeEntity = recipeRepository.Find(recipeId);
recipeEntity.Members.Add(memberEntity);
recipeRepository.InsertOrUpdate(recipeEntity);
recipeRepository.Save();
Here is the insert or update method on my Recipe repository
public void InsertOrUpdate(Recipe recipe)
{
if (recipe.Id == default(int))
{
// New entity
context.Recipes.Add(recipe);
} else
{
// Existing entity
context.Entry(recipe).State = EntityState.Modified;
}
}
I get an error of "InvalidOperationException : The relationship between the two objects cannot be defined because they are attached to different ObjectContext objects." on this line:
context.Entry(recipe).State = EntityState.Modified;
Does anyone know why that would happen? Do I have to add the member to the recipe and vice versa to get this to work? I'm not sure what the problem is because the recipeEntity seems to be the correct one.
Any help would be appreciated, thanks.
EDIT
The context is being created in each repository (RecipeRepository & MemberRepository) as shown, so I presume this is the problem in that a different context is being used for each .Find() request? and that causes problems?
private EatRateShareDbContext context = new EatRateShareDbContext();
I'm not sure this is the solution but it seems like you're using different contexts in your repository.
First make sure your have the same context for each lifetime. lifetime could be different based on your project type. (e.g. for web projects, usually it is the same for each HttpContext). You can use IoC to manage your context lifetime. Good IoC libraries for .Net are autofac and Castle Windsor
Also, I think your call to InsertOrUpdate method is unnecessary (unless you're calling Find method with no tracking.) just remove the line and see if it works:
var recipeEntity = recipeRepository.Find(recipeId);
recipeEntity.Members.Add(memberEntity);
recipeRepository.Save();
One easy way to share your DbContext per HttpRequest is mentioned here.
If you are using AutoFac, you must add SingleInstance() to your register code.
Example:
builder.Register(a => new EntityContainer()).As().SingleInstance()
Where EntityContainer is your DbContext
I have an entity that I need to return only records where a given field value is greater than zero. I have seen examples of conditional mapping in the edmx and that seems like what I am in need of. However, my project is in EF 4.1 code first.
Is there not a way to do this using the code first approach?
I dont think there is an inbuilt method for achieving this, you can however expose a property in your DbContext in which you apply filtering, initially this will be readonly but i dont see a reason why you shouldnt be able to create your own DbSet implementation reflecting back to another DbSet (ProxyDbSet)
Readonly example:
class MyDbContext : DbContext
{
public IDbSet<User> Users { get; set; }
public IQueryable<User> Admins
{
get
{
return from user in users
where user.Role == "admin"
select user;
}
}
}
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.