I have two entities in an EF Core project: Store and Employee
Employee has a key that references Store, and also has an active flag.
When I pull back Stores from the DbContext, I want to see only those Employees that have the key that references the store in question and has an active flag.
I am stuck as to how to restrict based on the active flag.
The Minimal example looks like:
public class Employee
{
public Guid Id {get; set;}
[ForeignKey("Store")]
public Guid StoreId{ get; set; }
public bool IsActive {get; set; }
}
public class Store
{
public Guid Id {get; set;
public List<Employee> Employees{get; set;}
}
How can I generate the behavior I want? At the moment, this will pull back every Employee, whether active or not.
You can setup a Global Query Filter.
Simply add the following to your OnModelCreating override:
modelBuilder.Entity<Employee>()
.HasQueryFilter(e => e.IsActive);
Unfortunately EF Core does not support query level filters. All you can do is to Disable Filters. So if you want this to be per specific query or want to query for Active == false, I'm afraid you have to use projection as suggested in another answer.
Something like this?
using( MyDBContext db = new MyDBContext()){
var activeEmployees = (from em in db.Employees
join s in db.Store on em.StoreId == s.Id
where em.IsActive == true
select em).ToList();
}
Related
I have the following object, called Filter with the following properties:
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Type> Types{ get; set; }
public virtual ICollection<Step> Steps { get; set; }
public virtual ICollection<Flow> Flows { get; set; }
public virtual ICollection<Room> Rooms { get; set; }
When I select a list of Filters from the database, I have no idea how to include the collections (Types, Steps, Flows, Rooms). My code is as follows:
var filters = (
from filter in dbContext.DbSet<Filter>()
let rooms = (
from r in dbContext.DbSet<Room>()
select r
)
let eventTypes = (
from t in dbContext.DbSet<Type>()
select t
)
let processFlows = (
from f in dbContext.DbSet<Flow>()
select f
)
let processFlowSteps = (
from s in dbContext.DbSet<Step>()
select s
)
select filter
).ToList();
My collection of Filter is returned, but the collections inside are empty. Could you please tell me how can I achieve this?
Ps: I do not want to use Include because of performance issues, I don't like how Entity Framework generates the query and I would like to do it this way.
Your method works, you are just doing it slighly wrong.
To include a navigation property, all you have to do is a subselect (using linq), example:
var filters = (from filter in dbContext.DbSet<Filter>()
select new Filter
{
filter.Id,
filter.Name,
Rooms = (from r in dbContext.DbSet<Room>()
where r.FilterId == filter.Id
select r).ToList()
}).ToList();
Keep in mind that EF won't execute the query until you call a return method (ToList, Any, FirstOrDefault, etc). With this, instead of doing those ugly queries you want to avoid by not using Include(), it will simply fire two queries and properly assign the values in the object you want.
You need to use Include extension method:
var filters=dbContext.DbSet<Filter>()
.Include(f=>f.Types)
.Include(f=>f.Steps)
.Include(f=>f.Flows)
.Include(f=>f.Rooms)
.ToList()
Update
#MrSilent, Include extension method was made exactly for the purpose of loading related entities, I think the other option you have is executing a raw sql, but the way you are doing is not the way to go you have four roundtrips to your database and you need to use join instead in order to get the related entities, Include generates those joins for you and it's just one roundtrip.
This is, eg, another way I guess you could do it, but again, it is against the purpose of using EF, the idea of your model is also to represent the relationship between your tables, not just to represent them individually
var query= from f in context.DbSet<Filter>()
from s in f.Steps
from r in f.Rooms
from t in f.Types
from fl in f.Flows
select new {f, s, r, t, fl};
You can use lazy loading , how :
You first need to get the properties that are Include inclined to be virtual and then an empty constructor that is protected access type to do your job well.
public virtual ICollection<Type> Types{ get; set; }
public virtual ICollection<Step> Steps { get; set; }
public virtual ICollection<Flow> Flows { get; set; }
public virtual ICollection<Room> Rooms { get; set; }
And
//FOR EF !
protected Filter() { }
I think this solution will solve your problem.
I have a quick question and looking for the best way to do this, whether EF has the capability or not, am not sure? I am using EntityFramework 6.3.
I have the following parent-child scenario,
public class Application{
[Key]
public int ApplicationId {get;set;}
public string Name {get;set;}
public string Status {get;set;}
public virtual List<Document> Documents {get;set;}
}
public class Document{
[Key]
public int DocumentId {get;set;}
[Index("IX_ApplicationDocument", 1, IsUnique = true)]
public string DocumentType {get;set;}
[Index("IX_ApplicationDocument", 1, IsUnique = true)]
public string Name {get;set;}
public int ApplicationId {get;set;}
[ForeignKey("ApplicationId")]
public Application Application {get;set;}
}
So an application is made to a department, and stored in the database, each application has a status and when submitted, status of pending, because various validation has to occur before it is approved. When an application is rejected, the submitter has to make a new application (please note I used a minimalistic example than what it actually is), however, the applicant may submit the same documents again. The problem is, this already exist in the system and can not be duplicated. As you can see, the second time they attempt to submit it will throw a constraint exception. How can I overcome this using EF, is there a way to create a constraint based on the parent's status, or is this something that can only be done programmatically?
Dont know if its helps you in your case or not, but check this out
Assuming your entity is defined as
public class Entity
{
public int Id { get; set; }
public int Parent { get; set; }
public int Child { get; set; }
}
Following fluent API code will create index as you desire:
modelBuilder.Entity<Entity>().HasIndex(p => new {p.Parent, p.Child})
.HasFilter("isdeleted = 0")
.HasName("unq_t_parent_child");
SQL generated
CREATE INDEX [unq_t_parent_child] ON [Entity] ([Parent], [Child]) WHERE isdeleted = 0;
HasIndex defines index over properties in the table Entity
HasFilter allows you to set a filter for your index. This value is sql so you need to make sure you are writing correct sql syntax.
HasName configures the name of the index.
(If you map Entity to table t & the properties to their column names, migrations will create exactly same sql as you want.)
Also a check constraint is different from unique index. If you are looking to add check constraint then you need to use migrationBuilder.Sql in your migration file.
So unfortunately I have searched around and there is no solution for this for EF 6. The best way I can do this was following the guidance of the following article, where you manually add the Filtered Index in your migration after table creation.
Blog
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.
SQL Layer:
I have a table
Entity Framwork Layer:
I have the following rule: all Offers, which have State is null, are Outstanding offers, State is true are Accepted offers, State is false are Declined offers. Also, part of fields used only for Outstanding, part - only for Accepted etc... I use Database first approach, so, I updated EF model from DB and renamed Offer entity to OfferBase and created 3 child classes:
it works fine for add/select entities/records. Right now I want to "move" offer from outstanding to accept offer, so, I need to set Status=true (from Status is null) for appropriate record. But how to do it by Entity Framework? If I try to select outstanding offer as Accept offer I get an null reference (and clearly why)
// record with ID=1 exists, but State is null, so, EF can not find this record and offer will be null after the following string
var offer = (from i in _db.OfferBases.OfType<EFModels.OfferAccepted>() where i.ID == 1 select i).FirstOrDefault();
if I try to select as OfferBase entity I get the following error:
Unable to cast object of type
'System.Data.Entity.DynamicProxies.OfferOutstanding_9DD3E4A5D716F158C6875FA0EDF5D0E52150A406416D4D641148F9AFE2B5A16A'
to type 'VTS.EFModels.OfferAccepted'.
var offerB = (from i in _db.OfferBases where i.ID == 1 select i).FirstOrDefault();
var offer = (EFModels.OfferAccepted)offerB;
ADDED NOTES ABOUT ARCHITECTURE:
I have 3 types of Offer entity. There are: AcceptOffer, DeclineOffer and OutstandingOffer.
AcceptOffer:
UserID
ContactID
Notes
FirstContactDate
LastContactDate
[... and 5-10 the unique fields...]
DeclineOffer:
UserID
ContactID
Notes
[... and 5-10 the unique fields...]
OutstandingOffer:
UserID
ContactID
FirstContactDate
LastContactDate
[... and 5-10 the unique fields...]
How to do it correctly? Of course, I can select a record, remove from DB and add new with appropriate state value, but how to do it normally?
You can't change the type of an object once it's created. Your object model seems wrong.
Either you delete the outstanding offer and create an accepted offer from it (looks like what you are currently doing) but you may lose relations as you created a new object with a new identity (you can also copy them before removing the old object). Or you want to keep the same object and change its state.
If you want to keep the same identity then preffer composition over inheritance.
Your code could look like this :
public class Offer
{
public int Id { get; set; }
public virtual OfferState State { get; set }
}
public class OfferState
{
public int OfferId { get; set; }
public string Notes { get; set; }
}
public class AcceptedOfferState : OfferState
{
public DateTimeOffset AcceptDate { get; set; }
}
public class DeclinedOfferState : OfferState
{
public DateTimeOffset DeclinedDate { get; set; }
}
If you still want to change the type of the object and keep its identity then you may use stored procedures ; as stated by Noam Ben-Ami (PM owner for EF) : Changing the type of an entity.
Rather than trying to add these custom classes to your entity framework model, just create them as normal c# classes and then use a projection to convert from the entity framework generated class to your own class e.g.
var accepteOffers= from i in _db.Offers
where i.ID == 1 && i.Status == true
select new OfferAccepted { AcceptDate = i.AcceptDate, StartTime = i.StartTime /* map all releaveant fields here */};
I'm using Fluent Nhibernate and my relevant entities are:
public class Product
{
public virtual int ID {get; set;}
public virtual string Name {get; set;}
public virtual Supplier Supplier {get; set;}
}
public class Supplier
{
public virtual int ID {get; set;}
public virtual string Name {get; set;}
public virtual List<Product> Products {get; set;}
}
In the mapping the supplier has HasMany on the Products + Inverse + Cascade.All in order to save all the prodcuts at once.
My Product Primary Key and thus the equality members is the Id which is generated with NH sequence.
I need to create a list of products to a new supplier. Well if I add the products to the list before they get the primary key from the DB, only the first product being added to the list because the ID is 0 for all the products so the equals method return true, and the list "thinks" it already has that product.
I can save the products one by one before adding to the supplier list so the will get the Id value for the data base. But that doesn't use the cascade ability.
Any creative suggestion will be welcome.
It's clear that you are intentionally breaking equality by doing a blind compare by Id.
I usually do not override Equals in my entities because it forces loading in some cases where it's not needed.
If you really want to do override Equals, you can do something like:
public override bool Equals(object obj)
{
if (Id == 0)
return ReferenceEquals(this, obj);
return obj != null && Id == ((Entity)obj).Id;
}
I dont know if I understood the question 100%, but anyhow:
We use a similiar approach where an Event has a list of Registrations. We do however, as you mentioned, save the registrations first before saving the actual event. This causes the N+1 problem when adding many registrations at once, but thats rarely the case for our scenario. Maybe that problem would go away if we used another id-generator such as HiLo? I dont know because we havent had the time to look into it.
When we delete a registration the cascade operation works successfully, and the registration collection of the event is properly updated.