I have a simple project in which i have the following model.
Customer 1--->N Addresses N<----1 City N<----1 Country
^
N|
|
AddressTypes 1-------+
Here is my code:
public partial class Customer {
public Customer() {
this.Address = new HashSet<Address>();
}
public int Id {get;set;}
public string FirstName {get;set;}
public string LastName {get;set;}
public System.DateTime Birthday {get;set;}
public virtual ICollection<Address> Address {get;set;}
}
public partial class Address {
public int Id {get;set;}
public string Street {get;set;}
public string Area {get;set;}
public string Post {get;set;}
public bool isDeleted {get;set;}
public virtual Customer Customer {get;set;}
public virtual City City {get;set;}
public virtual AddressType AddressType {get;set;}
}
public partial class AddressType {
public AddressType() {
this.Address = new HashSet<Address>();
}
public int Id {get;set;}
public string Name {get;set;}
public virtual ICollection<Address> Address {get;set;}
}
public partial class Country {
public Country() {
this.City = new HashSet<City>();
}
public int Id {get;set;}
public string Name {get;set;}
public virtual ICollection<City> City {get;set;}
}
public partial class City {
public City() {
this.Address = new HashSet<Address>();
}
public int Id {get;set;}
public string Name {get;set;}
public virtual ICollection<Address> Address {get;set;}
public virtual Country Country {get;set;}
}
public partial class MyModelContext : DbContext {
public MyModelContext()
: base("name=MyModelContext") {
this.Configuration.LazyLoadingEnabled = true;
this.Configuration.ProxyCreationEnabled = false;
}
public virtual DbSet<Country> Countries {get;set;}
public virtual DbSet<City> Cities {get;set;}
public virtual DbSet<AddressType> AddressTypes {get;set;}
public virtual DbSet<Address> Addresses {get;set;}
public virtual DbSet<Customer> Customers {get;set;}
}
I want to return a list of customers. At first i tried to do a simple Customers.ToList() but i was getting an exception during serialization and i read that this was caused by cyclical references. So i added
Configuration.LazyLoadingEnabled = true;
Configuration.ProxyCreationEnabled = false;
and then i was able to get the customers but without the addresses. I tried doing:
public List<Customer> getCustomers() {
MyModelContext db = new MyModelContext();
return db.Customers.Include("Address").ToList();
}
but i don't know how to retrieve ALL the properties. I want to get in the list every customer and his addresses including the city, country, addresstype of each address. How can i do it?
You have couple of options:
1. Use DTOs (Data Transfer Objects).
2. Explicitly ignore the problematic property that contains the circular references to the parent object by applying the below Attributes:
// in Address entity
[IgnoreDataMember]
[XmlIgnore]
[NonSerialized]
public Customer Custom { get; set; }
Also in AddressType.Address, City.Address, and Country.City.
3. Project your entities into anonymous objects and exclude the pr:
db.Customers.Select(c => new
{
c.Id,
c.FirstName,
c.LastName,
c.Birthday,
Address = c.Address.Select<Address, object>(a => new
{
a.Id,
AddressType = new
{
a.AddressType.Id,
a.AddressType.Name
},
a.Area,
City = new
{
a.City.Id,
Country = new
{
a.City.Country.Id,
a.City.Country.Name
},
a.City.Name,
},
a.Post,
a.Street,
a.isDeleted
}),
});
You'll have to turn LazyLoadingEnabled and ProxyCreationEnabled back on in order for it to work (or use Include).
(As a side note, i don't really understand why you need this circular references at all, what benefit do you get from AddressType.Address?)
I solved it this way:
public List<Customer> getCustomers() {
var customers = db.CustomerSet.Include("Address")
.Include("Address.AddressType")
.Include("Address.City")
.Include("Address.City.Country")
.ToList();
return customers;
}
i don't really like that i have to explicitly write all the properties of the customer and there is not a generic way of doing what i want but it works.
Related
I have three classes
public class Country
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public byte CountryID { get; set; }
public byte OfficialLangID { get; set; }
}
public class Language
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public byte LangID { get; set; }
}
public class Name
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public byte NameID { get; set; }
public bool isLanguage { get; set; } // true for language - false for country
public byte FK { get; set; } // FK=LangID or CountryID
}
Now I want to create Navigation properties:
Country.Name
Language.Name
Name.Language
Name.Country
I want to do it in this way for many reasons, one of them to search for all the names in one table without joining.
Please don't suggest another way,I want navigation properties for my way.
why you're doing it like this? putting a boolean field to check the type of the entity here doesn't make any sense? in the end, the ef framework will create 2 tables.
here is my approach:
public class Country
{
public byte Id {get;set;}
public string Name {get;set;}
public int LanguageId {get;set;}
}
public class Language
{
public byte Id {get;set;}
public string Name {get;set;}
// assuming that each language may have one or many countries
public ICollection<Country> Countries {get;set;}
}
ef core here will create the tables and the relations automatically. now if you see some code duplications (like I've understood), that the 2 entities use the same field types and names, here is what you can do
public abstract class EntityBase
{
public byte Id {get;set;}
public string Name {get;set;}
}
now inherit this abstract class to the entity class
public class Country : EntityBase
{
public int LanguageId {get;set;}
}
public class Language : EntityBase
{
public ICollection<Country> Countries {get;set;}
}
I am using "CodeFirst Existing Database" and have introduced a stored procedure from mycontext class which seems fine but the entity i am mapping to has a complex type.
MyContext
public virtual List<Student> Get_AllStudents(int year, string classes, string indicators) {
StringBuilder spCommand = new StringBuilder();// = "CALL Get_AllStudents(";
//params object[] parameters;
MySqlParameter[] mySqlParams = new MySqlParameter[]{new MySqlParameter("yearId", year),
new MySqlParameter("classIds", classes),
new MySqlParameter("indicatorList", indicators)};
spCommand.Append("CALL Get_AllStudents(#yearId,#classIds,#indicatorList);");
return this.Database.SqlQuery<Student>(spCommand.ToString(), mySqlParams).ToList<Student>();
}
and my query is
select s.firstname, s.surname,s.IndicatorID from students s
where ClassId in (classIds)
and yearId = yearId
and indicatorId in (indicatorList);
My Student Entity has a complex type "Name"
public class Student
{
public int studentID { get; set; }
public string studentCode { get; set; }
public Name name { get; set; }
public Nullable<System.DateTime> birthdate { get; set; }
}
public class Name {
[Column("surname")]
public string surname { get; set; }
[Column("middlename")]
public string middlename { get; set; }
[Column("firstname")]
public string firstname { get; set; }
[Column("preferredname")]
public string preferredname { get; set; }
}
How do i tell entity framework to map s.firstname and s.surname to map to Student.Name.firstname and Student.Name.surname properties
I havent used import function (as it is giving me some stupid error i dont want to deal with "Your project references the latest version of EntityFramework however EF data provider compatible with this version cannot be found...") can i change some sp settings in mycontext class to expect complex type?
Error
Cannot create a value for property 'name' of type 'DbContexts.Name'.
Only properties of primitive or enumeration types are supported.
I have 4 classes like that:
Organization1:
Contact 1:
Phones:
phone1
phone2
Addresses:
address1
address2
Bankdata:
bankdata1
bankdata2
Contact 2:
Phones:
phone1
phone2
Addresses:
address1
address2
Bankdata:
bankdata1
bankdata2
Contact 3:
Phones:
phone1
phone2
Addresses:
address1
address2
Bankdata:
bankdata1
bankdata2
Organization 2:
... and so on...
In order to grab the list of Organizations with all its contacts and each contact including JUST the phone1 (where main == true), address1 and bankdata1 I have written the following inside Entity Framework a query:
...
public class ContactManagementRepository : IContactManagementRepository
{
public IEnumerable<Organization> getAllOrganizations()
{
return _context.Organizations
.OrderBy(o => o.organizationName)
.Include(o => o.Contacts)
.ToList();
}
...
But I just get ONE Organization, and no contacts.
A tried a second attempt like this:
public class ContactManagementRepository : IContactManagementRepository
{
public IEnumerable<Organization> getAllOrganizations()
{
return _context.Organizations.ToList();
}
...
It returns all the organizations, but not data under the tree (contact->phones, etc)
A third attempt (very bad for performance) was to get ALL the organizations and loop through using getorganizationsbyID and get the info of each one like this:
...
public Organization GetOrganizationById(Guid Id)
{
return _context.Organizations
.Include(o => o.Contacts)
.ThenInclude(c => c.Phone.main == true)
.Where(o => o.Id == Id)
.FirstOrDefault();
}
...
But it just get the organization with JUST ONE CONTACT (the first one) and not phones, address or bankdata. The method "ThenInclude" does not accept a second call. It means accept just one child under contact, but not several children.
Here are my model classes:
Organization class:
public class Organization
{
public Organization() { }
private Organization(DateTime dateCreated)
{
this.dateCreated = DateTime.Now;
Contacts = new List<Contact>();
}
public Guid Id { get; set; }
public DateTime dateCreated { get; set; }
public string organizationName { get; set; }
public string organizationType { get; set; }
public string web { get; set; }
// Contacts
public ICollection<Contact> Contacts { get; set; }
}
Contact class:
public class Contact
{
public Contact() { }
private Contact(DateTime dateCreated)
{
this.dateCreated = DateTime.Now;
Phones = new List<Phone>();
Addresses = new List<Address>();
Bankdatas = new List<Bankdata>();
}
public Guid Id { get; set; }
public DateTime dateCreated { get; set; }
public string firstName { get; set; }
public string lastName { get; set; }
public string email { get; set; }
public Guid OrganizationId { get; set; }
[ForeignKey("OrganizationId")]
public virtual Organization Organization { get; set; }
public virtual ICollection<Phone> Phones { get; set; }
public virtual ICollection<Address> Addresses { get; set; }
public virtual ICollection<Bankdata> Bankdatas { get; set; }
}
Phone class (same structure for addresses and bankdata)
public class Phone
{
public Guid Id { get; set; }
public DateTime dateCreated { get; set; }
public string phone { get; set; }
public bool main { get; set; }
// Foreign Key for Contacts
public Guid ContactId { get; set; }
//Related Organization entity
[ForeignKey("ContactId")]
public virtual Contact Contact { get; set; }
}
Database is created with the PK and FK in correctly in place (code-first)
And now I am lost. Does anybody could help with this?
How do I query (LINQ) for organization -> child -> 4 grandchildren ?
I have read literally dozens of threads here and follow tutorials in Udemy and Pluralsight without success.
EDIT
# Hadi Hassan
I followed your instructions, I created new classes OrganizationDTO, ContactDTO, AddressDTO, PhoneDTO and BankdataDTO.
I created the DTO for Bank, Phone and address because even if am sure just one is main (there is a radio button at the front end) I still will need the whole list when I get the contactsdetails.html. It will allows me to make searches by country-code and so on.
I have changed, as per your advice, but it dd not even compile. I get error telling:
*DbSet<Organization>ContactManagement.Context.Organizations {get; set;}
Cannot assign method group to an implicitly-typed variable.*
I have decided to test changing the Context class like this:
DBSet. So I changes all DBset properties with the suffix DTO. Like this:
public DbSet<OrganizationDTO> OrganizationsDTO { get; set; }
public DbSet<ContactDTO> ContactsDTO { get; set; }
public DbSet<BankdataDTO> BankdatasDTO { get; set; }
public DbSet<AddressDTO> AddressesDTO { get; set; }
public DbSet<PhoneDTO> PhonesDTO { get; set; }
But the I have got copilations error everywhere.
Best way to get your data is by relying on the DTO
so your code will appear like this
public class OrganizationDTO
{
public OrganizationDTO()
{
Contacts = new List<ContactDTO>();
}
public Guid Id{get;set;}
public string Name{get; set;}
// other needed properties goes here
public IList<ContactDTO> Contacts{get;set;}
}
public class ContactDTO
{
public ContactDTO
{
Phones = new List<PhoneDTO>();
Addresses = new List<AddressDTO>();
Banks = new List<BankDTO>();
}
public Guid Id{get;set;}
public string FirstName{get;set;}
public string LastName {get;set;}
// other required properties goes here
public IList<PhoneDTO> Phones {get;set;}
public IList<BankDTO> Banks{get;set;}
public IList<AddressDTO> Addresses{get;set;}
}
public class PhoneDTO
{
public Guid Id{get;set;}
public string Phone{get;set;}
}
public class AddressDTO
{
public Guid Id{get;set;}
public string Description{get;set;}
}
public class BankDTO
{
public Guid Id{get;set;}
public string Name{get;set;}
}
Your query then will look like
var query = _context.Organizations
.Select t=> new OrganizationDTO{
Id = t.Id,
Name = t.organizationName,
Contacts = t.Contacts.Select(c => new ContactDTO{
Id = c.Id,
FirstName = c.firstName,
LastName = c.lastName,
Phones = c.Phones.Where(p=>p.Main).Select(p=>new PhoneDTO{Id = p.Id, Phone= p.phone}),
Banks = c.Banks.Where(p=>p.Main).Select(p=>new BankDTO{Id = p.Id, Name= p.BankName}),
Addresses = c.Addressess.Where(p=>p.Main).Select(p=>new AddressDTO{Id = p.Id, Description= p.Description}),
})
}).OrderBy(t=>t.Name);
Implementing the DTO will provide many advantages for your data
Avoid Circular references
Return the required data and not all the object (Data Projection)
Allows you to combine many objects together to appear in same result, for example, in the solution I returned object BankDTO and PhoneDTO and AddressDTO which allows your to return list of object from these entities, but if you are sure that only one phone, bank , address is main, then you can instead define these data as properties in the class ContactDTO and by this you combined 4 entities data in one result.
Your ContactDTO will become as
public class ContactDTO
{
public Guid Id{get;set;}
public string FirstName{get;set;}
public string LastName{get;set;}
public string Phone{get;set;}
public string Bank{get;set;}
public string Address{get;set;}
}
and your code will be like
var query = _context.Organizations
.Select t=> new OrganizationDTO{
Id = t.Id,
Name = t.organizationName,
Contacts = t.Contacts.Select(c => new ContactDTO{
Id = c.Id,
FirstName = c.firstName,
LastName = c.lastName,
Phone = c.Phones.Where(p=>p.Main).Select(p=>p.phone).FirstOrDefault(),
Bank = c.Banks.Where(p=>p.Main).Select(p=>p.Name).FirstOrDefault(),
Address = c.Addressess.Where(p=>p.Main).Select(p=>p.Discreption).FirstOrDefault(),
})
}).OrderBy(t=>t.Name);
and in case you want to get the phone as object and not list, may be you want to get the benefit of the phone Id, you can instead of IList<PhoneDTO> to define directly PhoneDTO.
Hope this will help you
I am using EF version 6.1 and have a mapping problem:
class BasePoco
{
public Guid Id{get;set;}
}
class Student : BasePoco
{
public string Name;
}
public class UserBase : BasePoco
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Gender { get; set; }
public string Photo { get; set; }
}
public class UserDetail : UserBase
{
public string MobileNumber { get; set; }
public string EmailID { get; set; }
}
public Enum UserType
{
Student = 1,
User=2
}
the Attendance class
public class Attendance
{
public class UserId {get;set;} // Can be either student or user
public UserType UserType {get;set;}
}
I need to mark attendance for Student as well as User in the same table.
The UserType would determine whether the Id is of a student or User and the primary key would be a combination of UserType and Id.
How can I accomplish this using EF code first approach.
Sorry you cant use multiple type over single property. You do understand, because EF run over metadata. Which use EF know metadata from model class. This is a problem. Attendance table foregin key is multiple table referance and Attendance model contains both model. You should create logical layer for check UserType and access correct model. For example
public class Attendance
{
public UserType userType {get;set;}
public Guid? UserId {get;set;}
public virtual User user {get;set;}
public Guid? StudentId {get;set;}
public virtual Student student {get;set;}
}
now layer class
public class AttendanceUserLayer
{
public static object GetUser(Attendance attendance) {
if (attendance.userType == UserType.User) {
return attendance.User;
} else {
return attendance.Student;
}
}
how to use
Attendance attendance = context.Attendance.FirstOrDefault();
var userOrStudent = AttendanceUserLayer.GetUser(attendance);
if you cannot use the type of object result, write interface both class and set return type that interface.
I created classes:
public class Country
{
public long CountryId {get;set;}
public string CountryName {get;set;}
}
public class Profile
{
public long ProfileId {get;set;}
public string ProfileName {get;set;}
public Country Country {get;set;}
}
and configuration for Profile:
public class ProfileConfiguration : EntityConfiguration<Profile>
{
public IlluminatiCoreProfileConfiguration()
{
Relation(p => p.Country);
}
}
Then I create context and run context.CreateDatabase(). New database contains table Profiles with column Country_CountryId. How can I write configuration for changing column name to "CountryId"?
Thanks.
In your POCO for Profile:
public class Profile
{
public long ProfileId {get;set;}
public string ProfileName {get;set;}
[ForeignKey("CountryId")]
public Country Country {get;set;}
}
In your POCO for Country
[Table("Country")]
public class Country
{
[Column(Name = "CountryId")]
public int CountryId {get;set;}
}
This will over write the brain dead Object_Property mapping that EF by default creates. You can specify this on any table / property to override the actual DB column naming conventions.
EDIT:
I guess the namespace for those annotations would be helpful:
using System.ComponentModel.DataAnnotations;