How do I get multi-mapping to work in Dapper? - ado.net

I am trying to do the following with Dapper (and failing).
My POCOs (all code simplified) are:
public class Company
{
public int CompanyId { get; private set; }
public string CompanyName { get; private set; }
public Person CompanyAddress { get; private set; }
public Person Administrator { get; private set; }
}
public class Person
{
public int PersonId { get; private set; }
public string FirstName { get; private set; }
public string LastName { get; private set; }
}
In the database the Company table has a FK for CompanyAddress and Administrator which maps to the PersonID PK in the Person table. Based on this and this I think the follwoing is how I want to do this:
public static Company Select(IDbConnection connection, int id)
{
Trap.trap();
return connection.Query<Company, Person, Person, Company>("select * from Company left join Person address on Company.CompanyAddress = address.PersonId left join Person admin on Company.Administrator = admin.PersonId where Company.CompanyId = #Id",
(cmpy, addr, admin) => new { PersonId = id }).FirstOrDefault();
}
But that gives me a compile error on the "new { PersonId = id }". What am I getting wrong?

You need to provide the SplitOn parameter to specify where the next table/class starts. You also shouldn't create an anonymous type but use a new scope to initialize the Administrator property of Company:
string sql = #"select c.CompanyId,c.CompanyName, c.CompanyAddress,
address.PersonId, etc. ....
from Company c
left join Person address
on Company.CompanyAddress = address.PersonId
left join Person admin
on Company.Administrator = admin.PersonId
where Company.CompanyId = #Id";
string splitOn = "PersonId"; // maybe two parameters separated by comma, see comment below the answer
return connection.Query<Company, Person, Person, Company>(sql,
(Company cmpy, Person addr, Person admin) => { cmpy.Administrator = admin; return cmpy; }
,null,null,true,splitOn)
.FirstOrDefault();
However, i'm not sure if that works already since you have two joins to the same table. So i think you need an alias for all of the duplicate columns like PersonId. But this migt be helpful anyway.

Related

Adding Entity with complex type property

I am trying to add the student entity in my database, which looks like this:
public class Student
{
public int ID { get; set; }
public string Name { get; set; }
public Course Course { get; set; }
}
The Course class looks like this:
public class Course
{
public int ID { get; set; }
public string Name { get; set; }
}
Data in the Course table:
| ID | Course |
------------------------
| 1 | Math |
-----------------------
| 2 | Physics |
-----------------------
When I define student as shown below:
var student = new Student
{
Name = "ABC",
Course = new Course { ID = 1, Name = "Math"}
};
and try to add it so
context.Students.add(student);
await context.SaveChangesAsync()
the entity wasn't added. I added this line of code
context.Entry(student).State = EntityState.Added;
but nothing changed.
First of all you have to set Foreign Key CourseId to your Student entity.
A) Student Entity
public class Student
{
public int StudentID { get; set; }
public string Name { get; set; }
public int CourseID { get; set; } //This is foreign key
public virtual Course Course { get; set; }
}
B) Course Entity: ICollection property gives you all students with particular CourseID.
public class Course
{
public int CourseID { get; set; }
public string Name { get; set; }
public virtual ICollection<Student> Students { get; set; }
}
1) If you add new student record with new course then
Course course = new Course
{
Name = "Math"
};
Course insertedCourse = context.Courses.Add(course);
Student student = new Student
{
CourseID = insertedCourse.CourseID,
Name = "ABC"
};
context.Students.Add(student);
await context.SaveChangesAsync();
2) If you have to reference your existing course from database then you have to get course from database and assign courseId to student entity.
Course existingCourse = GetCourseByIdFromDatabase();
Student student = new Student
{
CourseID = existingCourse.CourseID, //This ID comes from Db
Name = "ABC"
};
context.Students.Add(student);
await context.SaveChangesAsync();
Try once may it help you.
Edited:
As I understood with your last comment,
If you want to get student with its corresponding course then
Your get student method look like
public Student GetStudent(int Id)
{
Student student = context.Students.Find(Id);
return new Student
{
StudentID = student.StudentID,
Name = student.Name,
Course = student.Course
};
}
Ad you can access student's course like
Student student = GetStudent(1);
string CourseName = student.Course.Name;
you can use your async and await with above code depending your need.

MVC EF: Adding one to many component by ID

I am working with two classes Company and Visitor that have a one-to-many relationship.
public class Company
{
public int CompanyID { get; set; }
public string CompanyName { get; set; }
public virtual ICollection<Visitor> Visitors { get; set; }
}
public class Visitor
{
public int VisitorID { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public int CompanyID { get; set; }
public bool SendNewsletter { get; set; }
public virtual Company Company { get; set; }
}
I have a page where a Visitor can fill out information about themselves. The idea is that if a Company is not in the db, it will get added to the list. If the CompanyName the Visitor enters matches a name in the db, the Company is associated with the Visitor, rounding out its required info, which is then added to its own db.
var companyExists = db.Companies.Any(c => c.CompanyName == visitor.Company.CompanyName);
if(companyExists)
{
var existingCompany = db.Companies.SingleOrDefault(c => c.CompanyName == visitor.Company.CompanyName);
visitor.CompanyID = existingCompany.CompanyID;
visitor.Company = existingCompany;
}
db.Visitors.Add(visitor);
db.SaveChanges();
This code works but it seems redundant. I am associating the Visitor's CompanyID with the existing Company and then doing the same for the Company. Everything I read suggests that updating the Visitor's CompanyID should be sufficient but if I don't map the existingCompany to the Visitor's Company parameter, a second CompanyID is created. I feel like I'm missing some crucial point and am hoping someone here can point me in the right direction.
You don not need set visitor.Company. You can get Company information by CompanyID and you can use Where and FirstOrDefault to avoid redundant like this:
var companyExists = db.Companies.Where(c => c.CompanyName == visitor.Company.CompanyName).FirstOrDefault();
if (companyExists)
{
visitor.CompanyID = companyExists.CompanyID;
// visitor.Company = companyExists;
}
Notice that public virtual Company Company { get; set; } allows the Entity Framework to create a proxy around the virtual property so that the property can support lazy loading and more efficient change tracking. Please see this post for more information about virtual property in EF.

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

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

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

How to get Dapper to ignore/remove underscores in field names when mapping?

There are many ways to map database field names to class names, but what is the simplest way to just remove the underscores?
public IEnumerable<PersonResult> GetPerson(int personId)
{
using (var dbConnection = _dbConnectionFactory.Create(ConnectionStrings.ProjectXYZ))
{
IEnumerable<PersonResult> result =
dbConnection.Query<PersonResult>("fn_get_person", new { personId },
commandType: CommandType.StoredProcedure).ToList();
return result;
}
}
Table and database fields:
person
--------
person_id
full_name
Class that works: (dapper already ignores capitalization)
public class PersonResult
{
public int Person_Id { get; set; }
public string Full_Name { get; set; }
}
What I would like to change the class to:
public class PersonResult
{
public int PersonId { get; set; }
public string FullName { get; set; }
}
Dapper.DefaultTypeMap.MatchNamesWithUnderscores = true;
job done ;p
The readme doesn't show any support for renaming columns. You could alias them in select:
select person_id as personid, full_name as fullname from fn_get_person();
as suggested in this answer.