EF Core Mapping Entities to Models - c#

I'm trying to efficiently map entities on to models.
My entities are:
public class ParentEntity
{
public int Id { get; set; }
public string Name { get; set; }
public ChildEntity Child { get; set; }
}
public class ChildEntity
{
public int Id { get; set; }
public string Name { get; set; }
}
and my models are:
public class ParentModel
{
public int Id { get; set; }
public string Name { get; set; }
public ChildModel Child { get; set; }
}
public class ChildModel
{
public int Id { get; set; }
public string Name { get; set; }
}
(In practice, there would be differences between these classes, but not here for simplification.)
I've written an extension method to do the mapping:
public static IQueryable<ParentModel> ToParentModel (this IQueryable<ParentEntity> parentEntities)
{
return parentEntities.Select(p => new ParentModel
{
Id = p.Id,
Name = p.Name,
Child = new ChildModel { Id = p.Child.Id, Name = p.Child.Name.ToLower()}
});
}
The ToLower() is there to highlight the problem.
I can run this with:
var parents = _context.Set<ParentEntity>().ToParentModel().ToArray();
The generated SQL is:
SELECT "p"."Id", "p"."Name", "c"."Id", lower("c"."Name") AS "Name"
FROM "Parents" AS "p"
LEFT JOIN "Children" AS "c" ON "p"."ChildId" = "c"."Id"
i.e. the lowercase processing is done in the database.
All good so far, except that the separation of concerns is not good. The code to initialize a ChildModel is in the same place as the code to initialize a ParentModel.
I try using a constructor in ChildModel:
public ChildModel(ChildEntity ent)
{
Id = ent.Id;
Name = ent.Name.ToLower();
}
and in the extension method:
return parentEntities.Select(p => new ParentModel
{
Id = p.Id,
Name = p.Name,
Child = new ChildModel (p.Child)
});
This works, but the generated SQL does not do contains a lower. The conversion to lowercase is done in the program.
Is there a way I can have by cake and eat it?
Can I still have my C# code converted to SQL, but still structure my C# code in a modular way?

Related

Reference navigation property not loading (Entity Framework)

I got 3 models: Human, Skill and HumanSkill. There is a many to many relationship between Human and Skill, the HumanSkill is the intermediary table between them.
My query to the database loads the collection of the intermediary table HumanSkill correctly, but does not load the reference navigation property Skill through which I want to load the Skill name (Human -> HumanSkill -> Skill -> Skill.name) using a query projection with select.
public IActionResult Preview(int humanId)
{
var currentHuman = this.db.Humans
.Where(x => x.Id == humanId)
.Select(r => new HumanPreviewViewModel
{
PrimaryData = r.PrimaryData,
// How should I write this line?
Skills = r.Skills.ToList(),
}).SingleOrDefault();
return View(currentResume);
}
Human model:
public class Human
{
public Human()
{
this.Skills = new HashSet<HumanSkill>();
}
public int Id { get; set; }
public virtual PrimaryData PrimaryData { get; set; }
public virtual ICollection<HumanSkill> Skills { get; set; }
}
HumanSkill model:
public class HumanSkill
{
public int Id { get; set; }
public int HumanId { get; set; }
public Human Human { get; set; }
public int SkillId { get; set; }
public Skill Skill { get; set; }
}
Skill model:
public class Skill
{
public Skill()
{
this.Humans = new HashSet<HumanSkill>();
}
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<HumanSkill> Humans { get; set; }
}
HumanPreviewViewModel:
public class HumanPreviewViewModel
{
public HumanPreviewViewModel()
{
}
public PrimaryData PrimaryData { get; set; }
public List<HumanSkill> Skills { get; set; }
}
}
How can I achieve this without using include?
If you use some data from Skills table in the Select, EF will perform the necessary joins to retrieve the data
var currentHuman = this.db.Humans
.Where(x => x.Id == humanId)
.Select(r => new HumanPreviewViewModel
{
PrimaryData = r.PrimaryData,
SkillNames = r.Skills.Select(hs => hs.Skill.Name).ToList(),
}).SingleOrDefault();
When projecting from entity to a view model, avoid mixing them. For example, do not have a view model contain a reference or set of entities. While it might not seem necessary, if you want a list of the skills with their ID and name in the HumanPreviewViewModel then create a serialize-able view model for the skill as well as the PrimaryData if that is another related entity. Where PrimaryData might be a one-to-one or a many-to-one the desired properties from this relation can be "flattened" into the view model.
[Serializable]
public class HumanPreviewViewModel
{
public Id { get; set; }
public string DataPoint1 { get; set; }
public string DataPoint2 { get; set; }
public List<SkillViewModel> Skills { get; set; }
}
[Serializable]
public class SkillViewModel
{
public int Id { get; set; }
public string Name { get; set; }
}
Then when you go to extract your Humans:
var currentHuman = this.db.Humans
.Where(x => x.Id == humanId)
.Select(r => new HumanPreviewViewModel
{
Id = r.Id,
DataPoint1 = r.PrimaryData.DataPoint1,
DataPoint2 = r.PrimaryData.DataPoint2,
Skills = r.Skills.Select(s => new SkillViewModel
{
Id = s.Skill.Id,
Name = s.Skill.Name
}).ToList()
}).SingleOrDefault();
The reason you don't mix view models and entities even if they share all of the desired fields is that your entities will typically contain references to more entities. When something like your view model gets sent to a Serializer such as to send to a client from an API or due to a page calling something an innocent looking as:
var model = #Html.Raw(Json.Encode(Model));
then the serializer can, and will touch navigation properties in your referenced entities which will trigger numerous lazy load calls.

Handling Nested Objects in Entity Framework

I am struggling a bit to wrap my head around Entity Framework and It's driving me crazy. I have an target object that I'd like to populate:
public class ApiInvitationModel
{
public int Id { get; set; }
public EventModel Event { get; set; }
public UserModel InvitationSentTo { get; set; }
public UserModel AttendingUser { get; set; }
}
The schemas of the above models are:
public class EventModel {
public int Id? { get; set; }
public string Name { get; set; }
public DateTime? StartDate { get; set; }
public DateTime? EndDate { get; set }
public OrganizationModel HostingOrganization { get; set; }
public Venue Venue { get; set; }
public string Price { get; set; }
}
public class UserModel {
public int Id? { get; set; }
public string Name { get; set; }
public string PhoneNumber { get; set; }
public string MobileNumber { get; set; }
public List<OrganizationModel> Organizations { get; set; }
}
public class OrganizationModel {
public int Id? { get; set; }
public stirng Name { get; set; }
public string Address { get; set; }
public UserModel PrimaryContact { get; set; }
}
The above schemas are simplified for the purpose of the question and are the models we intend to return via API.
The problem is the origin schemas in the database is very different and I'm trying to map the database objects to these objects via Entity Framework 6.
My attempted solution was to try and nest the models via a query but that didn't work and I'm not sure where to go from here besides making numerous calls to the database.
public List<ApiInvitationModel> GetInvitations(int userId) {
using (var entities = new Entities()) {
return entities.EventInvitations
.Join(entities.Users, invitation => invitiation.userId, user => user.id, (invitation, user) => new {invitation, user})
.Join(entities.Events, model => model.invitation.eventId, ev => ev.id, (model, ev) => new {model.invitation, model.user, ev})
.Join(entities.organization, model => model.user.organizationId, organization => organization.id, (model, organization) => new ApiInvitationModel
{
Id = model.invitation.id,
Event = new EventModel {
Id = model.event.id,
Name = model.event.name,
StartDate = model.event.startDate,
EndDate = model.event.endDate,
HostingOrganization = new OrganizationModel {
Id = model.invitation.hostingId,
Name = model.event.venueName,
Address = model.event.address,
PrimaryContact = new UserModel {
Name = model.event.contactName,
PhoneNumber = model.event.contactNumber,
}
}
...
},
InvitedUser = {
}
}
).ToList();
}
}
As you can see above, there's quite a bit of nesting going on but this doesn't work in Entity Framework 6 as far as I am aware. I keep getting the following errors:
"The type 'Entities.Models.API.UserModel' appears in two structurally incompatible initializations within a single LINQ to Entities query. A type can be initialized in two places in the same query, but only if the same properties are set in both places and those properties are set in the same order.",
Based on the above error, I assumed that each of the model initiatilizations would need to be the same (i.e. initializing the values as the same ApiInvitationModel in each join in the same order) but that produces the same error.
What would be the best approach to handling this, keepign in mind the source database doesn't have foreign keys implemented?

LINQ Projection and loading child objects

Having an issue with projection and getting child objects to load. The following is simplified code to represent the logic I'm trying to implement, not the actual code.
public class TicketItem
{
public int TicketItemId { get; set; }
public string TicketReason { get; set; }
public Station Station { get; set; }
public TicketOwner TicketOwner { get; set; }
}
public class Station
{
public int StationId { get; set; }
public string Name { get; set; }
}
public class TicketOwner
{
public int TicketOwnerId { get; set; }
public Employee Employee { get; set; }
public Organization Organization { get; set; }
}
public class Employee
{
public int EmployeeId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class Organization
{
public int OrganizationId { get; set; }
public string Code { get; set; }
public string Name { get; set; }
}
public class CommonReasons
{
public int CommonReasonId { get; set; }
public string Reason { get; set; }
}
public TicketItem GetById(int id)
{
var query = from i in _dataContext.TicketItems
.Include("Station")
.Include("TicketOwner.Employee")
.Include("TicketOwner.Organization")
join r in _dataContext.CommonReasons on i.TicketReason equals r.CommonReasonId.ToString() into r1
from r2 in r1.DefaultIfEmpty()
where i.TicketItemId == id
select new TicketItem {
TicketItemId = i.TicketItemId,
TicketReason = r2.Reason == null ? i.Reason : r2.Reason,
Station = i.Station,
TicketOwner = i.TicketOwner
};
return query
.AsNoTracking()
.FirstOrDefault();
}
Most the code is self-explanatory. The part that is indirectly causing the trouble would be the relationship between TicketItem.TicketReason property (a string) and the CommonReasons entity. From the user interface side, the end-user has an input field of "Reason", and they can select from "common" reasons or input an adhoc reason. They original developer chose to have the TicketReason property contain either the key ID from the CommonReasons table (if the user selected from drop-down) or the adhoc reason typed in.
So, to handle this logic in the linq query, the only way I have found is to do a left join between TicketItem.TicketReason and CommonReasons.CommonReasonId, then use projection to modify the TicketReason column returning either the common reason text or adhoc text. If there is a different way to do this that would get me around the trouble I'm having with projection/include, I'm all ears.
For the "reason" logic, this query works, returning the proper text. The trouble is that none of the "grand-child" objects are returning, i.e. TicketItem.TicketOwner.Employee, TicketItem.TicketOwner.Organization. How do I get those objects to return also?
Changing the structure of the tables would be an absolute last resort, just based on the amount of code that would have to change. There are other spots in the code that are using the above logic but don't need the child objects.
Any help would be appreciated. Hope I've explained enough.

How to join two collections using lookup operator in C# Mongodb strongly typed driver

I am using the official C# MongoDb strongly typed driver version 2.8.0 to interact with MongoDB.
Consider the following classes:
public class Author {
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string BirthDate { get; set; }
public string ScientificDegree { get; set; }
}
public class Book
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
public string AuthorId {get; set;}
public string Title { get; set; }
public int PublishYear { get; set; }
public string Content { get; set; }
public bool IsVerified { get; set; }
public int DownloadCount {get; set; }
}
How to join (one to one) the two collections by the Id field in the author document and the AuthorId field in the Book document without using Linq and by using the lookup operator and the aggregation framwork.
If you use the peek definition of Visual Studio, it will show what Lookup to expect, as shown in the image below, surrounded with red.
The first Lookup is an extension of the IMongoCollection. It executes in the context of the collection and requires a foreign key collection as the first parameter, then the local field on which relation is established, the foreign field that composes the relation, and finally the result type. Unfortunately, the result type cannot be an anonymous type (or I did not discover how to be anonymous?). As always is expected that from the foreign collection will be returned more than one element, so, the operator always expects an array to be returned.
In your case, the result will be as shown in the snippet below. You should create 'LookedUpBooks' class as well.
var result = await collBooks.Aggregate()
.Lookup<Books, Authors, LookedUpBooks>(collAuthors,
x => x.AuthorId,
y => y.Id,
y => y.LastName
).ToListAsync();
public class LookedUpBooks
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
public string Title { get; set; }
// Add more properties as you need
public IEnumerable<Authors> InnerAuthors { get; set; }
}
See more on How to Program with MongoDB Using the .NET Driver
you can do one-to-one, one-to-many, many-to-many relationships quite easily with the MongoDB.Entities wrapper library. the following is how you do one-to-one relationships with two different collections.
using System.Linq;
using MongoDB.Entities;
namespace Library
{
public class Author : Entity
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class Book : Entity
{
public string Title { get; set; }
public One<Author> MainAuthor { get; set; } // one-to-one relationship
}
class Program
{
static void Main(string[] args)
{
new DB("library");
var author1 = new Author
{
FirstName = "Eckhart",
LastName = "Tolle"
};
author1.Save();
var book1 = new Book
{
Title = "The Power Of Now",
MainAuthor = author1.ToReference()
};
book1.Save();
var powerOfNow = DB.Collection<Book>()
.Where(b => b.Title.Contains("Now"))
.FirstOrDefault();
var eckhartTolle = powerOfNow.MainAuthor.ToEntity();
}
}
}

Converting infinitely nested objects in .NET Core

EDIT: I originally worded this question very poorly, stating the problem was with JSON serialization. The problem actually happens when I'm converting from my base classes to my returned models using my custom mappings. I apologize for the confusion. :(
I'm using .NET Core 1.1.0, EF Core 1.1.0. I'm querying an interest and want to get its category from my DB. EF is querying the DB properly, no problems there. The issue is that the returned category has a collection with one interest, which has one parent category, which has a collection with one interest, etc. When I attempt to convert this from the base class to my return model, I'm getting a stack overflow because it's attempting to convert the infinite loop of objects. The only way I can get around this is to set that collection to null before I serialize the category.
Interest/category is an example, but this is happening with ALL of the entities I query. Some of them get very messy with the loops to set the relevant properties to null, such as posts/comments.
What is the best way to address this? Right now I'm using custom mappings that I wrote to convert between base classes and the returned models, but I'm open to using any other tools that may be helpful. (I know my custom mappings are the reason for the stack overflow, but surely there must be a more graceful way of handling this than setting everything to null before projecting from base class to model.)
Classes:
public class InterestCategory
{
public long Id { get; set; }
public string Name { get; set; }
public ICollection<Interest> Interests { get; set; }
}
public class Interest
{
public long Id { get; set; }
public string Name { get; set; }
public long InterestCategoryId { get; set; }
public InterestCategory InterestCategory { get; set; }
}
Models:
public class InterestCategoryModel
{
public long Id { get; set; }
public string Name { get; set; }
public List<InterestModel> Interests { get; set; }
}
public class InterestModel
{
public long Id { get; set; }
public string Name { get; set; }
public InterestCategoryModel InterestCategory { get; set; }
public long? InterestCategoryId { get; set; }
}
Mapping functions:
public static InterestCategoryModel ToModel(this InterestCategory category)
{
var m = new InterestCategoryModel
{
Name = category.Name,
Description = category.Description
};
if (category.Interests != null)
m.Interests = category.Interests.Select(i => i.ToModel()).ToList();
return m;
}
public static InterestModel ToModel(this Interest interest)
{
var m = new InterestModel
{
Name = interest.Name,
Description = interest.Description
};
if (interest.InterestCategory != null)
m.InterestCategory = interest.InterestCategory.ToModel();
return m;
}
This is returned by the query. (Sorry, needed to censor some things.)
This is not .NET Core related! JSON.NET is doing the serialization.
To disable it globally, just add this during configuration in Startup
services.AddMvc()
.AddJsonOptions(options =>
{
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
}));
edit:
Is it an option to remove the circular references form the model and have 2 distinct pair of models, depending on whether you want to show categories or interests?
public class InterestCategoryModel
{
public long Id { get; set; }
public string Name { get; set; }
public List<InterestModel> Interests { get; set; }
public class InterestModel
{
public long Id { get; set; }
public string Name { get; set; }
}
}
public class InterestModel
{
public long Id { get; set; }
public string Name { get; set; }
public InterestCategoryModel InterestCategory { get; set; }
public class InterestCategoryModel
{
public long Id { get; set; }
public string Name { get; set; }
}
}
Note that each of the models has a nested class for it's child objects, but they have their back references removed, so there would be no infinite reference during deserialization?

Categories