Dapper easiest and fastest way to map single columns to multiple classes? - c#

I have a class that looks like this
public Guid assessmentId { get; set; }
public string? applicationNumber { get; set; }
public ApplicationStatus? applicationStatus { get; set; }
public List<CompanyInformationModel>? companyInformationModel { get; set; }
public List<LocationInformationModel>? locationInformationModels { get; set; }
public List<JobInformationModel>? jobInformationModel { get; set; }
public class CompanyInformationModel
{
public Guid CompanyInformationId { get; set; }
public string? companyName { get; set; }
public string? contactName { get; set; }
public string? primaryPhone { get; set; }
public string? secondaryPhone { get; set; }
public string? email { get; set; }
public string? hourlyRate { get; set; }
}
public class LocationInformationModel
{
public Guid LocationInformationId { 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 JobInformationModel
{
Guid JobInformationId { get; set; }
string? jobTitle { get; set; }
string? jobDescription { get; set; }
}
public enum ApplicationStatus
{
[Description("Pending")]
Pending,
[Description("Approved")]
Approved,
[Description("Declined")]
Declined
}
I query my table with joins like so in dapper
var sqlStatement = $#"Select * from SituationalAssessment s
join CompanyInformation c on c.CompanyInformationId = s.AssessmentId
join LocationInformation l on l.LocationInformationId = s.AssessmentId
join JobInformation j on j.JobInformationId = s.AssessmentId";
var retVal= await _dapperHelper.ExecuteQueryAsync<AssessmentModel>(sqlStatement);
My results will look like this
AssessmentId ApplicationNumber ApplicationStatus DateCreated ModifiedDateCreated CompanyInformationId CompanyName ContactName PrimaryPhone SecondaryPhone Email HourlyRate LocationInformationId Address1 Address2 City State Zip JobInformationId JobTitle JobDescription
78FE1852-5A26-4624-925C-74653DE9DAD9 227114 Pending 2023-02-08 13:57:52.743 NULL 78FE1852-5A26-4624-925C-74653DE9DAD9 Company Test Test Contact 123-234-2343 NULL test1#email.com NULL 78FE1852-5A26-4624-925C-74653DE9DAD9 123 Main Ste Apt 201 Los Angeles CA 90210 78FE1852-5A26-4624-925C-74653DE9DAD9 TEst Test Description
How do I map the results of CompanyInformation, LocationInformation and JobInformation to their own classes automatically in dapper?
The columns in the database are the exact same names as the fields in my classes

I assume that assessmentId, CompanyInformationId, LocationInformationId, and JobInformationId are the primary keys of the related tables. If it is so then your relationships between these tables should be 1 to 1 not 1 to many. And your assessment model should look like this :
public class AssesmentModel {
public Guid assessmentId { get; set; }
public string? applicationNumber { get; set; }
public ApplicationStatus? applicationStatus { get; set; }
public CompanyInformationModel? companyInformationModel { get; set; }
public LocationInformationModel? locationInformationModel { get; set; }
public JobInformationModel? jobInformationModel { get; set; }
}
Once we fix that then we can use the dappers splitOn parameter to split the query between the models.
var assessments = await connection.QueryAsync<AssesmentModel, CompanyInformationModel, LocationInformationModel, JobInformationModel>(sqlStatement, (assesment, company, location, job) => {
assesment.companyInformationModel = company;
assesment.locationInformationModel = location;
assesment.jobInformationModel = job;
return assesment;
}, splitOn: "CompanyInformationId, LocationInformationId, JobInformationId");
Need to mention that using explicit column names (instead of just *) in your query would be nice since dapper uses string.Split like operation to split the columns, changes to the related tables might break the query later on.
Edit
If you have somehow 1 to many relationships between those same method can be applied in this scenario like this :
var assessments = await connection.QueryAsync<AssesmentModel, CompanyInformationModel, LocationInformationModel, JobInformationModel>(sqlStatement, (assesment, company, location, job) => {
assesment.companyInformationModels.Add(company);
assesment.locationInformationModels.Add(location);
assesment.jobInformationModels.Add(job);
return assesment;
}, splitOn: "CompanyInformationId, LocationInformationId, JobInformationId");

Related

How to order by with related entity properties in EF core

Hello I want to sort my end result using related entity property which is in this case Locality. I got the keyword from client end as a string that includes column name and sort direction eg. "locality=asc" but when I do orderby with any parent entity properties it run fine however, the property with related entity gives me an error by saying that customer object does not have any locality property
here is my both class customer and Address
public class Customer : IEntity
{
public Guid Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public string Mobile { get; set; }
public Guid UserId { get; set; }
public DateTime DateCreated { get; set; }
public DateTime LastUpdated { get; set; }
[ForeignKey("Address")]
public Guid AddressId { get; set; }
public virtual Address Address { get; set; }
}
public class Address: IEntity
{
public Guid Id { get; set; }
public string Lat { get; set; }
public string Lon { get; set; }
public string Address1 { get; set; }
public string Address2 { get; set; }
public string Locality { get; set; }
}
Here I am trying to sort it with Address property like locality
int skip = (pageNum - 1) * pageSize;
if (skip < 0)
{
skip = 0;
}
searchTerm = searchTerm.ToLower();
var query = _context.Customers.Include(q => q.Address)
.Where(c => c.FirstName.ToLower().Contains(searchTerm)
|| c.LastName.ToLower().Contains(searchTerm)
|| c.Email.ToLower().Contains(searchTerm)
|| c.Mobile.ToLower().Contains(searchTerm));
//var sortOrderSplit = sortOrder.Split('=');
if (sortOrderSplit[0].ToLower() != "locality")
{
query = query.OrderByField("Email", "asc");
}
{
query = query.OrderByField("locality", "asc"); //that gives me an error because type is Address not Customer
}
var customers = query
.Skip(skip)
.Take(pageSize)
.ToList();
u want order by Locality ASC,right?
I think Class type of query is IEnumerable,so you can use lumbda expression.
because Locality is in Address Class,should follow the flow Customer => Address => Locality,not only search property Locality.
if (sortOrderSplit[0].ToLower() != "locality")
{
query = query.OrderBy(o => o.Email);
}
else
{
query = query.OrderBy(o => o.Address.Locality);
}
If your two entity classes have One-to-One relationship, you must add
public Customer Customer { get; set; }
into your Address class too.
Do this and try again.

Using Dapper with multiple inner joins

I am trying to use Dapper with the following sql string but I am unable to get it work:
string groupsStringDetailed = "SELECT SUSERGROUP.NAME, SUSERGROUP.DESCRIPTION, SPROGRAMS.PROGRAMNAME, SOBJECTS.NAME FROM ((SIDE.SADMIT SADMIT " +
"INNER JOIN SIDE.SOBJECTS SOBJECTS ON (SADMIT.PROGRAMID=SOBJECTS.PROGRAMID) AND (SADMIT.OBJECTID=SOBJECTS.ID)) " +
"INNER JOIN SIDE.SUSERGROUP SUSERGROUP ON SADMIT.GROUPID=SUSERGROUP.GROUPID) " +
"INNER JOIN SIDE.SPROGRAMS SPROGRAMS ON SOBJECTS.PROGRAMID=SPROGRAMS.ID " +
"WHERE SUSERGROUP.NAME NOT LIKE '%REPORT' ORDER BY SUSERGROUP.NAME, SPROGRAMS.PROGRAMNAME";
I have the following model classes:
public class SAdmit
{
public int GROUPID { get; set; }
public int OBJECTID { get; set; }
public int PROGRAMID { get; set; }
}
public class SObjects
{
public int ID { get; set; }
public int PROGRAMID { get; set; }
public string NAME { get; set; }
}
public class SPrograms
{
public int ID { get; set; }
public string PROGRAMNAME { get; set; }
}
public class SUserGroup
{
public int GROUPID { get; set; }
public string NAME { get; set; }
public string DESCRIPTION { get; set; }
public int VWLISTDEPTH { get; set; }
public int WDNBDAYHISTORY { get; set; }
public string RPDIRECTORY { get; set; }
public string SENDEREMAIL { get; set; }
public int CONNECTION_TIMEOUT { get; set; }
public int APPROVALSTATUS { get; set; }
}
I createad a custom group class hoping to map easier those models:
public class CustomSGroup
{
public SUserGroup Group { get; set; }
public SPrograms Programs { get; set; }
public SObjects Objects { get; set; }
}
I am trying to use Dapper to get results I want like this:
var output = await cnn.QueryAsync<CustomSGroup, SAdmit, SObjects, SPrograms, CustomSGroup>(groupsStringDetailed, (g, a, o, p) =>
{
a.PROGRAMID = o.PROGRAMID;
a.OBJECTID = o.ID;
a.GROUPID = g.Group.GROUPID;
o.PROGRAMID = p.ID;
return g;
}, splitOn: "PROGRAMID, OBJECTID, GROUPID, NAME");
but I am unable to see the big picture and what I am doing wrong because it throws an exception
"When using the multi-mapping APIs ensure you set the splitOn param if you have keys other than Id\r\nParameter name: splitOn"
I am able to use Dapper with a simpler (one) inner join sql string, but this one I cannot get it work.
I reviewed the code and I've come to the conclusion that you simply are not including the fields you need to split on. Add the following fields to your query (perhaps adding distinct labels for the types that share similar field names.
PROGRAMID, OBJECTID, GROUPID, NAME

ASP.NET MVC 5 Edit Action - How to write to Multiple DB Tables/Models

Is there any way to somehow combine the data from two models and THEN map them both to the same viewModel in the context of an edit action?
I have never had to update several tables at once in an edit action in ASP.NET MVC with Entity Framework 6.1.3. This is the layout:
I have a DB table called "Address" which has fields for StreetNumber, StreetName, City, State, ZipCode. It has a one-to-one relationship with another table called Bars. As in, a bar can only have one address and one address can only have one bar.
Because I am storing this data in two separate tables, I am having a very difficult time trying to successfully implement an Edit action which takes data from one form (BarForm) and should update both the Bar and Address database tables. See my code:
BarController
public ActionResult Edit(int id)
{
var bar = _context.Bars.SingleOrDefault(m => m.Id == id);
var address = _context.Addresses.SingleOrDefault(a => a.BarId == id);
//Make sure that the id actually exists:
if (bar == null)
{
return HttpNotFound();
}
var viewModel = Mapper.Map<Bar, BarFormViewModel>(bar, new BarFormViewModel());
if (address == null)
{
address = new Address();
}
Mapper.Map<Address, BarFormViewModel>(address, viewModel);
viewModel.IsNew = false;
return View("BarForm", viewModel);
}
[ValidateAntiForgeryToken]
public ActionResult Save(BarFormViewModel bar)
{
if (!ModelState.IsValid)
{
var viewModel = Mapper.Map<BarFormViewModel, BarFormViewModel>(bar, new BarFormViewModel());
viewModel.IsNew = false;
return View("BarForm", viewModel);
}
if (bar.Id == 0)
{
var newbar = Mapper.Map<BarFormViewModel, Bar>(bar);
newbar.LastUpdated = DateTime.UtcNow;
_context.Bars.Add(newbar);
var addressToAdd = Mapper.Map<BarFormViewModel, Address>(bar);
_context.Addresses.Add(addressToAdd);
}
else
{
var barInDb = _context.Bars.Single(b => b.Id == bar.Id);
var addressInDb = _context.Addresses.Single(a => a.BarId == bar.Id);
Mapper.Map<BarFormViewModel, Bar>(bar, barInDb);
Mapper.Map<BarFormViewModel, Address>(bar, addressInDb);
}
_context.SaveChanges();
return RedirectToAction("Index", "Bar");
}
Domain Models:
public class Bar
{
public int Id { get; set; }
public string Name { get; set; }
[Required]
public string GooglePlaceId { get; set; }
public string SundayDiscounts { get; set; }
public string MondayDiscounts { get; set; }
public string TuesdayDiscounts { get; set; }
public string WednesdayDiscounts { get; set; }
public string ThursdayDiscounts { get; set; }
public string FridayDiscounts { get; set; }
public string SaturdayDiscounts { get; set; }
[Display(Name = "Last Updated")]
public DateTime LastUpdated { get; set; }
}
public class Address
{
public int Id { get; set; }
public int? Number { get; set; }
public string StreetName { get; set; }
public string City { get; set; }
public string State { get; set; }
[Required]
public int ZipCode { get; set; }
public Bar Bar { get; set; }
public int BarId { get; set; }
}
View Model which includes both Address and Bar properties:
{
public class BarFormViewModel
{
public int? Id { get; set; }
public string Name { get; set; }
[Required]
[Display(Name = "Google Place ID")]
public string GooglePlaceId { get; set; }
[Display(Name = "Sunday Happy Hour Info:")]
public string SundayDiscounts { get; set; }
[Display(Name = "Monday Happy Hour Info:")]
public string MondayDiscounts { get; set; }
[Display(Name = "Tuesday Happy Hour Info:")]
public string TuesdayDiscounts { get; set; }
[Display(Name = "Wednesday Happy Hour Info:")]
public string WednesdayDiscounts { get; set; }
[Display(Name = "Thursday Happy Hour Info:")]
public string ThursdayDiscounts { get; set; }
[Display(Name = "Friday Happy Hour Info:")]
public string FridayDiscounts { get; set; }
[Display(Name = "Saturday Happy Hour Info:")]
public string SaturdayDiscounts { get; set; }
[Display(Name = "Last Updated")]
public DateTime? LastUpdated { get; set; }
//Address Model Info
public Address Address { get; set; }
public int? AddressId { get; set; }
[RegularExpression("([1-9][0-9]*)", ErrorMessage = "Must be a number")]
public int? Number { get; set; }
public string StreetName { get; set; }
public string City { get; set; }
public string State { get; set; }
[Required]
public int? ZipCode { get; set; }
public bool IsNew { get; set; }
}
The problem here is that I am getting an empty AddressId with this setup, which is causing an exception when the Save action gets run. This is because the BarForm view is getting passed a ViewModel which has been mapped from a Bar object and the Bar domain model actually has no Address information in it, since it is not the Address model/table.
Is there any way to somehow combine the data from both the Address and Bar models and THEN map them both to the same viewModel?
I keep getting a Sequence Contains no Elements error for this line in the Save action:
var addressInDb = _context.Addresses.Single(a => a.Id == bar.AddressId);
I also tried:
var addressInDb = _context.Addresses.Single(a => a.BarId == bar.Id);
Neither work. I understand what the error is saying and have also checked the actual HTML for my hidden Addressid field and it is blank... See code in my BarForm View:
#Html.HiddenFor(m => m.Id)
#Html.HiddenFor(m => m.AddressId)
#Html.AntiForgeryToken()
Remove the new BarFormViewModel() as the second parameter in your mapping calls as it is not necessary.
In your post action, inside your if statement that checks if the ModelState is valid and if bar.Id == 0, bar is already a view model, so no need to mapping.
And when you create your AutoMapper mapping, you must create a custom property mapping because the Address.Id property will not map automatically to the AddressId property as the name is not the same.
AutoMapper.Mapper.CreateMap<Address, BarFormViewModel>()
.ForMember(dest => dest.AddressId, o => o.MapFrom(source => source.Id));
And then do the same for the inverse mapping.

C# EF add data to model

I have those models
class Artist
{
public int Id { get; set; }
[StringLength(500)]
public string Name { get; set; }
[StringLength(50)]
public string LastName { get; set; }
public virtual ICollection<SimilarArtist> SimilarArtists { get; set; }
}
class SimilarArtist
{
public int Id { get; set; }
public int ArtistId { get; set; }
[ForeignKey("ArtistId")]
public Artist Artist { get; set; }
public int Similar_Artist_Id { get; set; }
}
So each artist have links to other 5 from the same table. When the migration generate database it made that stracture.
SELECT [Id]
,[Name]
,[LastName]
FROM [dbo].[Artists]
SELECT [Id]
,[ArtistId]
,[Similar_Artist_Id]
FROM [dbo].[SimilarArtists]
So when I do select the model it return this
var similar = _db.Artists.FirstOrDefault(x => x.Name == id).SimilarArtists.FirstOrDefault();
//similar.ArtistId
//similar.Id
//similar.Similar_Artist_Id
//similar.Artist //the object which return main artist
The question is how I can get in "var similar" not just Similar_Artist_Id but also name and lastname in the same request (without making requests by Similar_Artist_Id)
var similarId = model.SimilarArtists.FirstOrDefault().Id;
var artiest = _db.Artists.Where(x.Id = similarId);
or just:
similar.Artist.Name
Or if you want to be able to have strongly-type property such as similar.ArtistName, create a [NotMapped] getter property.
class SimilarArtist
{
public int Id { get; set; }
public int ArtistId { get; set; }
[ForeignKey("ArtistId")]
public Artist Artist { get; set; }
public int Similar_Artist_Id { get; set; }
[NotMapped]
public string ArtistName
{
get
{
return this.Artist.Name;
}
}
}
You can do
var similar = _db.Artists.Where(x => x.Name == id)
.Select(a => a.SimilarArtists.FirstOrDefault())
.FirstOrDefault();
This gives you the first SimilarArtists (with all of its properties) of the first Artists matching the predicate x.Name == id.

c#. EF entity sql. How to get entity with related objects?

I have made simple model for example.
public class Publisher
{
public int Id { get; set; }
public string Title { get; set; }
public Address Location { get; set; }
public virtual ICollection<Book> Books { get; set; }
}
public class Address
{
public string Country { get; set; }
public string City { get; set; }
public string Street { get; set; }
public string HouseNumber { get; set; }
}
public class Book
{
public int Id { get; set; }
public string Title { get; set; }
public string Author { get; set; }
public int LanguageId { get; set; }
public int? PublisherId { get; set; }
}
I need to get publishers with related books. I know how to do it using linq to entities. Is it possible to solve a problem using entity sql?
public class CatalogContext : DbContext {...}
public List<Publisher> GetByCity(string city)
{
var result = new List<Publisher>();
string queryString;
queryString = String.Format(#"SELECT VALUE row(a,b)
FROM CatalogContext.Publishers AS a
join CatalogContext.Books AS b on a.Id = b.PublisherId
WHERE a.Location.City = '{0}'", city);
var rows = ((IObjectContextAdapter)_context).ObjectContext.CreateQuery<DbDataRecord>(queryString).ToList();
return ???
}
Query returns required data but it's List<DbDataRecord> - list of pairs <publisher, book>. How to translate it to list of publishers with filled navigation property "Books"?
Is it possible to write query which directly returns List<Publisher>?
you can do the following:
var result = ObjectContext.Publishers.Include("Books").Include("Locations")
.Where(c => c.Location.City = "SOME_CITY").Select(c => c);
Include - basically joins the table.
Then you can drill down to books by doing the following:
var test = result[0].Books;
Why are you using direct sql command instead of Entity Framework code style?

Categories

Resources