Entity Framework: How to query (LINQ) for data ->child-> 4 grandchildren - entity-framework

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

Related

asp.net web api server data not syncing with database between BL

Hello I am new to servers and REST API and am trying to extract data from a dynamically created table and the data does not sync with the data in the database.
I have an sql database from which I extracted an entity database in asp.net web project.
This is an example for GET of one entity class (exists in database):
public class EmployeeBL
{
private FSProject1Entities db = new FSProject1Entities();
public List<Employee> GetEmployees(string fname, string lname, string depID)
{
return GetEmployeeSearchResult(fname, lname, depID);
}
}
And this is an example for a method from a class such as I created in order to combine data from 2 tables:
public class ShiftEmployeeDataBL
{
private FSProject1Entities db = new FSProject1Entities();
private List<ShiftEmployeeDataBL> GetEmployeeByShiftID(int id)
{
List<ShiftEmployeeDataBL> shiftEmpData = new List<ShiftEmployeeDataBL>();
foreach (Employee emp in db.Employee)
{//build list... }
return shiftEmpData;
}
My problem is that db.Employee via this GET request path (ShiftEmployeeData) is old data and via Employee GET request is good data (assuming the data was updated via Employee path).
And vice versa - it would appear that if I update Employee via ShiftEmployeeData class, it would appear as good data for ShiftEmployeeData class and not update for Employee.
I have APIcontrollers for both classes.
what is happening? I feel like I am missing something.
I tried closing cache options in browser.
update with code for elaboration:
entity Employee:
public partial class Employee
{
public int ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int StartWorkYear { get; set; }
public int DepartmentID { get; set; }
}
employee update(auto generated by entity model code generation from db):
public void UpdateEmployee(int id, Employee employee)
{
Employee emp= db.Employee.Where(x => x.ID == id).First();
emp.FirstName = employee.FirstName;
emp.LastName = employee.LastName;
emp.StartWorkYear = employee.StartWorkYear;
emp.DepartmentID = employee.DepartmentID;
db.SaveChanges();
}
employeeshiftdata class (not a db table but still in the models folder):
public class EmployeeShiftData
{
public int ID { get; set; } //EmployeeID
public string FirstName { get; set; }
public string LastName { get; set; }
public int StartWorkYear { get; set; }
public string DepartmentName { get; set; }
public List<Shift> Shifts { get; set; }
}
employeeshift GET part of the controller:
[EnableCors(origins: "*", headers: "*", methods: "*")]
public class EmployeeShiftDataController : ApiController
{
private static EmployeeShiftDataBL empShiftDataBL = new EmployeeShiftDataBL();
// GET: api/EmployeeShiftData
public IEnumerable<EmployeeShiftData> Get(string FirstName = "", string LastName = "", string Department = "")
{
return empShiftDataBL.GetAllEmployeeShiftData(FirstName, LastName, Department);
}
//...
}
Would need to see the code that interacts with the database, especially the code that makes the updates.
If the changes are written with Entity Framework, are the models themselves properly related with navigational properties?
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public List<EmployeeShift> EmployeeShifts { get; set; }
// etc.
}
public class EmployeeShift
{
public int Id { get; set; }
public Int EmployeeID { get; set; }
public Employee Employee { get; set; }
// etc.
}
If those are good, and both models are covered by Entity Framework's context tracking, then both should be updated.

How to using AutoMapper and Entity Framework to Pass a Dynamic Parameter To Select A Child Object And Map

I've a requirement to map Company to CompanyDTO based on the user domain. I need to pass the Domain ID to the the Mapper and select CompanyDomain object
public class Company
{
public int CompanyID { get; set;}
public int Name { get; set;}
public List<CompanyDomain> Domains { get; set; }
}
public class CompanyDomain
{
public int DomainID { get; set; }
public string Analyst { get; set; }
}
public class CompanyDTO
{
public int CompanyID { get; set; }
public string Name { get; set; }
public string Analyst { get; set; }
}
You can do this by using the mapping options. For example, set up your map like this:
cfg.CreateMap<Company, CompanyDTO>()
.ForMember(
d => d.Analyst,
opts => opts.MapFrom((s, d, _, ctx) => //Use the 4 parameter version
s.Domains.Single(d => d.DomainID == (int)ctx.Items["DomainID"]).Analyst));
It looks a little more complicated than it really is, but it's just telling AutoMapper to search the company domains using a parameter that was passed in. You would then use it like this:
var result = mapper.Map<CompanyDTO>(company, opts => opts.Items["DomainID"] = 2);

Entity Framework one to many : SqlException

I have a little problem when I try to save an item in my DB using EntityFramework.
My classes looks like:
public partial class Site
{
public int Id { get; set; }
public string Name { get; set; }
public string LongName { get; set; }
public string Adress { get; set; }
public City City { get; set; }
public Country Country { get; set; }
public string VATNumber { get; set; }
}
public class Country
{
public int CountryId { get; set; }
public string Name { get; set; }
public string IsoCode { get; set; }
}
And when I try to create a new site in my controller it works, but when I try to add a link to an existing Country :
if (SiteProvider.GetSiteByName(Site.Name) == null)
{
Site.Country = CountryProvider.GetCountryById(1);//it's working, i see the link to the right country
SiteProvider.Create(Site);
}
public static void Create(Site Site)
{
using (MyDBContext Context = new MyDBContext())
{
Context.Site.Add(Site);
Context.SaveChanges(); //here is the problem
}
}
I got this error:
SqlException: Cannot insert explicit value for identity column in
table 'Country' when IDENTITY_INSERT is set to OFF
Thanks in advance for your help.
Add CountryId property to Site class and when adding a new Site set CountryId instead of Country property
public int CountryId { get; set; }
[ForeignKey("CountryId")]
public Country Country{ get; set; }
You have a slight issue with your use of contexts here. You have used one DBContext instance to load the country (where this country object will be tracked) and then a second DBContext to save the site (where the first country object is a property).
It is preferable to perform all your operations for a single unit of work by using one DB context (that would be shared between your classes) and the responsibility for disposing of it to be handled outside your repository layer.

Entity framework 6 use parentid in where clause with lambda expression

I'm new to EF and want to get an entry from my database (SQLite) in the following way:
Classes:
public class Customer
{
public Guid Id { get; set; }
public List<Month> Months { get; } = new List<Month>();
}
public class Month
{
public int Id { get; set; }
public string CacheId { get; set; }
public string Data { get; set; }
[Required]
public Customer Customer { get; set; }
}
DBContext:
public DbSet<Customer> Customers { get; set; }
public DbSet<Month> Months { get; set; }
Usage:
using (var context = new CustomerContext())
{
var customer = context.Customers.First();
context.Database.Log = Console.WriteLine;
var shouldContainOneEntry = context.Months.Where(x => x.Customer.Id == customer.Id).ToList();
}
shouldContainOneEntry is emtpy, but a test with a delegate and a static variable instead of the lambda expression worked:
private static Guid staticGuid;
public static bool DelegateTest(Month x)
{
return staticGuid == x.Customer.Id;
}
...
staticGuid = customer.Id;
var workingVersion = context.Months.Where(DelegateTest).ToList();
The generated SQL looks correct:
SELECT
[Extent1].[Id] AS [Id],
[Extent1].[CacheId] AS [CacheId],
[Extent1].[Data] AS [Data],
[Extent1].[Customer_Id] AS [Customer_Id]
FROM [Months] AS [Extent1]
WHERE [Extent1].[Customer_Id] = #p__linq__0
-- p__linq__0: '5cfde6e0-5b3f-437b-84c8-2845b077462d' (Type = AnsiStringFixedLength, IsNullable = false)
Why is the version with the lambda expression not working?
The solution was found by following the hints of IvanStoev.
SQLite stores the Guid by default in a binary format.
Therefor a query like
SELECT * FROM Months WHERE CustomerId = '5cfde6e0-5b3f-437b-84c8-2845b077462d'
delivers an empty result.
Using SQLite Adminstrator the Guid is shown with the binary format.
Using the server explorer in VS the Guid is shown with a string format which lead me to the wrong conclusion that this should work.
After setting the option BinaryGUID of the connection string to false the code works fine.
Based on your declarations
public class Customer
{
public Guid Id { get; set; }
public List<Month> Months { get; } = new List<Month>();
}
public class Month
{
public int Id { get; set; }
public Guid CustomerId{get;set;} // you need to add the foreign Key of the customer ( i will suppose that your foreign key in db called CustomerId
public string CacheId { get; set; }
public string Data { get; set; }
[Required]
[ForeignKey("CustomerId")] // and you need to tell EF which column is the foreign key
public Customer Customer { get; set; }
}
how to use it now
using (var context = new CustomerContext())
{
var customer = context.Customers.First();
context.Database.Log = Console.WriteLine;
var shouldContainOneEntry = context.Months.Where(x => x.CustomerId == customer.Id).ToList();
}
hope it will help you

DBContext not updating foreign key on update

We are migrating over from EF4 to EF5 and part of this is the move from POCO Generator created classes to the DBContext generated classes. However, we are now seeing issues where the foreign keys are not being set on updates.
I have the following
public abstract partial class Contact
{
public string FirstName { get; set; }
public string LastName { get; set; }
public Nullable<int> AddressID { get; set; }
public virtual Address Address { get; set; }
}
public partial class UserContact : Contact
{
public string Description { get; set; }
public string Notes { get; set; }
}
public partial class Address
{
public string Line1 { get; set; }
public string Line2 { get; set; }
}
If I create a contact without an address, in my first operation, then update the contact to add an address in the next operation, the AddressID in the contact is not being populated - it remains null. So, when I do this:
UserContact contact = new UserContact
{
FirstName = "fname",
LastName = "lname"
};
int contactID = _contactAccessor.AddContact(contact); // add contact no address
UserContact retContact = m_contactAccessor.GetContact(contactID);
retContact.Address = new Address
{
Line1 = "line1",
Line2 = "line2",
};
m_contactAccessor.UpdateContact(retrievedContact); // add an address to the contact
In the ContactAccessor, my UpdateContact method does this:
public void UpdateContact(UserContact userContact)
{
UserContact retrievedContact = (from c in context.Contact.OfType<UserContact>()
.Include(c => c.Address)
where (c.ID == userContact.ID)
select c).FirstOrDefault());
retrievedContact.Address = userContact.Address;
context.Entry<Contact>(retrievedContact).CurrentValues.SetValues(userContact);
context.SaveChanges());
}
If I look at the database, the contact is created, as is the new address, but the AddressID on the contact is null, so the contact has no association with the address.
I'm not sure what I am doing wrong.