Entity Framework inheritance - c#

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 */};

Related

SqliteException: SQLite Error 1: 'foreign key mismatch - "Rental" referencing "Movie"'

I am getting SqliteException: SQLite Error 1: 'foreign key mismatch - "Rental" referencing "Movie"'.
CREATE TABLE Movie (
title VARCHAR(30) NOT NULL,
description VARCHAR(300) NOT NULL);
CREATE TABLE Rental (
user_id TEXT ,
movie_id INT ,
FOREIGN KEY (user_id) REFERENCES AspNetUsers(Email),
FOREIGN KEY (movie_id) REFERENCES Movie(rowid));
with following code.
public async Task OnGetAsync(int movie_id, string email)
{
Rental newRental = new Rental();
newRental.movie_id=movie_id;
newRental.user_id=email;
_context.Rental.Add(newRental);
_context.SaveChanges();
}
Movie table has implicit rowid automatically added by SQLlite
What am I doing wrong?
A couple things: Movie appears to be missing a PK. When using identity (auto-generated) keys EF needs to be told about them as well. It can deduce some by naming convention, but I recommend being explicit to avoid surprises. Your entities will need to be set up for the appropriate relationships:
I.e.
public class Movie
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int movie_id { get; set; }
// ...
}
public class Rental
{
[Key, Column(Order=0)]
public int movie_id { get; set; }
[Key, Column(Order=1)]
public int user_id { get; set; }
// ...
}
Unfortunately this doesn't really tell EF that there is a relationship between Movie and Rental. (Or User and Rental) From an application point of view, without that relationship,
what guarantee is there that the Movie_id your call receives from a client exists? It's also a bit strange that this method is listed as an "OnGet" type method which implies a GET action rather than a POST or PUT action.
Typically with EF you will want to leverage navigation properties for you domain rather than just exposing FKs. Also, you are defining this as an async method without awaiting any async operations.
I would recommend avoiding composite keys unless truly necessary, so give Rental a Rental ID and just rely on many to one relationships for the Movie and User references:
public class Rental
{
[Key, DatabaseGenerate(DatabaseGeneratedOption.Identity)]
public int rental_id { get; set; }
// ....
public int movie_id { get; set; }
[ForeignKey("movie_id")]
public virtual Movie Movie { get; set; }
public int user_id { get; set; }
[ForeignKey("user_id")]
public virtual User User { get; set; }
}
public void RentMovie(int movie_id, string email)
{
var movie = _context.Movies.Single(x => x.Movie_Id == movie_id);
// Better would be to go to the Session for the current logged in user rather than trusting what is coming from the client...
var user = _context.Users.Single(x => x.Email = email);
try
{
Rental newRental = new Rental
{
Movie = movie;
User = user;
};
_context.Rental.Add(newRental);
_context.SaveChanges();
}
catch
{ // TODO: Handle what to do if a movie could not be rented.
}
}
In the above example we attempt to load the requested movie and user. If these do not exist, this would fall through to the global exception handler which should be set up to end the current login session and log the exception. (I.e. any tampering or invalid state should be captured and log the user out.) Where when attempting to record the rental, the exception handling can be set up to display a message to the user etc. rather than a hard failure.
You can go a step further and remove the FK properties from the entities and use Shadow Properties (EF Core) or .Map(x => x.MapKey()) (EF6) to set up the relationship. This avoids having two sources of truth for viewing/updating relationships between entities.
Optionally the Movie object could have an ICollection<Rental> where Rentals could have a RentedDate and ReturnedDate for instance so that movies could be inspected to see if a copy was available to rent. I.e. searching by name then determining if one or more copies is currently in-stock. A Rental record could be added to movie.Rentals rather than treating Rentals as a top-level entity.
Using navigation properties is a powerful feature of EF and can accommodate some impressive querying and data retrieval options via Linq without reading a lot of records and piecing things together client side.

Entity Framework database-first approach conditionally insert data

Using Entity Framework, how can I insert data if it does not exist, and update a field if it does?
public class Rootobject
{
public string odatacontext { get; set; }
public Value[] value { get; set; }
}
public class Value
{
public int AccountId { get; set; }
public DateTime? SubmissionDate { get; set; }
public string Status { get; set; }
}
To retrieve all the data from my API I use
root.value.Select(x => new satiaL
{
accountID = x.AccountID,
subDate = x.SubmissionDate,
x_status = x.Status
});
which of course will insert all records.
If the AccountID already exists in the database, I want to update the value of x_status, but if the AccountID does NOT yet exist in the database, then I want to insert all values.
You can not.
Upsert functionality is not part of an object/relational model - objects are there or not, and tracked by identity. Thre is no "update if it is not there" concept - at all. So, there is nothing for EfCore to implement.
This smells like abusing an ORM as a ETL loader, and this is not what you should do - ETL (mass data loading) is not what and ORM is made for. Time to write your own method to move data up into tables and possibly do upswert there. Did that years ago, comes really handy at times.
Right now all you can do is run a lot of finds for every account and basicalyl write code: create if not exists, update if exists.
Pseudocode:
var account = Find ( select ) or default from db
if account == null create
else update
savechanges
Something along this line. Beware of performance - you may want to just builk load all accounts. Beware of conflicting updates.

Model relationship based on compound key

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();
}

Updating a slightly complex entity in EntityFrameworkCore

I am trying out EntityFrameworkCore. I looked at the documentation, but couldn't find a way to easily update a complex entity that is related to another entity.
Here is a simple example. I have 2 classes - Company & Employee.
public class Company
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public Company Company { get; set; }
}
Company is a simple class, and Employee is only slightly complex, as it contains a property with reference to the Company class.
In my action method, which takes in the updated entity, I could first look up the existing entity by id, and then set each property on it before I call SaveChanges.
[HttpPut]
public IActionResult Update(int id, [FromBody]Employee updatedEmployee)
{
if (updatedEmployee == null || updatedEmployee.Id != id)
return BadRequest();
var existingEmployee = _dbContext.Employees
.FirstOrDefault(m => m.Id == id);
if (existingEmployee == null)
return NotFound();
existingEmployee.Name = updatedEmployee.Name;
if (updatedEmployee.Company == null)
existingEmployee.Company = null; //as this is not a PATCH
else
{
var existingCompany = _dbContext.Companies.FirstOrDefault(m =>
m.Id == updatedEmployee.Company.Id);
existingEmployee.Company = existingCompany;
}
_dbContext.SaveChanges();
return NoContent();
}
With this sample data, I make an HTTP PUT call on Employees/3.
{
"id": 3,
"name": "Road Runner",
"company":
{
"id": 1
}
}
And that works.
But, I hope to avoid having to set each property this way. Is there a way I could replace the existing entity with the new one, with a simple call such as this?
_dbContext.Entry(existingEmployee).Context.Update(updatedEmployee);
When I try this, it gives this error:
System.InvalidOperationException: The instance of entity type
'Employee' cannot be tracked because another instance of this type
with the same key is already being tracked. When adding new entities,
for most key types a unique tem porary key value will be created if no
key is set (i.e. if the key property is assigned the default value for
its t ype). If you are explicitly setting key values for new entities,
ensure they do not collide with existing entities or temporary values
generated for other new entities. When attaching existing entities,
ensure that only one entity instance with a given key value is
attached to the context.
I can avoid this error if I retrieve the existing entity without tracking it.
var existingEmployee = _dbContext.Employees.AsNoTracking()
.FirstOrDefault(m => m.Id == id);
And this works for simple entities, but if this entity has references to other entities, this causes an UPDATE statement for each of those referenced entities as well, which is not within the scope of the current entity update. The documentation for the Update method says that as well:
// Begins tracking the given entity, and any other reachable entities that are not already being tracked, in the Microsoft.EntityFrameworkCore.EntityState.Modified state such that they will be updated in the database when Microsoft.EntityFrameworkCore.DbContext.SaveChanges is called.
In this case, when I update the Employee entity, my Company entity changes from
{
"id": 1,
"name": "Acme Products"
}
to
{
"id": 1,
"name": null
}
How can I avoid the updates on the related entities?
UPDATE:
Based on the inputs in the comments and the accepted answer, this is what I ended up with:
Updated Employee class to include a property for CompanyId in addition to having a navigational property for Company. I don't like doing this as there are 2 ways in which the company id is contained within Employee, but this is what works best with EF.
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public int CompanyId { get; set; }
public Company Company { get; set; }
}
And now my Update simply becomes:
[HttpPut]
public IActionResult Update(int id, [FromBody]Employee updatedEmployee)
{
if (updatedEmployee == null || updatedEmployee.Id != id)
return BadRequest();
var existingEmployeeCount = _dbContext.Employees.Count(m => m.Id == id);
if (existingEmployeeCount != 1)
return NotFound();
_dbContext.Update(updatedEmployee);
_dbContext.SaveChanges();
return NoContent();
}
Based on documentation of Update
Ref:Update
Begins tracking the given entity in the Modified state such that it will be updated in the database when SaveChanges() is called.
All properties of the entity will be marked as modified. To mark only some properties as modified, use Attach(Object) to begin tracking the entity in the Unchanged state and then use the returned EntityEntry to mark the desired properties as modified.
A recursive search of the navigation properties will be performed to find reachable entities that are not already being tracked by the context. These entities will also begin to be tracked by the context. If a reachable entity has its primary key value set then it will be tracked in the Modified state. If the primary key value is not set then it will be tracked in the Added state. An entity is considered to have its primary key value set if the primary key property is set to anything other than the CLR default for the property type.
In your case, you have updatedEmployee.Company navigation property filled in. So when you call context.Update(updatedEmployee) it will recursively search through all navigations. Since the entity represented by updatedEmployee.Company has PK property set, EF will add it as modified entity. A point to notice here is Company entity has only PK property filled in not others. (i.e. Name is null). Therefore while EF determines that Company with id=1 has been modified to have Name=null and issues appropriate update statement.
When you are updating navigation by yourself, then you are actually finding the company from server (with all properties populated) and attaching that to existingEmployee.Company Therefore it works since there are no changes in Company, only changes in existingEmployee.
In summary, if you want to use Update while having a navigation property filled in then you need to make sure that entity represented by navigation has all data and not just PK property value.
Now if you have only Company.Id available to you and cannot get other properties filled in updatedEmployee then for relationship fixup you should use foreign key property (which needs PK(or AK) values only) instead of navigation (which requires a full entity).
As said in question comments:
You should add CompanyId property to Employee class. Employee is still non-poco (complex) entity due to navigation present.
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public int? CompanyId {get; set; }
public Company Company { get; set; }
}
During update action pass updatedEmployee in following structure. (See this is the same amount of data, just structured bit differently.)
{
"Id": 3,
"Name": "Road Runner",
"CompanyId": 1,
"Company": null //optional
}
Then in your action, you can just call context.Update(updatedEmployee) and it will save employee but not modify the company.
Due to Employee being complex class, you can still use the navigation. If you have loaded employee with eager loading (Include) then employee.Company will have relevant Company entity value.
Notes:
_dbContext.Entry(<any entity>).Context gives you _dbContext only so you can write just _dbContext.Update(updatedEmployee) directly.
As you figured out with AsNoTracking, if you load the entity in the context, then you cannot call Update with updatedEmployee. At that point you need to modify each property manually because you need to apply changes to the entity being tracked by EF. Update function gives EF telling, this is modified entity, start tracking it and do necessary things at SaveChanges. So AsNoTracking is right to use in this case. Further, if the purpose of retrieving entity from server for you is to check existence of employee only, then you can query _dbContext.Employees.Count(m => m.Id == id); and compare return value to 1. This fetches lesser data from the server and avoids materializing the entity.
There is no harm in putting property CompanyId if you don't add it to CLR class then EF creates one for you in background as shadow property. There will be database column to store value of FK property. Either you define property for it or EF will.

Entity framework - selecting from child lists without getting top level object

In my model, a "User" has "Projects".
public class User
{
public int Id { get; set; }
public virtual List<Project> Projects { get; set; }
}
(amongst other things).
IN code, I'm wanting to get a list of projects:
var p = _context.User(x => x.Id == someUserId).Projects;
This is pulling the user object as well, but I don't want it to (its wasted effort).
I could change my project model to not have an object relationship with User but that breaks other things because the relationship is expected for other things.
What's the best way to achieve this?
On the Project class, can I have both UserId and a User object reference?
What about start your query by Projects instead of Users? I think you might have a Fk property in Project entity referring to the related User:
var projects=_context.Projects.Where(p=>p.UserId==someUserId);
So, your Project entity should have these two properties
public class Project
{
//other properties
//FYI, EF by a name convention will use this property as FK,
//but is a good practice specify explicitly which is the FK in a relationship
[ForeignKey("User")]
public int UserId{get;set;}
public virtual User User{get;set;}
}

Categories