Adapt navigational property using Mapster - linq

I have a Post class with a User property in it. When I try to get all Posts, I also want to map the User to a UserDto object.
public class Post {
public Guid Id {get; set;}
public string Content {get;set;}
public User User {get; set;}
}
var result = await _ctx.Posts.Include(u => u.User.Adapt<UserDto>()).ToListAsync()
Adapting inside the Include is throwing this error:
Lambda expression used inside Include is not valid

It seems you are mixing up Include because Entity Framework and Mapster both have that function. The Include that you showed us belongs to Entity Framework : https://learn.microsoft.com/en-us/ef/core/querying/related-data#eager-loading
So, first you need to retrieve data with using Include as follows:
var result = await _ctx.Posts.Include(u => u.User).ToListAsync();
On the other hand you need to set mapster config:
TypeAdapterConfig<Post, PostDto>.NewConfig()
.PreserveReference(true);
TypeAdapterConfig<User, UserDto>.NewConfig()
.PreserveReference(true);
See for nested mapping in Mapster:
https://github.com/MapsterMapper/Mapster/wiki/Config-for-nested-mapping
Thus you can get PostDto which includes UserDto:
var postDto = result.Adapt<PostDto>();

Related

Is there any way to pass a whole model via html.actionlink in ASP.NET MVC 3?

How do I pass a whole model via html.actionlink or using any other method except form submission? Is there any way or tips for it?
Though it's not advisable in complex cases, you can still do that!
public class QueryViewModel
{
public string Search { get; set; }
public string Category { get; set; }
public int Page { get; set; }
}
// just for testing
#{
var queryViewModel = new QueryViewModel
{
Search = "routing",
Category = "mvc",
Page = 23
};
}
#Html.ActionLink("Looking for something", "SearchAction", "SearchController"
queryViewModel, null);
This will generate an action link with href like this,
/SearchController/SearchAction?Search=routing&Category=mvc&Page=23
Here will be your action,
public ViewResult SearchAction(QueryViewModel query)
{
...
}
No, you cannot pass entire complex objects with links or forms. You have a couple of possible approaches that you could take:
Include each individual property of the object as query string parameters (or input fields if you are using a form) so that the default model binder is able to reconstruct the object back in the controller action
Pass only an id as query string parameter (or input field if you are using a form) and have the controller action use this id to retrieve the actual object from some data store
Use session
You could use javascript to detect a click on the link, serialize the form (or whatever data you want to pass) and append it to your request parameters. This should achieve what you're looking to achieve...

How to Save Navigation property in ASP.NET MVC3 using Code first

i have been using code first technique in ASP.NET MVC3 app. here it is very basic issue i.e. how to update a navigation property. following is detailed code.
public class Destination
{
public int ID {get;set;}
// some other properties
public Country {get;set;}
}
public class Country
{
int ID {get;set;}
string Name {get;set;}
}
//i have simple structure as above. when i go to update destination entity. Country is not getting updated.even i tried following:
_db.Entry(Destination.Country).State = System.Data.EntityState.Modified;
_db.Entry(Destination).State = System.Data.EntityState.Modified;
//_db.ChangeTracker.DetectChanges();
_db.SaveChanges();
Secondly when i go for Add it works fine. is there any need to have foriegnKey relationship explicityly required?
You can add entity in three ways:
Calling the Add() method on DbSet. This puts the entity into the Added state, meaning that it will be inserted into the database the next time that SaveChanges() is called.
context.Destination.Add(yourDestination);
Changing its state to Added.
context.Entry(yourDestination).State = EntityState.Added;
Adding a new entity to the context by hooking it up to another entity that is already being tracked
context.Destination.Country.Add(yourCountry);
Regards.

Show display values for a foreign key property in a model

I have a model that looks kinda like this:
public class Notes
{
public int NoteID {get; set;}
public string Note {get; set;}
public int CustomerID {get; set;}
{
On the Notes Details view, I would like to be able to show Customer Name instead of CustomerID. Obviously if this were a Create or Edit view I use a dropdown list.
However I am not sure how to show the value and not the ID in a read only Details view.
Thanks!
Code First is mainly... code, not Database logic.
So, instead of having the Foreign Keys (like CustomerID) in your models (it's also possible, and sometimes needed, but not always), you'll be more confortable having a reference property
public virtual Customer Customer {get;set;}
So in your view having Notes as Model, you could simply use
#Html.DisplayFor(m => m.Customer.Name);
When you retrieve your Notes entity, don't forget to include the related entities / properties needed for your View / ViewModel (I let you read about lazy loading)
You can try this:
var applicationDbContext = _context.Notes.Include(n => n.Custumer);
return View(await applicationDbContext.ToListAsync());
This code must be in your controller, in the Index method or whatever you named it.
And this is in view.
#Html.DisplayFor(modelItem => item.Customer.Name)

Model binding in the controller when form is posted - navigation properties are not loaded automatically

I'm using the Entity Framework version 4.2. There are two classes in my small test app:
public class TestParent
{
public int TestParentID { get; set; }
public string Name { get; set; }
public string Comment { get; set; }
public virtual ICollection<TestChild> TestChildren { get; set; }
}
public class TestChild
{
public int TestChildID { get; set; }
public int TestParentID { get; set; }
public string Name { get; set; }
public string Comment { get; set; }
public virtual TestParent TestParent { get; set; }
}
Populating objects with data from the database works well. So I can use testParent.TestChildren.OrderBy(tc => tc.Name).First().Name etc. in my code.
Then I built a standard edit form for my testParents. The controller look like this:
public class TestController : Controller
{
private EFDbTestParentRepository testParentRepository = new EFDbTestParentRepository();
private EFDbTestChildRepository testChildRepository = new EFDbTestChildRepository();
public ActionResult ListParents()
{
return View(testParentRepository.TestParents);
}
public ViewResult EditParent(int testParentID)
{
return View(testParentRepository.TestParents.First(tp => tp.TestParentID == testParentID));
}
[HttpPost]
public ActionResult EditParent(TestParent testParent)
{
if (ModelState.IsValid)
{
testParentRepository.SaveTestParent(testParent);
TempData["message"] = string.Format("Changes to test parents have been saved: {0} (ID = {1})",
testParent.Name,
testParent.TestParentID);
return RedirectToAction("ListParents");
}
// something wrong with the data values
return View(testParent);
}
}
When the form is posted back to the server the model binding appears to be working well - i.e. testParent looks okay (id, name and comment set as expected). However the navigation property TestChildren remains at NULL.
This I guess is not sooo surprising since the model binding merely extracts the form values as they were sent from the browser and pushes them into an object of the TestParent class. Populating testParent.TestChildren however requires an immediate roundtrip to the database which is the responsibility of the Entity Framework. And EF probably doesn't get involved in the binding process.
I was however expecting the lazy loading to kick in when I call testParent.TestChildren.First(). Instead that leads to an ArgumentNullException.
Is it necessary to tag an object in a special way after model binding so that the Entity Framework will do lazy loading? How can I achieve this?
Obviously I could manually retrieve the children with the second repository testChildRepository. But that (a) doesn't feel right and (b) leads to problems with the way my repositories are set up (each using their own DBContext - which is an issue that I haven't managed to come to terms with yet).
In order to get lazy loading for your child collection two requirements must be fulfilled:
The parent entity must be attached to an EF context
Your parent entity must be a lazy loading proxy
Both requirements are met if you load the parent entity from the database through a context (and your navigation properties are virtual to allow proxy creation).
If you don't load the entity from the database but create it manually you can achieve the same by using the appropriate EF functions:
var parent = context.TestParents.Create();
parent.TestParentID = 1;
context.TestParents.Attach(parent);
Using Create and not new is important here because it creates the required lazy loading proxy. You can then access the child collection and the children of parent with ID = 1 will be loaded lazily:
var children = parent.TestChildren; // no NullReferenceException
Now, the default modelbinder has no clue about those specific EF functions and will simply instantiate the parent with new and also doesn't attach it to any context. Both requirements are not fulfilled and lazy loading cannot work.
You could write your own model binder to create the instance with Create() but this is probably the worst solution as it would make your view layer very EF dependent.
If you need the child collection after model binding I would in this case load it via explicit loading:
// parent comes as parameter from POST action method
context.TestParents.Attach(parent);
context.Entry(parent).Collection(p => p.TestChildren).Load();
If your context and EF is hidden behind a repository you will need a new repository method for this, like:
void LoadNavigationCollection<TElement>(T entity,
Expression<Func<T, ICollection<TElement>>> navigationProperty)
where TElement : class
{
_context.Set<T>().Attach(entity);
_context.Entry(entity).Collection(navigationProperty).Load();
}
...where _context is a member of the repository class.
But the better way, as Darin mentioned, is to bind ViewModels and then map them to your entities as needed. Then you would have the option to instantiate the entities with Create().
One possibility is to use hidden fields inside the form that will store the values of the child collection:
#model TestParent
#using (Html.BegniForm())
{
... some input fields of the parent
#Html.EditorFor(x => x.TestChildren)
<button type="submit">OK</button>
}
and then have an editor template for the children containing the hidden fields (~/Views/Shared/EditorTemplates/TestChild.cshtml):
#model TestChild
#Html.HiddenFor(x => x.TestChildID)
#Html.HiddenFor(x => x.Name)
...
But since you are not following good practices here and are directly passing your domain models to the view instead of using view models you will have a problem with the recursive relationship you have between the children and parents. You might need to manually populate the parent for each children.
But a better way would be to query your database in the POST action and fetch the children that are associated to the given parent since the user cannot edit the children inside the view anyway.

Dynamic Linq Search Expression on Navigation Properties

We are building dynamic search expressions using the Dynamic Linq library. We have run into an issue with how to construct a lamba expression using the dynamic linq library for navigation properties that have a one to many relationship.
We have the following that we are using with a contains statement-
Person.Names.Select(FamilyName).FirstOrDefault()
It works but there are two problems.
It of course only selects the FirstOrDefault() name. We want it to use all the names for each person.
If there are no names for a person the Select throws an exception.
It is not that difficult with a regular query because we can do two from statements, but the lambda expression is more challenging.
Any recommendations would be appreciated.
EDIT-
Additional code information...a non dynamic linq expression would look something like this.
var results = persons.Where(p => p.Names.Select(n => n.FamilyName).FirstOrDefault().Contains("Smith")).ToList();
and the class looks like the following-
public class Person
{
public bool IsActive { get; set;}
public virtual ICollection<Name> Names {get; set;}
}
public class Name
{
public string GivenName { get; set; }
public string FamilyName { get; set; }
public virtual Person Person { get; set;}
}
We hashed it out and made it, but it was quite challenging. Below are the various methods on how we progressed to the final result. Now we just have to rethink how our SearchExpression class is built...but that is another story.
1. Equivalent Query Syntax
var results = from person in persons
from name in person.names
where name.FamilyName.Contains("Smith")
select person;
2. Equivalent Lambda Syntax
var results = persons.SelectMany(person => person.Names)
.Where(name => name.FamilyName.Contains("Smith"))
.Select(personName => personName.Person);
3. Equivalent Lambda Syntax with Dynamic Linq
var results = persons.AsQueryable().SelectMany("Names")
.Where("FamilyName.Contains(#0)", "Smith")
.Select("Person");
Notes - You will have to add a Contains method to the Dynamic Linq library.
EDIT - Alternatively use just a select...much more simple...but it require the Contains method addition as noted above.
var results = persons.AsQueryable().Where("Names.Select(FamilyName)
.Contains(#0", "Smith)
We originally tried this, but ran into the dreaded 'No applicable aggregate method Contains exists.' error. I a round about way we resolved the problem when trying to get the SelectMany working...therefore just went back to the Select method.

Resources