I have a view patient
in mysql database, which consists of patient_info
and patient_documents
tables.
View patient
:
+---------------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------------+--------------+------+-----+---------+-------+
| id | int | NO | | 0 | |
| name | varchar(30) | NO | | NULL | |
| passport_data | char(11) | NO | | NULL | |
+---------------+--------------+------+-----+---------+-------+
Table patient_info
:
+------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+--------------+------+-----+---------+----------------+
| id | int | NO | PRI | NULL | auto_increment |
| name | varchar(30) | NO | | NULL | |
+------------+--------------+------+-----+---------+----------------+
Table patient_documents
:
+---------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------------+--------------+------+-----+---------+----------------+
| documents_id | int | NO | PRI | NULL | auto_increment |
| patient_id | int | NO | UNI | NULL | |
| passport_data | char(11) | NO | UNI | NULL | |
+---------------+--------------+------+-----+---------+----------------+
I want to keep getting of entities from view patient
, but change adding of Patient
entity to database via DbContext
in this way: insert Patient
's attributes into patient_info
and patient_document
.
How can I implement it? I thought it's a good idea because it'd be possible to add extra hidden attributes to patient_info
by other types of users and these attributes won't be read by anyone who doesn't have an access to patient_info
(view patient
restricts getting attributes).
Maybe I recreated the wheel here and there's no sense in such approach?
Summary: how to override adding of entity Patient
to DbContext
as adding of it's attributes to patient_info
and patient_documents
, but keep getting of patient info through view patient
?
I want something like
public Patient patient
{
get => PatientView.Patient; // keep EFC behavior
set
{
var insertedPatient = value;
// breaking patient entity into two entity
var insertedPatientInfo = new PatientInfo
{
Name = insertedPatient.Name;
};
PatientInfo(insertedPatientInfo); // insert in one table
var insertedPatientDocuments = new PatientDocuments
{
PasportData = insertedPatient.PassportData;
}
PatientDocuments.Add(insertedPatientDocuments); // insert in second table
}
}
Mapping to views should be considered only for read-only scenarios, and complex ones at that. That example is probably a simplification, but as it stands, such a relationship does not warrant a view. Even where a view might be beneficial for flattening a complex, conditional arrangement of tables, there should also be an entity structure set up for the arrangement of tables and their relationships directly to perform updates with. (I.e. use the view to get a simpler set on Read, but then fetch the entity aggregate root or specific entities on update, not attempt to update through a view)
That example is suited to either a common one-to-many or an enforced one-to-one data partitioning strategy which EF has no issue respecting either as a traditional join or an "owned" relationship. If it is a one-to-many relationship then it doesn't make any sense at all to use a view. A patient_documents table could either be a one-to-many where you have an Id and a PatientId with a DocumentType (I.e. "passport" or "driverLicense", etc.) and a DocumentData (the value for that document type) or a one-to-one relationship which can have a PatientId but typically would share the same PK as it's corresponding patient. (patient_documents.Id = patient_info.Id) In this sense the table columns would be something like Id, passport_data, driverlicence_data, etc. The one-to-one relation can have a separate Id vs. PatientId but it is honestly not needed.
The entity declarations would be on Patient Info and Patient Documents.
[Table("patient_info")
public class PatientInfo
{
[Column("id")]
public int Id { get; protected set;}
[Column("name")]
public string Name { get; set; }
// For a one-to-one relationship
public virtual PatientDocument PatientDocument { get; set; }
// Or for a one-to-many relationship
//public ICollection<PatientDocument> PatientDocuments { get; protected set; } = new[];
}
[Table("patient_documents")]
public class PatientInfo
{
[Column("id")]
public int Id { get; protected set;}
// for one-to-many, also add a DocumentType and DocumentData rather than PassportData...
//[Column("patient_id")]
//public string PatientId { get; protected set; }
// Suitable for one-to-one relationship
[Column("passport_data")]
public string PassportData { get; set; }
// or suited to a one-to-many relationship
//[Column("document_typeId")]
//public DocumentTypeIds DocumentTypeId { get; set; } // Enum for types of document, I.e. "Passport"
//[Column("document_data")]
//public string DocumentData { get; set; }
}
Then when configuring the entities:
modelBuilder.Entity<PatientInfo)()
.HasOne(pi => pi.PatientDocument)
.WithOne();
If you want to use a separate PatientId instead of joining on the PKs then you also need to add:
.HasPrincipalKey<PatientDocument>(pd => pd.PatientId);
If it's one-to-many:
modelBuilder.Entity<PatientInfo)()
.HasMany(pi => pi.PatientDocuments)
.WithOne()
.HasForeignKey(pd => pd.PatientId);
So instead of a view in the database to get a "Patient", create a Patient ViewModel in your class:
[Serializable]
public class PatientViewModel
{
public int PatientId { get; set; }
public string Name { get; set; }
public string? PassportData { get; set; }
}
Now when you go to read your data to get that view model you leverage EF's projection to populate your view model. For a one-to-one relationship:
var patients = _context.PatientInfo
.Select(pi => new PatientViewModel
{
PatientId = pi.PatientId,
Name = pi.Name,
PassportData = pi.PatientDocument.PassportData
}).ToList();
if you have a one-to-many relationship with documents then:
var patients = _context.PatientInfo
.Select(pi => new PatientViewModel
{
PatientId = pi.PatientId,
Name = pi.Name,
PassportData = pi.PatientDocuments
.Where(pd => pd.DocumentTypeId == DocumentTypeIds.Passport)
.Select(pd => pd.DocumentData)
.FirstOrDefault()
}).ToList();
When you want to update a patient or a patient document, simply fetch the PatientInfo entity by ID, and eager load the patient document(s) if you want to update. For instance to update the passportdata on a patient in a one-to-one relationship:
var patient = _context.PatientInfos
.Include(pi => pi.PatientDocument)
.Single(pi => pi.Id == request.PatientId);
patient.Name = request.Name;
patient.PatientDocument.PassportData = request.PassportData;
_context.SaveChanges();