I'm trying to figure out the best way to define a one to many relationship table as it pertains to Customers and Addresses. Each Customer can have multiple address (Mailing, Billing, Delivery, etc). The Type of address is stored in a separate table (AddressType).
Here's what I have:
public class Company
{
public int Id { get; set; }
public string Name { get; set; }
public List<Address> Addresses { get; set; }
}
public class Address
{
public int Id { get; set; }
public int AddressTypeId { get; set; }
public AddressType AddressType { get; set; }
public string Street1 { get; set; }
public string Street2 { get; set; }
public string City { get; set; }
public int StateId { get; set; }
public State State { get; set; }
public string PostalCode { get; set; }
public int CountryId { get; set; }
public Country Country { get; set; }
public bool Active { get; set; }
public int CompanyId { get; set; }
}
public class AddressType
{
public int Id { get; set; }
public string Display { get; set; }
public bool Active { get; set; }
}
Couple of questions ...
Would what I have above be considered good practice? If not, how would you define it?
Assuming that the AddressType table contains Mailing, Billing and Delivery, how would I issue a Linq query where I only want to pull the Mailing Address?
Thanks a bunch.
--- Val
I'd suggest a 'base' address class. Mailing, Billing, Delivery etc. would inherit from this base class.
public class Address
{
public string Street { get; set; }
public int HouseNumber { get; set; }
public string HouseNumberAddition { get; set; }
public string City { get; set; }
public string Country { get; set; }
}
In case of delivery, you might want to print a delivery note with the address label for the delivery driver. But is does not make sense to include a DeliveryNote in the base address class, because when would a billing address need a delivery note?
So you inherit from your base Address class to create specific address types.
For example:
public class DeliveryAddress : Address
{
public string DeliveryNote { get; set; } = "Please don't ring the bell after 10pm."
}
Assuming you use EF Code First, Entity framework creates an Address table, with a discriminator column. When you save a new address, the discriminator column defines the type of address.
var googleCompany = new Company
{
DeliveryAddress = new DeliveryAddress
{
Street = "Google Street",
HouseNumber = 1,
DeliveryNote = "Watch out for the dog."
},
CompanyAddress = new CompanyAddress()
};
var microsoftCompany = new Company
{
DeliveryAddress = new DeliveryAddress
{
Street = "Microsoft Street",
HouseNumber = 2,
DeliveryNote = "Don't bring an Apple device on the premise."
},
CompanyAddress = new CompanyAddress()
};
_context.Companies.Add(googleCompany);
_context.Companies.Add(microsoftCompany);
_context.SaveChanges();
Now to query the companies and specify the type of address you need, you just need to make a call to include and let EF Core include the address.
var companiesWithBothDeliveryAddress =
_context.Companies.Include(x => x.CompanyAddress)
.Include(x => x.DeliveryAddress).ToList();
var companiesWithOnlyDeliveryAddress =
_context.Companies.Include(x => x.DeliveryAddress).ToList();
The EF Fluent API configuration should be something like this:
protected override void OnModelCreating(ModelBuilder builder)
{
// Company to CompanyAddress, without inverse property on CompanyAddress.
builder.Entity<Company>()
.HasOne(x => x.CompanyAddress)
.WithOne()
.HasForeignKey<Company>(x => x.CompanyAddressId)
.IsRequired(false)
.OnDelete(DeleteBehavior.NoAction);
// Company to DeliveryAddress, without inverse property on DeliveryAddress.
builder.Entity<Company>()
.HasOne(x => x.DeliveryAddress)
.WithOne()
.HasForeignKey<Company>(x => x.DeliveryAddressId)
.IsRequired(false)
.OnDelete(DeleteBehavior.NoAction);
// We let all the Address types share the 'CompanyId' column,
// otherwise, EF would create a seperate CompanyId column for all of them.
builder.Entity<Address>()
.Property(x => x.CompanyId)
.HasColumnName(nameof(Address.CompanyId));
builder.Entity<CompanyAddress>()
.Property(x => x.CompanyId)
.HasColumnName(nameof(Address.CompanyId));
builder.Entity<DeliveryAddress>()
.Property(x => x.CompanyId)
.HasColumnName(nameof(Address.CompanyId));
base.OnModelCreating(builder);
}
public DbSet<Address> Addresses { get; set; }
public DbSet<DeliveryAddress> DeliveryAddresses { get; set; }
public DbSet<CompanyAddress> CompanyAddresses { get; set; }
The result would look like this:
Address Table (I left out some columns for conciseness)
Companies Table (I left out some columns for conciseness)
Related
I am struggling a bit to wrap my head around Entity Framework and It's driving me crazy. I have an target object that I'd like to populate:
public class ApiInvitationModel
{
public int Id { get; set; }
public EventModel Event { get; set; }
public UserModel InvitationSentTo { get; set; }
public UserModel AttendingUser { get; set; }
}
The schemas of the above models are:
public class EventModel {
public int Id? { get; set; }
public string Name { get; set; }
public DateTime? StartDate { get; set; }
public DateTime? EndDate { get; set }
public OrganizationModel HostingOrganization { get; set; }
public Venue Venue { get; set; }
public string Price { get; set; }
}
public class UserModel {
public int Id? { get; set; }
public string Name { get; set; }
public string PhoneNumber { get; set; }
public string MobileNumber { get; set; }
public List<OrganizationModel> Organizations { get; set; }
}
public class OrganizationModel {
public int Id? { get; set; }
public stirng Name { get; set; }
public string Address { get; set; }
public UserModel PrimaryContact { get; set; }
}
The above schemas are simplified for the purpose of the question and are the models we intend to return via API.
The problem is the origin schemas in the database is very different and I'm trying to map the database objects to these objects via Entity Framework 6.
My attempted solution was to try and nest the models via a query but that didn't work and I'm not sure where to go from here besides making numerous calls to the database.
public List<ApiInvitationModel> GetInvitations(int userId) {
using (var entities = new Entities()) {
return entities.EventInvitations
.Join(entities.Users, invitation => invitiation.userId, user => user.id, (invitation, user) => new {invitation, user})
.Join(entities.Events, model => model.invitation.eventId, ev => ev.id, (model, ev) => new {model.invitation, model.user, ev})
.Join(entities.organization, model => model.user.organizationId, organization => organization.id, (model, organization) => new ApiInvitationModel
{
Id = model.invitation.id,
Event = new EventModel {
Id = model.event.id,
Name = model.event.name,
StartDate = model.event.startDate,
EndDate = model.event.endDate,
HostingOrganization = new OrganizationModel {
Id = model.invitation.hostingId,
Name = model.event.venueName,
Address = model.event.address,
PrimaryContact = new UserModel {
Name = model.event.contactName,
PhoneNumber = model.event.contactNumber,
}
}
...
},
InvitedUser = {
}
}
).ToList();
}
}
As you can see above, there's quite a bit of nesting going on but this doesn't work in Entity Framework 6 as far as I am aware. I keep getting the following errors:
"The type 'Entities.Models.API.UserModel' appears in two structurally incompatible initializations within a single LINQ to Entities query. A type can be initialized in two places in the same query, but only if the same properties are set in both places and those properties are set in the same order.",
Based on the above error, I assumed that each of the model initiatilizations would need to be the same (i.e. initializing the values as the same ApiInvitationModel in each join in the same order) but that produces the same error.
What would be the best approach to handling this, keepign in mind the source database doesn't have foreign keys implemented?
EDIT After some editing I am now able to post the data but have issues scaffolding the structure. My model builder feels like it's better now but something is still amiss
modelBuilder.Entity<Client>().HasMany(x => x.Machines).WithOne(x=>x.Client).HasForeignKey(x=>x.ClientID);
modelBuilder.Entity<Machine>().HasKey(x => new { x.ID, x.ClientID });
modelBuilder.Entity<MachineBob>().HasOne(x => x.Machine).WithMany(x => x.MachineBobData).HasForeignKey(x => new { x.MachineID, x.ClientID }).OnDelete(DeleteBehavior.NoAction);
modelBuilder.Entity<MachineMixer>().HasOne(x => x.Machine).WithMany(x => x.MachineMixerData).HasForeignKey(x => new { x.MachineID, x.ClientID }).OnDelete(DeleteBehavior.NoAction);
modelBuilder.Entity<MachineBob>().HasKey(c => new { c.TimeStamp, c.ClientID, c.MachineID });
modelBuilder.Entity<MachineMixer>().HasKey(x => new { x.TimeStamp, x.MachineID, x.ClientID });
END - EDIT
I am attempting to build a web app using EF Core that will allow a Client to own any number of machines and log their data. Each machine can be of a different type and every type of machine has a unique table as they log different things. I am able to create a Client and Machine object without any trouble but as soon as I try to log data into my Machinexxx data tables I run into issues concerning my use of composite foreign keys.
CloudApp.Models
public class Client
{
[Required]
[MaxLength(100)]
public string ClientName { get; set; }
[MaxLength(100)]
[Key]
public int ID { get; set; }
[MaxLength(100)]
// TODO: Automatically assign these email addresses as CLAIMS to allow the USER to see the CLIENT
public List<String> Emails;
public ICollection<Machine>? Machines { get; set; }
}
Which has a one to many relationship with the Machine class here
public class Machine
{
[MaxLength(50)]
[Key]
public int ID { get; set; }
[MaxLength(100)]
public string Factory { get; set; }
[MaxLength(50)]
public string Line { get; set; }
[MaxLength(50)]
public string MachineType { get; set; }
[MaxLength(50)]
public string MachineName { get; set; }
[MaxLength(50)]
public string Country { get; set; }
[MaxLength(50)]
public string City { get; set; }
[MaxLength(25)]
public int ZipCode { get; set; }
[MaxLength(50)]
public string Address { get; set; }
[MaxLength(50)]
public int ClientID { get; set; }
public Client Client { get; set; } // Navivation property
public List<MachineBob>? MachineBobData { get; set; } // Navivation property
public List<MachineMixer>? MachineMixerData { get; set; }// Navivation property
Which has a one to many relationship with the (currently) two possible machine types
public class MachineBob
{
[MaxLength(50)]
[XmlElement("TimeStamp")]
public DateTime TimeStamp { get; set; }
[XmlElement("Temperature")]
[MaxLength(50)]
public int Temperature { get; set; }
[MaxLength(50)]
[XmlElement("Heartbeat")]
public Boolean Heartbeat { get; set; }
[MaxLength(50)]
[ForeignKey("MachineID")]
public int MachineID { get; set; }
public Machine Machine { get; set; }
[MaxLength(50)]
[ForeignKey("ClientID")]
public int ClientID { get; set; }
public Client Client { get; set; }
}
[XmlRoot("Root"),Serializable]
public class MachineMixer
{
[MaxLength(50)]
[XmlElement("TimeStamp")]
public DateTime TimeStamp { get; set; }
[XmlElement("Temperature")]
[MaxLength(50)]
public int MixingRatio { get; set; }
[MaxLength(50)]
[XmlElement("Heartbeat")]
public Boolean Heartbeat { get; set; }
[XmlIgnore]
[MaxLength(50)]
public int MachineID { get; set; }
public Machine Machine { get; set; }
[MaxLength(50)]
public int ClientID { get; set; }
public Client Client { get; set; }
}
Lastly my model builder looks like this (all of my relationships are made here I try not to use data annotations due to their limitation)
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
foreach (var foreignKey in modelBuilder.Model.GetEntityTypes().SelectMany(e => e.GetForeignKeys()))
{
foreignKey.DeleteBehavior = DeleteBehavior.Cascade;
}
modelBuilder.Entity<Client>().HasMany(x => x.Machines);
modelBuilder.Entity<Machine>().HasOne(x => x.Client).WithMany(x => x.Machines).HasForeignKey(x => x.ClientID);
modelBuilder.Entity<Machine>().HasKey(x => new { x.ClientID, x.ID });
modelBuilder.Entity<Machine>().HasMany(x => x.MachineBobData).WithOne(x => x.Machine).HasForeignKey(x => new { x.MachineID, x.ClientID })OnDelete(DeleteBehavior.NoAction);
modelBuilder.Entity<Machine>().HasMany(x => x.MachineMixerData).WithOne(x => x.Machine).HasForeignKey(x => new { x.MachineID, x.ClientID }).OnDelete(DeleteBehavior.NoAction);
modelBuilder.Entity<MachineBob>().HasKey(c => new { c.TimeStamp, c.ClientID, c.MachineID });
modelBuilder.Entity<MachineMixer>().HasKey(x => new { x.TimeStamp, x.MachineID, x.ClientID });
}
What I would like to do is have every row be one data entry with a unique composite key composed of MachineID, ClientID, Timestamp. But as I explain below that is not currently possible...
As I stated before all CRUD operations for bot the Client and Machine class work fine. But MachineBob is incredibly problematic. Even though the model design sees that there is a composite key of three elements I get a SQL error whenever I attempt to write to the table. What is very strange is that I can write as expected so long as I used ClientID = 1, MachineID = 1, and a unique Timestamp which is what I want. But as soon as I attempt to add another row or another instance of this Machine BOB by writing Client ID=X issues start to arise.
Here is the SQL exception which seems to only have a problem with ClientId not being unique. But it is part of a composite key so the problem shouldn't be occurring anyway:
SqlException: The INSERT statement conflicted with the FOREIGN KEY constraint "FK_MachineBobs_Clients_ClientID". The conflict occurred in database "aspnet-ProcessCloudApp-B3DBF3A9-F26C-41F4-B7D6-CC2D2782C266", table "dbo.Clients", column 'ID'. The statement has been terminated.
I would like some assistance concerning my modelbuilding as I feel most lost with this.
Part of me feels that the problem may also lay with how I'm thinking about this "MachineBob" data model. Perhaps it would be better to instead have a one to one relationship between "Machine" and "MachineBob" and have all properties of type ICollection<T> or List<T>
Thank you for your assistance.
I was able to solve the issue (only took all week). I modified my modelbuilders accordingly but also got rid of the "public Client Client" in the MachineBOB. Even though I need the ClientID the objects are not directly connected to each other. Here's the code for reference to anyone else who finds it worth critiquing further or for their own use on this semi-complex relationship.
modelBuilder.Entity<Client>().HasMany(x => x.Machines).WithOne(x=>x.Client).HasForeignKey(x=>x.ClientID);
modelBuilder.Entity<Client>().HasKey(x => x.ID);
modelBuilder.Entity<Machine>().HasKey(x => new { x.ID, x.ClientID });
modelBuilder.Entity<MachineBob>().HasOne(x => x.Machine).WithMany(x => x.MachineBobData).HasForeignKey(x => new { x.MachineID, x.ClientID}).OnDelete(DeleteBehavior.NoAction);
modelBuilder.Entity<MachineMixer>().HasOne(x => x.Machine).WithMany(x => x.MachineMixerData).HasForeignKey(x => new { x.MachineID, x.ClientID }).OnDelete(DeleteBehavior.NoAction);
modelBuilder.Entity<MachineBob>().HasKey(c => new { c.TimeStamp, c.ClientID, c.MachineID });
modelBuilder.Entity<MachineMixer>().HasKey(x => new { x.TimeStamp, x.MachineID, x.ClientID });
and the class
[XmlRoot("Root"),Serializable]
public class MachineBob
{
[MaxLength(50)]
[XmlElement("TimeStamp")]
public DateTime TimeStamp { get; set; }
[XmlElement("Temperature")]
[MaxLength(50)]
public int Temperature { get; set; }
[MaxLength(50)]
[XmlElement("Heartbeat")]
public Boolean Heartbeat { get; set; }
[MaxLength(50)]
[ForeignKey("MachineID")]
public int MachineID { get; set; }
public Machine Machine { get; set; }
[MaxLength(50)]
[ForeignKey("ClientID")]
public int ClientID { get; set; }
So I am dipping into EF Core a little bit and experimenting with inheritance and the TPH pattern (I have no prior experience with this). The resulting database that EF creates is not what I expected and I am wondering if it's possible to get the result I am looking for using fluent-api, or if I am just missing the point altogether.
First, here are my POCO classes:
public class Commission
{
public int Id { get; set; }
public string Description { get; set; }
public double Rate { get; set; }
}
public class Party
{
public int PartyId { get; set; }
public string Name { get; set; }
public string Address1 { get; set; }
public string Address2 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Zip { get; set; }
}
public class Agency : Party
{
public string AgencyCode { get; set; }
public ICollection<Commission> Commissions { get; set; }
}
public class Carrier : Party
{
public string CarrierCode { get; set; }
public ICollection<Commission> Commissions { get; set; }
}
public class Principal : Party
{
public string Website { get; set; }
public string DistrictCode { get; set; }
}
And my context class just in case:
public class PartyContext : DbContext
{
public DbSet<Agency> Agencies { get; set; }
public DbSet<Carrier> Carriers { get; set; }
public DbSet<Party> Parties { get; set; }
public DbSet<Commission> Commissions { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(#"Server=LAPTOP-PC\SQLEXPRESS;Database=MyDatabase;Trusted_Connection=True;");
}
}
Basically, Agency, Carrier, and Principal all inherit from Party, so they should have all the same properties as Party. Agency and Carrier have additional specific properties, and should have a zero or one-to-many relationship with Commissions. Additionally, Principal has a couple specific properties, but has no relationship to commissions.
The resulting database is as follows:
I have no issue with the output of the Parties table itself, and understand what the discriminator field is, however I do not understand the two foreign-key relationships it created:
Parties to Commissions_AgencyPartyId
Parties to Commissions_CarrierPartyId
My question is why can't I just have one foreign key relationship from Parties to Commissions_PartyId on the back-end? And if I can, how can I tell EF to create it that way?
EDIT
Using Dmitry's suggestion and using the [InverseProperty] attribute, I ended up with the following database design which is not the desired output:
It actually made a third field (PartyId1). So I started looking at the EF documentation on relationships again and started playing with different annotations. Using the [ForeignKey("PartyId")] attribute gave me some hope after it produced the design that I am expecting:
However, this too had some unexpected effects. After trying to populate the database with an Agency and a Carrier, I receive an exception.
Here's the populating code:
PartyContext _context = new PartyContext();
// Add an Agency
var agencyCommission1 = new Commission
{
Description = "Contract",
Rate = 0.075
};
var agencyCcommission2 = new Commission
{
Description = "Hauling",
Rate = 0.10
};
var agencyCommissionList = new List<Commission>
{
agencyCommission1, agencyCcommission2
};
var agency = new Agency
{
Name = "Agency International",
Address1 = "12345 Main Street",
Address2 = "Suite 100",
City = "Chicago",
State = "IL",
Zip = "31202",
AgencyCode = "AI",
Commissions = agencyCommissionList
};
// Add Carrier
var carrierCommission1 = new Commission
{
Description = "Coal",
Rate = 0.15
};
var carrierCommission2 = new Commission
{
Description = "Mining",
Rate = 0.115
};
var carrierCommissionList = new List<Commission>
{
carrierCommission1, carrierCommission2
};
var carrier = new Carrier
{
Name = "Carrier International",
Address1 = "54321 Main Street",
Address2 = "Suite 300",
City = "Cleveland",
State = "OH",
Zip = "44115",
CarrierCode = "CI",
Commissions = carrierCommissionList
};
_context.Agencies.Add(agency);
_context.Carriers.Add(carrier);
try
{
_context.SaveChanges();
}
catch(Exception ex)
{
return;
}
The exception when adding the Agency is "Unable to cast object of type 'EFTest.Agency' to type 'EFTest.Carrier'." and the exception when trying to add the Carrier is "Unable to cast object of type 'EFTest.Carrier' to type 'EFTest.Agency'."
I will add that when using the original EF design, the program does work as expected, however the additional fields and foreign keys are making my OCD a little crazy :) Any more thoughts are welcome!
If you configure both relationships to use the same property as the foreign key you still have two relationships. So when adding a Commission with PartyIdequal to 1 EF interprets it as being related to an Agency with PartyId equal to 1 and a Carrier with PartyId equal to 1, obviously this would be impossible.
What you would need to do is create a relationship between Commission and Party, however this would mean that the Commissions navigation property would need to be moved to Party as well. But you can still hide it on Principal and other derived classes by making it protected and only exposing it on Agency and Carrier:
public class PartyContext : DbContext
{
public PartyContext(DbContextOptions options)
: base(options)
{
}
public DbSet<Agency> Agencies { get; set; }
public DbSet<Carrier> Carriers { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Party>().HasMany(typeof(Commission), "Commissions").WithOne();
}
}
public class Party
{
public int PartyId { get; set; }
protected ICollection<Commission> Commissions { get; set; }
}
public class Agency : Party
{
public new ICollection<Commission> Commissions
{
get { return base.Commissions; }
set { base.Commissions = value; }
}
}
public class Carrier : Party
{
public new ICollection<Commission> Commissions
{
get { return base.Commissions; }
set { base.Commissions = value; }
}
}
public class Commission
{
public int Id { get; set; }
}
Try this:
public class Commission
{
public int Id { get; set; }
public string Description { get; set; }
public double Rate { get; set; }
public Party Party { get; set; } // <-- "parent" party link here
}
public class Agency : Party
{
public string AgencyCode { get; set; }
[InverseProperty("Party")] // <-- link this collection with Party.Party
public ICollection<Commission> Commissions { get; set; }
}
public class Carrier : Party
{
public string CarrierCode { get; set; }
[InverseProperty("Party")] // <-- link this collection with Party.Party
public ICollection<Commission> Commissions { get; set; }
}
see my class structure first.
public class CustomerBase
{
public int CustomerID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Address1 { get; set; }
public string Address2 { get; set; }
public string Phone { get; set; }
public string Fax { get; set; }
}
public class Customer : CustomerBase
{
public virtual List<Addresses> Addresses { get; set; }
}
public class Addresses
{
[Key]
public int AddressID { get; set; }
public string Address1 { get; set; }
public string Address2 { get; set; }
public bool IsDefault { get; set; }
public virtual List<Contacts> Contacts { get; set; }
public int CustomerID { get; set; }
public virtual Customer Customer { get; set; }
}
public class Contacts
{
[Key]
public int ContactID { get; set; }
public string Phone { get; set; }
public string Fax { get; set; }
public bool IsDefault { get; set; }
public int AddressID { get; set; }
public virtual Addresses Customer { get; set; }
}
public class TestDBContext : DbContext
{
public TestDBContext()
: base("name=TestDBContext")
{
}
public DbSet<Customer> Customer { get; set; }
public DbSet<Addresses> Addresses { get; set; }
public DbSet<Contacts> Contacts { get; set; }
}
now this way i am trying to populate my customer base but getting error.
var bsCustomer1 = (from c in db.Customer
where (c.CustomerID == 2)
select new
{
CustomerID = c.CustomerID,
FirstName = c.FirstName,
LastName = c.LastName,
Addresses = (from ad in c.Addresses
where (ad.IsDefault == true)
from cts in ad.Contacts
where (cts != null && cts.IsDefault == true)
select ad).ToList(),
}).ToList()
.Select(x => new CustomerBase
{
CustomerID = x.CustomerID,
FirstName = x.FirstName,
LastName = x.LastName,
Address1 = x.Addresses.Select(a => a.Address1).SingleOrDefault(),
Address2 = x.Addresses.Select(a => a.Address2).SingleOrDefault(),
Phone = x.Addresses.Select(c => c.Contacts.Select(cd => cd.Phone).SingleOrDefault()),
Fax = x.Addresses.Select(c => c.Contacts.Select(cd => cd.Fax).SingleOrDefault())
}).ToList();
as per my situation a single customer may have multiple address but there should be one default one which i am pulling. a single address may have multiple contacts details but there should be one default one which i am pulling.
address1,address2, Phone and Fax are in base customer class. i want to pull single data from address and contacts tables based on isdefault is true and populate my customer. i am not very good in linq. so not being able to compose the query. please help me to compose it. thanks
Try the code below, guess it may fit about your request.
var bsCustomer1 = db.Customer.Where(p => p.CustomerID == 2)
.Select(x => new CustomerBase
{
CustomerID = x.CustomerID,
FirstName = x.FirstName,
LastName = x.LastName,
Address1 = x.Addresses.First(a => a.IsDefault).Address1,
Address2 = x.Addresses.First(a => a.IsDefault).Address2,
Phone = x.Addresses.First(a => a.IsDefault).Contacts.First(c => c.IsDefault).Phone),
Fax = x.Addresses.First(a => a.IsDefault).Contacts.First(c => c.IsDefault).Fax)
}).ToList();
Without knowing your actual meaning when you say: "i want to pull single data from address and contacts tables based on isdefault is true and populate my customer" that could mean two things:
I want to project a new object
I want to UPDATE the backing database.
Okay a few things about EF:
You have a context for CRUD (Create, Retrieve, Update, Delete) statements to the database.
The Context knows all the objects you identified in the database when setting up the EF file.
The t4 templates are created for entity context and the entity name itself and generate the context reference in the previous steps as well as create POCO class objects.
To create NEW objects you don't have to reference the object above it or below it. You just need to create it and then update the database with it.
So for an example of EF let's say I have two database tables:
I have a table tePerson that has fields: PersonId, FirstName, LastName, OrderId. This table has values
1 Brett X 1
2 Emily X 2
4 Ryan Y 1
10 Mark Z 1
OrderId is a foreign Key to a table teOrder with only has two fields: OrderId and Description.
1 Shirt
2 Dress
And my POCO objects generated from the T4 are:
public partial class tePerson
{
public int PersonId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public Nullable<int> OrderId { get; set; }
public virtual teOrder teOrder { get; set; }
}
It is important to note that that 'virtual teOrder' points to another POCO for me like this:
public partial class teOrder
{
public teOrder()
{
this.tePersons = new HashSet<tePerson>();
}
public int OrderId { get; set; }
public string Description { get; set; }
public virtual ICollection<tePerson> tePersons { get; set; }
}
Example for just projecting and for updating the database from the context as well as updating the database below. The key thing to remember is that objects when doing 'selects' with EF are not realized till you do a method like 'ToList()' to make them concrete. Else they are context db set which you cannot chain off of.
public class OtherPerson
{
public int PersonId { get; set; }
public string PersonLongName { get; set; }
public teOrder Order { get; set; }
}
static void Main(string[] args)
{
using (var context = new TesterEntities())
{
//Say I just want to project a new object with a select starting from orders and then traversing up. Not too hard
var newObjects = context.teOrders.Where(order => order.OrderId == 1)
//SelectMan will FLATTEN a list off of a parent or child in a one to many relationship
.SelectMany(peopleInOrderOne => peopleInOrderOne.tePersons)
.ToList()
.Select(existingPerson => new OtherPerson
{
PersonId = existingPerson.PersonId,
PersonLongName = $"{existingPerson.FirstName} {existingPerson.LastName}",
Order = existingPerson.teOrder
})
.ToList();
newObjects.ForEach(newPerson => Console.WriteLine($"{newPerson.PersonId} {newPerson.PersonLongName} {newPerson.Order.Description}"));
// Just an action clause to repeat find items in my context, the important thing to note is that y extends teOrder which is another POCO inside my POCO
Action<string, List<tePerson>> GetOrdersForPeople = (header, people) =>
{
Console.WriteLine(header);
people.ForEach(person => Console.WriteLine($"{person.FirstName} {person.LastName} {person.teOrder.Description}"));
Console.WriteLine();
};
//I want to look at a person and their orders. I don't have to do multiple selects down, lazy loading by default gives me a child object off of EF
GetOrdersForPeople("First Run", context.tePersons.ToList());
//Say I want a new order for a set of persons in my list?
var newOrder = new teOrder { Description = "Shoes" };
context.teOrders.Add(newOrder);
context.SaveChanges();
//Now I want to add the new order
context.tePersons.SingleOrDefault(person => person.PersonId == 1).teOrder = newOrder;
context.SaveChanges();
//I want to rexamine now
GetOrdersForPeople("After changes", context.tePersons.ToList());
//My newOrder is in memory and I can alter it like clay still and the database will know if I change the context
newOrder.Description = "Athletic Shoes";
context.SaveChanges();
GetOrdersForPeople("After changes 2", context.tePersons.ToList());
//Say I want to update a few people with new orders at the same time
var peopleBesidesFirst = context.tePersons.Where(person => person.PersonId != 1).ToList();
var firstPersonInList = context.tePersons.Where(person => person.PersonId == 1).ToList();
var newOrders = new List<teOrder> {
new teOrder { Description = "Hat", tePersons = peopleBesidesFirst },
new teOrder { Description = "Tie", tePersons = firstPersonInList }
};
context.teOrders.AddRange(newOrders);
context.SaveChanges();
GetOrdersForPeople("After changes 3", context.tePersons.ToList());
}
Console.ReadLine();
}
I'm writing an MVC5 application with EF6 which allows users to enter job timesheets, job notes and email the customer an update.
I want my database structure to compose of 3 tables for this (Timers, JobNotes and Emails). I can have 3 models like this;
[Table("Timers")]
public partial class TimerModel
{
public int ID { get; set; }
public string User { get; set; }
...etc
public virtual CustomerEmailModel CustomerEmail { get; set; }
}
[Table("JobNotes")]
public partial class JobNoteModel
{
public int ID { get; set; }
[Column("Username")]
public string User { get; set; }
...etc
public virtual CustomerEmailModel CustomerEmail { get; set; }
}
[ComplexType]
public partial class CustomerEmailModel
{
[Display(Name = "Email Customer?")]
public bool SendEmail { get; set; }
[DataType(DataType.EmailAddress)]
public string To { get; set; }
public string Subject { get; set; }
[DataType(DataType.MultilineText)]
public string Body { get; set; }
}
But this obviously doens't create the Email table, and instead adds the properties to the Timer and JobNotes tables (e.g. Email_SendEmail, Email_To, etc).
If i remove the [ComplexType] annotation, and change my other models to have;
public int? EmailID { get; set; }
[ForeignKey("EmailID")]
public virtual CustomerEmailModel CustomerEmail { get; set; }
Then it does create the table, but then i'm unsure of how to add new entries (i.e. emails are added on the fly, with a 0..1 relationship. It would mean that i would need to add to the email explicitly, get the added entries ID, and then assign it to the other model (timer or jobnote model) EmailID.
Is there a better way of doing this?
Thanks for any help in advance. Please let me know if you need any further information.
EDIT:
It seems that I do need to provide more information from the answers I'm getting so far.
I have two databases in play. A vendor supplied database, for an application which records job, timesheet, employee, customer, etc information. Lets call it DB01.
And a database (call it DB02) for my application (its a mobile app for users to record timesheet or jobnote information, and submit it to DB01).
The mobile app has a user interface which has the following key inputs;
Start Time, Stop Time, Break Time, Job Selector (drop down), Timesheet Title, Timesheet Notes, Send Email To Customer (checkbox), To Address, Subject, Body.
As such, a complex type does work correctly (and is what i'm using in the interm). However, because I have another page which will be able to email the customer as well, I wanted a separate email table (however, i don't want to have to save the email separately, and then assign it to the timesheet or jobnote).
Also - the only tables i need (for DB02), are the ones to store the timesheet, jobnote and email data. Once the timesheet or jobnote gets submitted, it can be deleted or archived. I only need these tables, because all other relevant information is contained in DB01. I can retrieve this data from views on DB02, and i can submit the information from DB02 to DB01 with a stored procedure from DB02. (DB02 has DB01 as a linked server)
This is a simple example of the use of foreign keys with EF6
public class A
{
public int Id { get; set; }
public virtual B Bobject { get; set; }
public int BId;
public virtual ICollection<C> Cs { get; set; }
}
public class B
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<A> As { get; set; }
}
}
public class C
{
public int Id { get; set; }
public string TransactionId { get; set; }
public virtual A Aobj { get; set; }
public int AId { get; set; }
}
You have quite big issues with your database to begin with, and you're not clearly defining the different sets of data.
I've redeveloped what you're trying to achieve using the Fluent API.
public class TimeSheet
{
public TimeSheet()
{
this.TimeSheetId = Guid.NewGuid()
.ToString();
}
public virtual Employee Employee { get; set; }
public string EmployeeId { get; set; }
public virtual Job Job { get; set; }
public string JobId { get; set; }
public string TimeSheetId { get; set; }
}
public class Employee
{
public Employee()
{
this.EmployeeId = Guid.NewGuid()
.ToString();
}
public string EmployeeId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string EmailAddress { get; set; }
public virtual ICollection<TimeSheet> TimeSheets { get; set; }
public virtual ICollection<Mail> MailsSent { get; set; }
}
public class Job
{
public Job()
{
this.JobId = Guid.NewGuid()
.ToString();
}
// One job will have one client
public virtual Client Client { get; set; }
public string ClientId { get; set; }
public string JobId { get; set; }
public string Name { get; set; }
public string Notes { get; set; }
// A Job may have many time sheets
public virtual ICollection<TimeSheet> TimeSheets { get; set; }
}
public class Client
{
public Client()
{
this.ClientId = Guid.NewGuid()
.ToString();
}
public string ClientId { get; set; }
public string EmailAddress { get; set; }
// A client can have many work packages / jobs.
public virtual ICollection<Job> WorkPackages { get; set; }
public virtual ICollection<Mail> Mails { get; set; }
}
public class Mail
{
public Mail()
{
this.MailId = Guid.NewGuid()
.ToString();
}
// A mail item will reference one client.
public virtual Client Client { get; set; }
public string ClientId { get; set; }
public string MailId { get; set; }
public string Subject { get; set; }
public string Body { get; set; }
public string EmployeeId { get; set; }
// A mail item will also originate from an employee
public virtual Employee Employee { get; set; }
// This doesn't belong here... as if it isn't
// being sent, then it wouldn't make sense to create
// create the email in the first place...
// If you want to queue emails, rename the field to `IsSent`
//
// public bool SendEmail { get; set; }
}
public class TimeSheetConfiguration : EntityTypeConfiguration<TimeSheet>
{
public TimeSheetConfiguration()
{
this.ToTable("TimeSheets");
this.HasKey(timeSheet => timeSheet.TimeSheetId);
this.Property(property => property.TimeSheetId).IsRequired().HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
this.Property(property => property.JobId) .IsRequired();
this.Property(property => property.EmployeeId) .IsRequired();
this.HasRequired(timeSheet => timeSheet.Job) .WithMany(job => job.TimeSheets).HasForeignKey(timeSheet => timeSheet.JobId);
this.HasRequired(timeSheet => timeSheet.Employee).WithMany(emp => emp.TimeSheets).HasForeignKey(timeSheet => timeSheet.EmployeeId);
}
}
public class EmployeeConfiguration : EntityTypeConfiguration<Employee>
{
public EmployeeConfiguration()
{
this.ToTable("Employees");
this.HasKey(emp => emp.EmployeeId);
this.Property(property => property.EmployeeId) .IsRequired().HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
this.Property(property => property.FirstName) .IsRequired();
this.Property(property => property.LastName) .IsOptional();
this.Property(property => property.EmailAddress).IsRequired();
this.HasMany(employee => employee.TimeSheets).WithRequired(time => time.Employee).HasForeignKey(time => time.EmployeeId);
this.HasMany(employee => employee.MailsSent) .WithRequired(mail => mail.Employee).HasForeignKey(mail => mail.EmployeeId);
}
}
public class ClientConfiguration : EntityTypeConfiguration<Client>
{
public ClientConfiguration()
{
this.ToTable("Clients");
this.HasKey(client => client.ClientId);
this.Property(property => property.ClientId) .IsRequired().HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
this.Property(property => property.EmailAddress).IsRequired();
this.HasMany(property => property.WorkPackages).WithRequired(job => job.Client) .HasForeignKey(job => job.ClientId);
this.HasMany(property => property.Mails) .WithRequired(mail => mail.Client).HasForeignKey(mail => mail.ClientId);
}
}
public class JobConfiguration : EntityTypeConfiguration<Job>
{
public JobConfiguration()
{
this.ToTable("Jobs");
this.HasKey(job => job.JobId);
this.Property(property => property.JobId) .IsRequired().HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
this.Property(property => property.Name) .IsRequired();
this.Property(property => property.ClientId).IsRequired();
this.Property(property => property.Notes) .IsRequired();
this.HasMany(job => job.TimeSheets).WithRequired(time => time.Job) .HasForeignKey(time => time.JobId);
this.HasRequired(job => job.Client).WithMany (client => client.WorkPackages).HasForeignKey(job => job.ClientId);
}
}
public class MailConfiguration : EntityTypeConfiguration<Mail>
{
public MailConfiguration()
{
this.ToTable("Mails");
this.HasKey(mail => mail.MailId);
this.Property(property => property.MailId) .IsRequired().HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
this.Property(property => property.ClientId) .IsRequired();
this.Property(property => property.EmployeeId).IsRequired();
this.Property(property => property.Subject) .IsRequired();
this.Property(property => property.Body) .IsRequired();
this.HasRequired(mail => mail.Client) .WithMany(client => client.Mails) .HasForeignKey(mail => mail.ClientId);
this.HasRequired(mail => mail.Employee).WithMany(employee => employee.MailsSent).HasForeignKey(mail => mail.EmployeeId);
}
}
public class ExampleContext : DbContext
{
public DbSet<Mail> Mails { get; set; }
public DbSet<Job> Jobs { get; set; }
public DbSet<Client> Clients { get; set; }
public DbSet<Employee> Employees { get; set; }
public DbSet<TimeSheet> TimeSheets { get; set; }
/// <summary>
/// This method is called when the model for a derived context has been initialized, but
/// before the model has been locked down and used to initialize the context. The default
/// implementation of this method does nothing, but it can be overridden in a derived class
/// such that the model can be further configured before it is locked down.
/// </summary>
/// <remarks>
/// Typically, this method is called only once when the first instance of a derived context
/// is created. The model for that context is then cached and is for all further instances of
/// the context in the app domain. This caching can be disabled by setting the ModelCaching
/// property on the given ModelBuidler, but note that this can seriously degrade performance.
/// More control over caching is provided through use of the DbModelBuilder and DbContextFactory
/// classes directly.
/// </remarks>
/// <param name="modelBuilder">The builder that defines the model for the context being created. </param>
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new MailConfiguration());
modelBuilder.Configurations.Add(new ClientConfiguration());
modelBuilder.Configurations.Add(new EmployeeConfiguration());
modelBuilder.Configurations.Add(new TimeSheetConfiguration());
modelBuilder.Configurations.Add(new JobConfiguration());
base.OnModelCreating(modelBuilder);
}
}