Automapper for DTO to Domain - ef-code-first

I have domain like this
public class Person
{
public int id { get; set; }
public string name { get; set; }
public Gender gender { get; set; }
}
public class Gender
{
public int id { get; set; }
public string description { get; set; }
}
the Gender property of the person is lookup... which mean from the UI the user select the Gender as drop-down
public class EmployeeDTO
{
public int personid { get; set; }
public string name { get; set; }
public int genderid { get; set; }
}
so how do I setup my AutoMapper... to convert the DTO to Domain and vice verse?

Actually, AutoMapper provides object flattening out-of-the-box, so this will be done automatically for you.
Person person = new Person();
Mapper.CreateMap<Person, EmployeeDTO>()
.ForMember(dest => dest.personid, opt =>
opt.MapFrom(src => src.id)); // this line is only because I noticed different property names (id vs personid)
EmployeeDTO employeeDTO = Mapper.Map<EmployeeDTO>(person);
employeeDTO.genderid.ShouldEqual(person.gender.id);
If you use the convention OuterProperty.InnerProperty in your complex domain object, and the types match properly, AutoMapper will flatten it down to OuterPropertyInnerProperty in the destination object. You can read all about that here: http://github.com/AutoMapper/AutoMapper/wiki/Flattening

Related

EF Core 3.18 get sum and count from related table

I have a web api where I am trying to get sum and count of a related table. Using .net core 3 and EF Core 3.1.8.
This is what I have tried:
_context.Books
.Include(r => r.BookCategories)
.Include(r => r.Resources)
.Include(r => r.Ratings.GroupBy(g => g.Bookid).Select(s => new { SumAllVotes = s.Sum(item => item.Rating) }))
.ToListAsync();
But I just get an error message. (see below).
I find it difficault debugging with EF Core as I dont know where it is going wrong. Have been trying a couple of hours, but whatever I write I get the same error message.
Thought maybe you guys were able to see what was wrong.
What I want
I am trying to get Sum of all Rating inside table Ratings.
Rating contains only 0 or 1. And I am trying to sum ratings on each bookid. I wanted to have it in this class public int SumAllVotes { get; set; }.
Because I list out all Books...and one of the properties will then be SumAllVotes. (And also CountAllVotes, when I have finished this problem).
By the end I will have a SumAllVotes and CountAllVotes and can calculate the percentage of how many have pressed "1".
Error message:
An unhandled exception occurred while processing the request.
InvalidOperationException: Lambda expression used inside Include is
not valid.
Microsoft.EntityFrameworkCore.Query.Internal.NavigationExpandingExpressionVisitor.ProcessInclude(NavigationExpansionExpression
source, Expression expression, bool thenInclude)
What I have tried:
[HttpGet]
public async Task<ActionResult<IEnumerable<Books>>> GetBooks()
{
Guid userid = Guid.Parse(this.User.FindFirst(ClaimTypes.NameIdentifier).Value);
return await _context.Books
.Include(r => r.BookCategories)
.Include(r => r.Resources)
.Include(r => r.Ratings.GroupBy(g => g.Bookid).Select(s => new { SumAllVotes = s.Sum(item => item.Rating) }))
.ToListAsync();
}
Books and Ratings are defined as -
public partial class Books
{
public Books()
{
Bookmarks = new HashSet<Bookmarks>();
Comments = new HashSet<Comments>();
Favourites = new HashSet<Favourites>();
BookCategories = new HashSet<BookCategories>();
Resources = new HashSet<Resources>();
Ratings = new HashSet<Ratings>();
}
public int Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public byte Scaleoffun { get; set; }
public byte Scaleoflearning { get; set; }
public int? Goal { get; set; }
public int? Secondgoal { get; set; }
public int? Thirdgoal { get; set; }
public int? Subjectid { get; set; }
public int? Categoryid { get; set; }
public string Language { get; set; }
public string Estimatedtime { get; set; }
public string Image { get; set; }
public int? File { get; set; }
public int? Ownerid { get; set; }
public DateTime Createdon { get; set; }
public DateTime? Lastmodifiedon { get; set; }
public string Active { get; set; }
public string Url { get; set; }
public Guid Userid { get; set; }
public byte? Grade { get; set; }
[NotMapped]
public int SumAllVotes { get; set; }
[NotMapped]
public int CountAllVotes { get; set; }
public virtual Categories Category { get; set; }
public virtual Curriculum GoalNavigation { get; set; }
public virtual Users Owner { get; set; }
public virtual Curriculum SecondgoalNavigation { get; set; }
public virtual Subjects Subject { get; set; }
public virtual Curriculum ThirdgoalNavigation { get; set; }
public virtual ICollection<Bookmarks> Bookmarks { get; set; }
public virtual ICollection<Comments> Comments { get; set; }
public virtual ICollection<Favourites> Favourites { get; set; }
public virtual ICollection<BookCategories> BookCategories { get; set; }
public virtual ICollection<Resources> Resources { get; set; }
public virtual ICollection<Ratings> Ratings { get; set; }
}
public partial class Ratings
{
public int Id { get; set; }
public int? Bookid { get; set; }
public string Type { get; set; }
public Int16? Rating { get; set; }
public Guid Userid { get; set; }
public string Subject { get; set; }
public DateTime Createdon { get; set; }
public DateTime? Modifiedon { get; set; }
public byte? Active { get; set; }
public virtual Books Book { get; set; }
//public virtual Users User { get; set; }
}
These are some other solutions I have tried, but got the same error message:
.Include(r=> r.Ratings.Sum(i=>i.Rating))
and
.Include(r => new { m = r.Ratings.GroupBy(g => g.Bookid) })
You don't need to group child entities by parent's Id. When you Include one-to-many child entities, they are added to their parent's child list, and hence grouped by their parent's identity, based on the relationship between them. All you need to do is tell EF what values you want from that child list.
Change your query to -
_context.Books
.Include(r => r.BookCategories)
.Include(r => r.Resources)
.Include(r => r.Ratings)
.Select(p => new
{
// set ALL the primitive properties from Books entity
Id = p.Id,
Title = p.Title,
// etc ...
// set the computed properties
CountAllVotes = p.Ratings.Count,
SumAllVotes = p.Ratings.Sum(x => x.Rating)
// set the related entities
BookCategories = p.BookCategories,
Resources = p.Resources
})
.ToListAsync();
AutoMapper has a ProjectTo method that generates the required query and does the projection (the Select part) automatically. You can use that to avoid the hassle of setting all those properties manually.
I suggest you don't use Include with Select. Read article how to make queries with Projection (Select). Note, that Rating.Rating is nullable and you need to handle this. Here is a possible code sample:
var view = await _context.Books
.Where(your condition)
.Select(item => new
{
//Todo: fill other props
SumAllVotes = item.Ratings.Sum(rating => (Int16?) rating.Rating),
CountAllVotes = item.Ratings.Count,
})
.ToListAsync()

ServiceStack AutoQuery join use

After reading the documentation, I am not sure but I have come to the conclusion that when creating QueryDb, you cannot choose the columns to join by? And I am under the impression, you must have DTO object to copy to? You cannot copy to a regular object or a dynamic object?
public class SampleAutoQueryDb : QueryDb<MailResponseDetailOrm, object>, ILeftJoin<MailResponseDetailOrm, MailResponseOrm> { }
Can anyone provide any insight on joining my MailResponseOrm to MailResponseDetailOrm. MailResponseDetailOrm has 5 fields namely the Email address. And I would like MailResponseOrm to be joined to it by Email as well. I also, for good measure do not want to alter either columnname. Would I have to create a custom implementation or a service to do this?
UPDATE
Here is my code as posted below:
[Alias("MailReportsDetail")]
public class MailResponseDetailOrm
{
public string Email { get; set; }
public int ID { get; set; }
[Alias("RespDate")]
public DateTime? AddedDateTime { get; set; }
[Alias("DLReport")]
public string Action { get; set; }
public string ActionDetail { get; set; }
public string IP { get; set; }
public string UserAgent { get; set; }
public string EmailReferrer { get; set; }
}
[Alias("MailReports")]
public class MailResponseOrm
{
public int ID { get; set; }
public string Email { get; set; }
public string Address1 { get; set; }
public string Address2 { get; set; }
public string City { get; set; }
public string Company { get; set; }
public string Contact { get; set; }
public string Country { get; set; }
[Alias("LastMail")]
public DateTime? ModifiedDateTime { get; set; }
[Alias("LastReport")]
public string Action { get; set; }
public DateTime? OptOut { get; set; }
public string Part { get; set; }
public string Phone { get; set; }
public string PostalCode { get; set; }
public string Source { get; set; }
public string State { get; set; }
public string Title { get; set; }
#region Obsolete
[Obsolete]
public string Class { get; set; }
[Obsolete]
public string IP { get; set; }
#endregion
}
public class SampleAutoQueryDb : QueryDb<MailResponseDetailOrm> { }
public class MyQueryServices : Service
{
public IAutoQueryDb AutoQuery { get; set; }
// Override with custom implementation
public object Any(SampleAutoQueryDb query)
{
var q = AutoQuery.CreateQuery(query, base.Request);
q.Join<MailResponseDetailOrm, MailResponseOrm>((x, y) => x.Email == y.Email)
// .Select<MailResponseDetailOrm, MailResponseOrm>((x, y) => new { x.ID, y.Email })
;
return AutoQuery.Execute(query, q);
}
}
Joins in AutoQuery needs to use OrmLite's Joins Reference conventions and all AutoQuery Services results are returned in a Typed DTO, which by default is the table being queried or you can use the QueryDb<From,Into> base class to return a custom result of columns from multiple joined tables.
You would need to use a Custom AutoQuery Implementation or your own Service implementation if you need customizations beyond this, e.g:
public class SampleAutoQueryDb : QueryDb<MailResponseDetailOrm> { }
public class MyQueryServices : Service
{
public IAutoQueryDb AutoQuery { get; set; }
// Override with custom implementation
public object Any(SampleAutoQueryDb query)
{
var q = AutoQuery.CreateQuery(query, base.Request);
q.Join<MailResponseDetailOrm,MailResponseOrm>((x, y) => x.Email == y.Email);
return AutoQuery.Execute(query, q);
}
}
// The query to join 2 objects on field names not specifically set in the class.
var q = Db.From<MailResponseDetailOrm>().Join<MailResponseDetailOrm>(x,y) => x.Email = y.Email);
// Run the query
var results = Db.Select(q);

How to include multiple child objects?

I'm playing with new ASP.Net 5.0 WebApi and strugling to understand how to return more then one child object, or child of the child.
Lets say I have 4 classes:
public class Car
{
public int Id { get; set; }
public string Name { get; set; }
public int TypeId { get; set; }
public int ColourId { get; set; }
public virtual Type Type { get; set; }
public virtual Colour Colour { get; set; }
}
public class Type
{
public int Id { get; set; }
public string Name { get; set; }
public int TypeGroupId { get; set; }
public virtual TypeGroup TypeGroup { get; set; }
}
public class Colour
{
public int Id { get; set; }
public string Name { get; set; }
}
public class TypeGroup
{
public int Id { get; set; }
public string Name { get; set; }
}
And would like to return all the data for the car including Type, Colour, and even TypeGroup of the Type. How do I Do it?
When I do like this it includes only Type:
[HttpGet]
public IEnumerable<Car> Get()
{
return _dbContext.Cars.Include(c => c.Type);
}
This is my setup in Startup.cs:
services.AddMvc().AddJsonOptions(options =>
{
options.SerializerSettings.ContractResolver =
new CamelCasePropertyNamesContractResolver();
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
});
Is it possible to set to return every child object and grandchild and etc?
Many thanks
You can turn off lazy loading for all entities by using the following in your DbContext class (place this in the constructor):
this.Configuration.LazyLoadingEnabled = false;
This will disable it for all entities - so be wary of this and watch for performance issues.
Another way you can load all the entities for a particular class is to remove the virtual keyword from the property declarations.

Entity Framework Code First Foreign Key Columnname inheritence

Entity Framework code first (v6) creates a columnname in the database that I don't like. In tablename SharepointMappings it adds columnname: 'SharepointDestination_DestinationId' (foreign key).
It also generates a columnname SharepointDestinationId.
I would like to have 1 column, a foreign key, with the name 'SharepointDestinationId'.
My model looks like this:
public class Destination
{
public int DestinationId { get; set; }
}
public class SharepointDestination : Destination
{
public string UserName { get; set; }
public string Password { get; set; }
public string Domain { get; set; }
public string SiteUrl { get; set; }
public string DocumentLibraryName { get; set; }
public List<SharepointMapping> Mappings { get; set; }
}
public class SharepointMapping
{
public int SharepointMappingId { get; set; }
public string SourceFieldName { get; set; }
public string DestinationFieldName { get; set; }
//[ForeignKey("SharepointDestination")]
public int SharepointDestinationId { get; set; }
//[ForeignKey("SharepointDestinationId")]
public virtual SharepointDestination SharepointDestination { get; set; }
}
//.....
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// To use TPT inheritence
modelBuilder.Entity<SharepointDestination>().ToTable("SharepointDestinations");
//modelBuilder.Entity<SharepointMapping>()
// .HasRequired(m => m.SharepointDestination)
// .WithMany(d => d.Mappings)
// .HasForeignKey(m => m.SharepointDestinationId)
// .WillCascadeOnDelete(true);
}
It doesn't matter if i leave or add the attribute ForeignKey and it also doesn't matter if i make properties virtual or not. Completely deleting both properties on SharepointMapping or giving them a complete other name has no consequences.
I think this has something to do with the inheritence structure. Because it's 'only' a 1-n mapping.
How should I configure EF to have only 1 column with the name 'SharepointDestinationId' which should be a foreign key? (and also have the navigation property and DestinationId property on the SharepointMapping class)
Since the key of SharepointDestination is DestinationId, EF can't automatically figure it out. You could go with the annotation:
[ForeignKey("DestinationId")]
public virtual SharepointDestination SharepointDestination { get; set; }
and remove this:
[ForeignKey("SharepointDestination")]
public int SharepointDestinationId { get; set; }
The fluent should work as well if you comment out the annotation:
modelBuilder.Entity<SharepointMapping>()
.HasRequired(m => m.SharepointDestination)
.WithMany(d => d.Mappings)
.HasForeignKey(m => m.DestinationId)
.WillCascadeOnDelete(true);
The ForeignKey attribute is expecting a property name, not a table column name.
Really, you should be able to do this without any attributes.
The following should work:
public class Parent
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Child
{
public int Id { get; set; }
public string Name { get; set; }
public virtual Parent Parent { get; set; }
public int ParentId { get; set; }
}

LinqPad Query to Visual Studio - how to use a nested query to populate a viewmodel

This is a follow up to an earlier question.
I want to populate a ViewModel, which has 3 properties, and one list of Occ class (which also has 3 properties.
public class RatesViewModel
{
public string TypeName { get; set; }
public long TypeID { get; set; }
public int TypeCount { get; set; }
public virtual IQueryable<Occ> Occs { get; set; }
}
public class Occ
{
public string occ { get; set; }
public decimal ratetocharge { get; set; }
public int numOfOcc { get; set; }
public virtual RatesViewModel RatesViewModel { get; set; }
}
When I run the following Linq query in LinqPad:
var rooms = tblRoom
.GroupBy(p => p.tblType)
.Select(g => new
{
TypeName = g.Key.type_name,
TypeID = g.Key.type_id,
TypeCount = g.Count(),
Occs = rates.Where(rt => rt.type_id == g.Key.type_id &&
(
(rt.type_id == g.Key.type_id)
))
.GroupBy(rt => rt.occ)
.Select(proj => new
{
occ = proj.Key,
ratetocharge = proj.Sum(s => s.rate),
numOfOcc = proj.Count()
})
});
rooms.Dump();
...as before, it correctly returns the data model I'm looking for:
...and when I click on Occs it drills down into the Occs class:
The complete view in LinqPad is:
My query in Visual Studio is:
var rooms = dbr.Rooms
.GroupBy(p => p.RoomTypes).Select(g => new RatesViewModel
{
TypeName = g.Key.type_name,
TypeID = g.Key.type_id,
TypeCount = g.Count()
,
Occs = db.Rates.Where(rt => rt.type_id == g.Key.type_id &&
(
(rt.type_id == g.Key.type_id)
))
.GroupBy(rt => rt.occ)
.Select(proj => new Occ
{
occ = proj.Key,
ratetocharge = proj.Sum(s => s.rate),
numOfOcc = proj.Count()
})
})
.ToList();
However when running this, I get an error:
The specified LINQ expression contains references to queries that are associated with different contexts.
I think I understand the error - but I'm not sure how to separate the query into 2 separate queries, and then join those query results together again to get my original results set.
My model classes are:
public class Rates
{
public int id { get; set; }
public long type_id { get; set; }
public DateTime ratedate { get; set; }
public decimal rate { get; set; }
public string occ { get; set; }
public List<RoomType> Type { get; set; }
}
public class Rental
{
[Key]
public long rental_id { get; set; }
public long room_id { get; set; }
public DateTime check_in { get; set; }
public DateTime check_out { get; set; }
public virtual Room Room { get; set; }
}
public class Room
{
[Key]
public long room_id { get; set; }
public long type_id { get; set; }
public virtual RoomType RoomTypes { get; set; }
public virtual ICollection<Rental> Rentals { get; set; }
}
public class RoomType
{
[Key]
public long type_id { get; set; }
public string type_name { get; set; }
public IQueryable<Rates> Rates { get; set; }
public virtual ICollection<Room> Room { get; set; }
}
Can anyone help either review my query or models, so it works with one query, or show me how to separate the query into two, and then combine the result sets?
Thank you,
Mark
apitest.Models.RoomContext' does not contain a definition for 'Rates'...
(your comment on hydr's answer)
Well, there you go: not only two different context instances but two different context classes. I suspect your linqpad query was directly against the database connection, which means it used one linq-to-sql DataContext (created on the fly).
You need to use one context class (and one instance of it) in your query. And connect to it in Linqpad to make sure you test the same query provider as Visual Studio.
dbr and db seem to be two different instances of the same context. But in one query you should only use one context. So I would suggest the following:
Occs = dbr.Rates.Where(rt => rt.type_id == g.Key.type_id && ....
If this doesn't help can you quote the lines where you initialize the contexts?

Resources