I have a model with 2 subclasses:
public class User
{
public string eRaiderUsername { get; set; }
public int AllowedSpaces { get; set; }
public ContactInformation ContactInformation { get; set; }
public Ethnicity Ethnicity { get; set; }
public Classification Classification { get; set; }
public Living Living { get; set; }
}
public class Student : User
{
public Student()
{
AllowedSpaces = AppSettings.AllowedStudentSpaces;
}
}
public class OrganizationRepresentative : User
{
public Organization Organization { get; set; }
public OrganizationRepresentative()
{
AllowedSpaces = AppSettings.AllowedOrganizationSpaces;
}
}
public class ContactInformation
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string EmailAddress { get; set; }
public string CellPhoneNumber { get; set; }
}
public enum Ethnicity
{
AfricanAmerican,
AmericanIndian,
Asian,
Black,
Hispanic,
NativeHawaiian,
NonResidentAlien,
White
}
public enum Classification
{
Freshman,
Sophomore,
Junior,
Senior,
GraduateStudent
}
public enum Living
{
OnCompus,
OffCampus
}
This is (mostly) saving fine using these initializers:
var students = new List<Student>
{
new Student{ eRaiderUsername="somestudent", ContactInformation=new ContactInformation{FirstName="Some", LastName="Student", EmailAddress="student#example.com", CellPhoneNumber="1234567890"}, Classification=Classification.Junior, Ethnicity=Ethnicity.Hispanic, Living=Living.OffCampus }
};
students.ForEach(s => context.Users.Add(s));
context.SaveChanges();
var orgReps = new List<OrganizationRepresentative>
{
new OrganizationRepresentative{ eRaiderUsername="somerep", ContactInformation=new ContactInformation{FirstName="Some", LastName="Representative", EmailAddress="orgrep#example.com", CellPhoneNumber="0987654321"}, Classification=Classification.Freshman, Ethnicity=Ethnicity.White, Living=Living.OnCompus, Organization=context.Organizations.Find(1) }
};
orgReps.ForEach(o => context.Users.Add(o));
context.SaveChanges();
None of the enums are saving (advice on this would be awesome too). But everything else is saving fine.
I have noticed Entity has added a Discriminator column with the subclass names. How do I use this to query only students, only organization reps, or just tell if the current object is a student or organization rep in a controller or view?
The discriminator column is used internally by EF to determine the type of object to instantiate.
For example you could query for a student directly. context.Set<Student>.Find(id). The same is true for an org rep. Or you could query for any user context.Set<User>.Find(id).
If you query for a student, but pass an org rep's ID, then EF will return null, because the ID doesn't belong to a student.
Related
I have a method that creates a Course. The Course retains the Teacher and their id. But after adding the Course, TeacherID has the value but Teacher has null. I think the problem is in the mapping. CourseAddRequest only has a teacherID, how can I add a Teacher?
AddCourse:
public CourseResponse AddCourse(CourseAddRequest courseAddRequest, Guid teacherId)
{
var teacher = _uniDbContext.Teachers
.Include(t => t.Courses)
.SingleOrDefault(t => t.Id == teacherId);
if (teacher == null)
throw new Exception("User doesn't exist");
var course = _mapper.Map<Course>(courseAddRequest);
teacher.Courses.Add(course);
_uniDbContext.Teachers.Update(teacher);
_uniDbContext.Courses.Update(course);
_uniDbContext.Courses.Add(course);
_uniDbContext.SaveChanges();
return _mapper.Map<CourseResponse>(course);
}
Course:
public class Course : BaseEntity
{
public string Header { get; set; }
public string Description { get; set; }
public Guid TeacherId { get; set; }
public Teacher Teacher { get; set; }
public List<Student> StudentsOnCourse { get; set; } = new List<Student>();
}
CourseResponse:
public class CourseResponse
{
public Guid Id { get; set; }
public string Header { get; set; }
public string Description { get; set; }
public TeacherResponse Teacher { get; set; }
public Guid TeacherId { get; set; }
public IEnumerable<StudentResponse> Students { get; set; }
}
CourseAddRequest:
public class CourseAddRequest
{
public string Header { get; set; }
public string Description { get; set; }
public Guid TeacherId { get; set; }
}
CourseProfile:
public class CourseProfile : Profile
{
public CourseProfile()
{
CreateMap<CourseAddRequest, Course>();
CreateMap<Course, CourseResponse>();
}
}
TeacherProfile:
public class TeacherProfile : Profile
{
public TeacherProfile()
{
CreateMap<TeacherAddRequest, Teacher>();
CreateMap<Teacher, TeacherResponse>();
}
}
You guessed right. When you execute the mapping from CourseAddRequest to Course it has no Teacher Therefore the Course teach will be null.
var course = _mapper.Map<Course>(courseAddRequest);
Assuming you're using EntityFramework or another ORM it'll be able to do the insertion correctly due to the existence of the Teacher that you've referenced in the Course via TeacherId property.
And while you add the Course to the teacher's in line 11 of your method, This still leaves the Teacher property null in course. As a result, when you map it to CourseResponse you get null.
There's two way to fix this, First, you can add the teach to your course object So the mapper finds the teacher before mapping to CourseResponse in the return statement.
course.Teacher = teacher;
Or map the teacher object directly to the response.
var courseResponse = _mapper.Map<CourseResponse>(course);
courseResponse.Teacher = _mapper.Map<TeacherResponse>(teacher);
return courseResponse;
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?
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; }
}
I am trying to update a record and its child at the same time. When I create the object from the database the child property is null (the property is a generic list).
I want to update the class and also update the child class without creating duplicated records in the system.
Here is how I generate the object:
var r = db.SupplierAs.Where(o => o.id == 1).First();
The SupplierA class has a property List. Using the above line of code this comes back null. I have been trying work out the code to initialize this property so I can update it but I am having no joy.
This is the original item I created:
db.Products.Add(new Product
{
name = "product test",
supplierA = new SupplierA
{
name = "supA",
price = 1.99m,
sku = "abc123",
otherCurrencies = new List<Currency>
{
new Currency
{
eur = 2.99m,
usd = 3.99m
}
}
},
});
db.SaveChanges();
I can update the supplier on its own easily like so:
var r = db.SupplierAs.Where(o => o.id == 1).First();
r.name = "Updated name";
db.SupplierAs.Attach(r);
db.Entry(r).State = EntityState.Modified;
db.SaveChanges();
But I cannot figure out how to generate the Currency object list as part of the SupplierAs object. Currencies doesnt seem to be in the db context.
Here are the class files:
public class Product
{
public int id { get; set; }
public string name { get; set; }
public virtual SupplierA supplierA { get; set; }
}
public class SupplierA
{
public int id { get; set; }
public string name { get; set; }
public string sku { get; set; }
public decimal price { get; set; }
public List<Currency> Currencies { get; set; }
}
public class Currency
{
public int id { get; set; }
public decimal eur { get; set; }
public decimal usd { get; set; }
}
The idea of products, suppliers and currencies doesn't make the greatest sense I know, I have extracted logic from my app in example, hopefully it makes enough sense what I am trying to achieve.
Models in question:
public class EmployeeType
{
public int employeeTypeId { get; set; }
public string typeName { get; set; }
public virtual List<Employee> Employees { get; set; }
}
public class Employee
{
public int employeeId { get; set; }
public string userName { get; set; }
public string firstName { get; set; }
public string lastName { get; set; }
public string password { get; set; }
public int employeeTypeId { get; set; }
public virtual EmployeeType EmployeeTypes { get; set; }
public virtual List<PhoneNumber> PhoneNumbers { get; set; }
}
At the moment i am adding different values through:
db.EmployeeType.Add(new EmployeeType
{
typeName = "Administrator"
});
db.EmployeeType.Add(new EmployeeType
{
typeName = "Seller"
});
db.EmployeeType.Add(new EmployeeType
{
typeName = "Accountant"
});
But in a case when i have to check if the user is an administrator or etc. i have to check through linq query and find out if the id is equal to the id in the Employee table.
How could i define default records in the EmployeeType model and not add the values through multiple .Add lines, so that i could use something like this:
if (db.Employee.FirstOrDefault(o => ...).servictypeId
== EmployeeType.Administrator)
{
}
The best way to handle this would be to convert employeetypeId into an enum in EF. You can easily achieve this by "converting" the field into an enum within the EDMX. Just right click on the property in the edmx model design screen and click "convert to enum".
Firstly though, you need to create an Enum, Called UserRole :-
enum UserRole : int
{
Administrator = 1,
Manager = 2,
Client = 3
}
Now, when you want to make new UserRole's you add them in your Enum.
When it comes to adding a new user, you simply do the following :-
new User object1 { Name = "Fred", UserRole = UserRole.Client};
dbContext.Save(object1);
EF will know to save the employeeTypeId as 3 in the database.
The advantage gets better, because now you can say :-
if(User.UserRole == UserRole.Adminstartor)
{
//Do Stuff
}