I have a few tables which all refer to each other with many-to-many relationships but not the normal kind.
Usually a many-to-many relationship has a join table which joins the two other tables on their primary keys.
In my case I have a few tables which relate to each other by sharing matching foreign keys.
I have patient records with two tables.
Doctors who are assigned to a patient.
The patients test results.
I am not allowed to store anything about the patient besides their patient id (and I have no reason to) so there is no point in a patient table.
How could I relate the Doctors to the TestResults?
They both have a foreign key to a table that does not exist. i.e. they both have patient record numbers but there is no table of patient record numbers (the record numbers are generated by a system that I do not have access to).
So in fact they are in a many-to-many relationship with each other.
I did think of making a table just to hold the record ids. That table would have one column which is the primary key and nothing else.
That solution would not work for me at all.
My storage agnostic (poco) library which would be managing and analysing these records would have no way to check if a patient was in our system when adding a new test result.
Even if I did pass a Database Context to the managing library. That would mean that the system would have to make a database call every time it wanted to add a test record just to see if the patient had any previous records with us or if this was the first one. All to add a record in a table that had no purpose. During peak processing times this could be thousands of times per minute. Something that would be trivial to do if your just accessing clr objects, but totally overwhelming if you need to make a database call for each one.
Thank you!
As trivial and probably prohibitive as it is, to enforce the relationship you describe at the physical level, there has to be a patient table. Then the relationship is modeled simply as below:
public class Doctor
{
[Key]
public int DoctorId {get; set;}
public virtual ICollection<Patient> Patients {get; set;}
}
public class Patient
{
[Key]
public int PatientId {get; set;}
public virtual ICollection<Doctor> Doctors {get; set;}
public virtual ICollection<TestResult> Results {get; set;}
}
public class PatientMap : EntityTypeConfiguration<Patient>
{
public PatientMap()
{
HasMany(p => p.Doctors)
.WithMany(d => d.Patients)
.Map(x => {
x.ToTable("DoctorPatient");
x.WithLeftKey("PatientId");
x.WithRightKey("DoctorId");
});
}
}
public class TestResult
{
[Key]
public int ResultId {get; set;}
public int PatientId {get; set;}
[ForeignKey("PatientId")]
public virtual Patient Patient {get; set;}
}
And the SQL just for clarity:
create table Doctor(
DoctorId int not null primary key,
Name nvarchar(50) not null
)
create table Patient(
PatientId int not null primary key,
)
create table DoctorPatient(
DoctorId int not null,
PatientId int not null,
primary key (DoctorId, PatientId),
foreign key (DoctorId) references Doctor(DoctorId),
foreign key (PatientId) references Patient(PatientId)
)
create table TestResult(
ResultId int not null primary key,
PatientId int not null,
foreign key (PatientId) references Patient(PatientId)
)
Related
I need some help writing and understanding EF Lambda expressions.
If I received a productID as a parameter (selectedID), how would I go about accessing the customer name, invoiceID, and PriceTotal related to that productID?
This is what I tried for getting the invoiceID:
db.InvoiceLines.Where(z => z.ProductID == selectedID).Select(x => x.InvoiceID).ToList();
But I can't figure out how to access the rest of the data.
Any help and explanations would be greatly appreciated!
So every Customer has zero or more Invoices, every Invoice is the invoice of exactly one Customer, namely the Customer that the foreign key CustomerId refers to: a straightforward one-to-many relation.
Similarly there is a one-to-many relationship between Invoices and InvoiceLines: Every Invoice has zero or more InvoiceLines, every InvoiceLine belongs to exactly one Invoice, using a foreign key.
Products - InvoiceLines: also a one-to-many relation using a foreign key.
If you've followed the entity-framework coding conventions, you will have classes similar to the following:
class Customer
{
public int Id {get; set;}
public string Name {get; set;}
...
// Every Customer has zero or more Invoices (one-to-many)
public virtual ICollection<Invoice> Invoices {get; set;}
}
class Invoice
{
public int Id {get; set;}
...
// Every invoice belongs to exactly one Customer, using foreign key:
public int CustomerId {get; set;}
public virtual Customer Customer {get; set;}
// Every Invoice has zero or more InvoiceLiness (one-to-many)
public virtual ICollection<InvoiceLine> InvoiceLiness {get; set;}
}
In entity framework columns of tables are represented by non-virtual properties. The virtual properties represent the relations between the tables.
The foreign key CustomerId is a real column in the Invoices table. Hence it is non-virtual. That an Invoice has a relation to a Customer can be seen on the virtual property Customer.
In entity framework the type of relation between tables (one-to-one, one-to-many, many-to-many) can be seen by the virtual ICollection<...> or virtual ...
Tables and collections are identified using plural nouns; a single row of the table, or one item in the collection is identified by a singular noun. Later when we discuss lambda, you will see that this makes interpreting the lambda easier.
class InvoiceLIne
{
public int Id {get; set;}
...
// Every InvoiceLine belongs to exactly one Invoice, using foreign key:
public int InvoiceId{get; set;}
public virtual Invoice Invoice {get; set;}
// Every InvoiceLine has exacaly one Product, using foreign key:
public int ProductId {get; set;}
public virtual Product Product {get; set;}
}
class Product
{
public int Id {get; set;}
public string Name {get; set;}
...
// Every Product is use in has zero or more InvoiceLines (one-to-many)
public virtual ICollection<InvoiceLine> InvoiceLInes {get; set;}
}
And finally the DbContext
class OrderingDbContext : DbContext
{
public DbSet<Customer> Customers {get; set;}
public DbSet<Invoice> Invoices {get; set;}
public DbSet<InvoiceLIne> InvoiceLines {get; set;}
public DbSet<Product> Products {get; set;}
}
In fact, this is all that entity framework needs to know to detect the tables, the columns in the tables and the relations between the tables. No need for attributes or fluent api. Only if you want identifiers that deviate from the default identifiers, for table names, or columnames, or if you want to define something special in the relations, you need fluent API.
Back to your question
If I received a productID as a parameter (selectedID), how would I go about accessing the customer name, invoiceID, and PriceTotal related to that productID?
First of all, if you have a productId, there are probably several Customers who bought the product with this productId, and thus several invoices, and several priceTotals. So you can't say THE customer name, THE invoice id.
Requirement
If I have a productId, give me the names of all Customers who bought this Product, together with the InvoiceIds where is stated that they bought this product and the total price of the invoice where they bought this product.
Note that a Customer might have bought the product several times in different orders, so they can have several Invoices.
If you've followed the conventions as I wrote above, the solution is fairly simple:
int productId = ...
var customersWhoBoughThisProduct = dbContext.Customers
// I only want Customers who have at least one Invoice that has at least one
// invoiceLine for this product
.Where(customer => customer.Invoices
.SelectMany(invoice => invoiceLines, invoice => invoice.ProductId)
.Contains(productId))
// from the customers who bough this product, select several properties:
.Select(customer => new
{
// Select only the Customer properties that you plan to use
Id = customer.Id,
Name = customer.Name,
...
// You want only the invoices that have this product as one of the InvoiceLines
Invoices = customer.Invoices
.Where(invoice => invoice.InvoiceLines
.Any(invoiceLine => invoiceLine.ProductId == productId)
.Select(invoice => new
{
// select the invoice properties that you plan to use, for example:
Id = invoice.Id,
OrderDate = invoice.OrderDate,
PriceTotal = invoice.InvoiceLines
.Select(invoiceLine => invoiceLine.Price)
.Sum(),
})
.ToList(),
});
Well you see several Lambda expressions here. Because I'm quite strict in my use of identifiers, they shouldn't be too difficult:
dbContext.Customers.Where(...)
.Select(customer => new
{
// Select only the Customer properties that you plan to use
Id = customer.Id,
Name = customer.Name,
...
After the Where, I've got a sequence of Customers, and from every customer in this sequence, I make exactly one new object, with property Id, that has the value of customerId, and property Name, which has the value of customer.Name.
This customer object, also has a property Invoices:
Invoices = customer.Invoices
.Where(...)
.Select(invoice => new{...}
You don't want all invoices of this customer, you only want the invoice, where at least one invoice line has the foreign key to the product with ProductId:
.Where(invoice => invoice.InvoiceLines.Any(invoiceLine => invoiceLine.ProductId == productId)
This says: keep only those invoices, that have at least one InvoiceLIne where property ProductId equals productId. Which is the kind of invoices we want.
PriceTotal = invoice.InvoiceLines
.Select(invoiceLine => invoiceLine.Price)
.Sum(),
To calculate the PriceTotal of the invoice that had this product, we take the Price of every InvoiceLine in this invoice, and we sum all these prices.
The most difficult one: the Where of the customers:
.Where(customer => customer.Invoices
.SelectMany(invoice => invoiceLines)
.Any(invoiceLine => invoiceLine.ProductId == productId))
So the input was a sequence of Customers. We keep only those Customers, that have at least an invoiceLine in the sequence of Invoices of this customer that with a productId equal to productId.
Whenever you have a sequence where every element in the sequence has a subsequence, and you want to check all these subsequences as one sequence, use SelectMany.
So if you have Schools, and every School has Students, and you want to see all Students of all Schools in one sequence, user: schools.SelectMany(school => school.Students) I did the same with Invoices and InvoiceLines, to get all InvoiceLines of all Invoices of this Customer: invoices.SelectMany(invoice => invoice.InvoiceLines)
By the way, did you see that because I am very strict in singular and plural nouns, that the identifiers in the lambda expressions are quite easy to understand what they represent?
Hope this helped a bit in understanding lambda expressions
I have table that has these columns:
Id
Title
Description
CreateDateTime
CategoryId
Picture
quantity
Price
RentPrice
WantToExchange
NumberOfRoom
DepositPrice
Only the first 6 are required for each row and other column will be null based on entity category.
For example in cat1 only first 6 field and Price filled by user,and in cat2 only first 6 field and RentPrice, DepositPrice are filled, so after a while many column of table rows become a null
I see some solution in NopCommerce source code that used for store various product property in different language - there are entity called LocalizedProperty and storeEntityId, LanguageId, Name of entity, name of field and its value
it use
Expression<Func<T, TPropType>>
and PropertyInfo to get name of field, but I am searching for an easier way.
How can I redesign my entity?
If you'd have designed this object orientedly, you would probably have create a PictureDescription class, and a PicturePrice class, that would have the other properties.
Every PictureDescription would have zero or one PicturePrice object: a straightforward aggregation
If you want zero-or-one in a relational Database, then this is quite often done using two tables, with a foreign key from one table pointing to the other table.
See Entity Framework zero-or-one-to-one
class PictureDescription
{
public int Id {get; set;}
public string Title {get; set;}
...
// every PictureDescription has zero or one PicturePrice
public virtual PicturePrice PicturePrice {get; set;}
}
class PicturePrice
{
public int Id {get; set;}
public decimal Price {get; set;}
...
// every PicturePrice belongs to exactly one Picture, using foreign key
public int PictureDescriptionId {get; set;}
public virtual PictureDescription PictureDescription {get; set;}
}
This will be enough for entity framework to detect your columns and the relations between the tables. If you want to use fluent API, in DbContext.OnModelCreating:
// every PictureDescription has zero or one PicturePrice:
modelBuilder.Entity<PictureDescription>()
.HasOptional(description => description.PicturePrice)
.WithRequired(price => price.PictureDescription)
.HasForeignKey(price => price.PictureDescriptionId);
In EntityFramework code first models, there exists a 1:1 relationship:
public class Child1
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Name { get; set; }
public Child2 Child2 { get; set; }
}
public class Child2
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[ForeignKey("Child1")]
public int Id { get; set; }
public string Name { get; set; }
public Child1 Child1 { get; set; }
}
When I tried to insert some data to the database, it thrown an exception:
{"A dependent property in a ReferentialConstraint is mapped to a
store-generated column. Column: 'Id'."}
It seems I cannot use auto generated Id for Child2, how can I keep this feature and make the relationship established successfully meanwhile?
Here there are two problems, the obvious one, shown in the exception. When you define a one-to-one relationship, the FK must be also the PK. In this case the PK and FK of both entities is the Id field. The problem shown in the exception is that the FK is database generated. So, if you insert a Child1 with a related Child2, EF has no way to set the FK value of the related Child2 because it's database generated.
The second problem, that has still not arisen, is that a one-to-one relationship is only a theoric thing in a database like SQL Server. If you want to insert Child1 that depends on Child2, you need to insert first Child1, and then the related Child2. That's right, but, ooops, you also have to insert Child2 before inserting Child1, because Child1 depends also on Child2. So, having a pure one to one relationship is not possible.
To solve this problem you need to do two things:
make the relationship a 1-to-(0 or 1). I.e. you must have a principal entity and a dependent entity which can or cannot exist. This will allow you to insert the principal entity, without the dependent entity, because with this configuration you can isnert the principal without the dependent.
the principal PK can be left as database generated, but you have to change the PK on the dependent entity not to be db generated. So, when you insert the dependent entity the PK, which is also the FK, can be freely specified by EF.
Finally, if you think of it, a 1-to-1 relationship usually makes no sense. You can use a single table that holds all the columns in both tables, because whenver a row exists in table A, it must exists in table B and viceversa. So having a single table has the same effect.
However, if you still want to use the 1-to-1 relationship, EF allows you to model it like this:
modelBuilder.Entity<Child1>()
.HasRequired(c1 => c1.Child2)
.WithRequiredPrincipal(c2 => c2.Child1);
Note that, in this case, the EF abstraction takes care to allow you to have a 1-to-1 relationship, even if it cannot exists in the DB. However, it's necessary to specify this relationship using the ModelBuilder because you need to specify a principal and a dependent side. In this case the principal is Child1 and the dependent is Child2. Note that you still have to be careful with the rule for database generated values.
NOTE that this is modelled in the DB with a single FK from Child2 to Child1, and not FK from Child1 to Child2. So, in the DB is a (1)-to(0 or 1) relationship, as explained above
public class Child1
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)] // Leave as is
public int Id { get; set; }
...
public class Child2
{
//[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[DatabaseGenerated(DatabaseGeneratedOption.None)] // Not db-generated
//[ForeignKey("Child1")] -- specified in the model builder
public int Id { get; set; }
First of all, I'm new to Entity Framework and I'm trying to do a project using the Code-First model, so please forgive my ignorance on what may turn out to be a trivial problem...
I'm working on creating some POCO EF classes and I'm having difficulty figuring out how to setup some of the relationships in the DbContext derived class.
If I were to setup the tables with SQL, this is what they would look like (extraneous columns removed for clarity and brevity:
CREATE TABLE DBO.Application (
ApplicationId NUMERIC(18,0) IDENTITY(1,1) NOT NULL,
MinimumVersionId NUMERIC(18,0),
CurrentVersionId NUMERIC(18,0));
CREATE TABLE DBO.ApplicationVersion (
ApplicationVersionId NUMERIC(18,0) IDENTITY(1,1) NOT NULL,
ApplicationId NUMERIC(18,0) NOT NULL;
ALTER TABLE DBO.Application ADD
PRIMARY KEY (ApplicationId),
CONSTRAINT Application_FK1
FOREIGN KEY (MinimumVersionId)
REFERENCES DBO.ApplicationVersion (ApplicationVersionId),
CONSTRAINT Application_FK2
FOREIGN KEY (CurrentVersionId)
REFERENCES DBO.ApplicationVersion (ApplicationVersionId);
ALTER TABLE DBO.ApplicationVersion ADD
PRIMARY KEY (ApplicationVersionId),
CONSTRAINT ApplicationVersion_FK1
FOREIGN KEY (ApplicationId)
REFERENCES DBO.Application (ApplicationId);
The relevant part of the ApplicationModel POCO class is (Application DB Table shown above):
public class ApplicationModel
{
public long ApplicationId { get; set; }
public virtual ApplicationVersionModel CurrentVersion { get; set; }
public long? CurrentVersionId { get; set; }
public virtual ApplicationVersionModel MinimumVersion { get; set; }
public long? MinimumVersionId { get; set; }
public virtual IList<ApplicationVersionModel> Versions { get; set; }
}
And the ApplicationVersionM POCO class (ApplicationVersion DB Table shown above):
public class ApplicationVersionModel
{
public virtual ApplicationModel Application { get; set; }
public long ApplicationId { get; set; }
public long ApplicationVersionId { get; set; }
}
So far, in the OnModelCreating method of the class that inherits from DbContext, I have this:
modelBuilder.Entity<ApplicationModel>()
.HasMany<ApplicationVersionModel>(a => a.Versions)
.WithRequired(av => av.Application)
.HasForeignKey(a => a.ApplicationId);
This is to establish the one to many relationship between Application and ApplicationVersion.
Where I'm getting confused is how to write the entries for the CurrentVersion and MinimumVersion fields. Each of these are to hold a value that would be found in ApplicationVersion.ApplicationVersionId (the primary key). However, these fields are nullable in the database and, therefore, optional.
I'm getting lost in all the options like:
WithMany - I know this one isn't it as I'm pointing to a single record
WithOptionalDependant
WithOptionalPrincipal
WithRequired - I don't think this is it since the field is nullable
And then, I'm not exactly sure what methods would be chained after that.
Any help would be appreciated. It would also be beneficial if, in your answers, you could explain WHY I need to do it that way. Knowing why will help me (and possibly others that may read the question) understand the processes and relationships better.
I have two classes:
public class Account
{
public int Id {get;set;}
public PayoffData PayoffData {get;set;}
}
public class PayoffData
{
public int Id {get;set;}
//some other fields
}
Now, i want class PayoffData to have the same id that class Account has, and it needs to be one-to-one relationship (account can (or can not) have one payoffdata).
I've tried to do something with modelbuilder, but as far i can see that to set up a foreignkey in PayoffData class, i need to set a .HasMany relationship, which i don't want. How can i solve my problem using modelbuilder? (I don't want to use data annotations approach)
modelBuilder.Entity<Account>()
.HasOptional(a => a.PayoffData)
.WithRequired();
Both tables will have a primary key column called Id and the primary key Id in the PayoffData table will be the foreign key to the Account table at the same time.