Table with with different fields depending on category - entity-framework

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

Related

EF Lambda Expression How to Access Data from Multiple Tables

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

How to map table name to EF Entity?

I have been using EF Code First for 1 month. My project needs to work with many tables (over 100 tables of data) and they will be changed many times in the future. It hard to control and take time to add the migration.
So is there any solution to skip the add-migration step, just create the table in the database and create a mapping to the Entity?
Example:
In database I have a table like this:
CREATE TABLE dbo.MyTable
(
Id INT,
Name VARCHAR(255),
Description VARCHAR(1000)
);
I want to map it to the following entity:
public class MyEntity
{
public Id int {get; set;}
public Name string {get; set;}
public Description string {get; set;}
}
My expected solution is something like this:
CreateMap("dbo.MyTable", MyEntity)
Use the attribute System.ComponentModel.DataAnnotations.Schema.Table
[Table("MyTable")]
public class MyEntity
{
public Id int {get; set;}
public Name string {get; set;}
public Description string {get; set;}
}
If you don't actually want to run the migrations, I suggest create them anyway and comment out the relevant code before you run them. That way you will be well set up for the occassion when you do actually want to run them.
You can define the entity maps by overriding the method called OnModelCreating in your DbContext:
Then you can add some maps similar to:
modelBuilder.Entity<Order>(m => {
m.ToTable("MyTable", "dbo");
})
Info: https://learn.microsoft.com/en-us/ef/core/modeling/relational/tables
EDIT: I don't like adding annotation directly to my entity as I prefer to centralize it in one place. Easier to manage. After all I guess it's a question of preferences :)

Add Navigation Property when column already exists

How can I add a navigation property to my context, when the corresponding column has already been created in the database from a previous migration?
For example: A lady has many cats. I want to add a LadyId property to the Cats class, but a LadyId column already exists in the Cats table. This LadyId column was created in a previous migration to accommodate the Cats.Lady navigation property.
DB:
Lady Table
--------------
Id Name
Cats Table
--------------
Id LadyId FurColor
Classes:
public class Lady {
public Id {get; set;}
public Name {get; set;}
public List<Cat> Cats {get; set;}
}
public class Cat {
public Id {get; set;}
public Lady Lady {get; set;}
//I want to add a LadyId property here
}
So far, I've tried...
Adding the property: Error: "The model backing the 'Context' context has changed..."
Adding a migration to update the DB: This creates a migration, but it just renames the column to LadyId1, which is pointless. The DB should remain the same, I just want to add a Navigation Property.
Commenting out code in migration Up and Down methods to try and "trick" the migration table into having the correct hash to match my context. This throws an error "Invalid column name 'EmployeePerformanceReview_id1"

Recurrency problem Entity Framework

I'm working with EF 4.1 CTP5 and SQL Server 2008. I need to understand how to solve a recurrency problem. I have the following 2 classes:
public class Nation
{
public int ID {get; set;}
public string name {get;set;}
public List<NationAlly> NationAllies {get;set;}
}
public class NationAlly
{
public int ID {get; set;}
public int level {get;set;}
public Nation Owner {get; set;}
public Nation toNation {get;set;}
}
The entities are mapped to the database with two tables (Nations and NationAllies). Besides, there are two relationships. 1) From NationAllies.OwnerID to Nation.ID
2) From NationAllies.ToNationID to Nation.ID
When I try to retrieve an object of Nation type from my database, I access the DbContext class NationDB:
Nation selectedNation = ((nationDB.Nations.Include("NationAllies")).Where(m => m.name == "France")).FirstOrDefault();
The problem is that I get a selectedNation object which has a list of NationAllies but every NationAlly in the list has the field toNation = null.
First of all I would like the field toNation to retrieve the correct information from the database. How do I do this?
Then of course toNation will be connected with other NationAllies which on their turn will have an other Nation. How could possibly a recursive map be built? My idea is to navigate the map until a certain level, by querying the database in a specific way. Doing so, what would be the best approach to have good speed performance?
It looks like NationAllies is junction table with additional properties. The problem is that EF doesn't eager load nested navigation properties if you do not specify them explicitly in Include method. If you want to have toNation filled you must use
nationDB.Nations.Include("NationAllies.toNation")
or
nationDB.Nations.Include(n => n.NationAllies.Select(na => na.toNation))
You can also enable lazy loading. Make all your navigation properties virtual (toNation, NationOwner and NationAllies) and unless you close the context all properties will be loaded once you first access them.

Entity Framework many to many relationship without primary key

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)
)