I'm having a really tough time finding guidance on how to AVOID including navigation properties in EF Core in a database-first approach. Using unmodified scaffolded web API controllers in Visual Studio, I have an entity that looks like this (simplified for example):
public partial class ProjectPhase
{
public ProjectPhase()
{
Projects = new HashSet<Project>();
}
public int PhaseId { get; set; }
public string PhaseName { get; set; }
public virtual ICollection<Project> Projects { get; set; }
}
My request is the default scaffolded HTTP GET request:
// GET: api/Phases
[HttpGet]
public async Task<ActionResult<IEnumerable<ProjectPhase>>> GetPhases ()
{
return await _context.ProjectPhases.ToListAsync();
}
The return value looks like this:
...{
"phaseId": 1,
"phaseName": "Pilot",
"projects": []
},...
I want this request to NOT include projects in the returned object. How do I do this?
if you want to read-only an entity use AsNoTracking.
The AsNoTracking() extension method returns a new query and the returned entities will not be cached by the context (DbContext or Object Context).
[HttpGet]
public async Task<ActionResult<IEnumerable<ProjectPhase>>> GetPhases()
{
return await _context.ProjectPhases.AsNoTracking().ToListAsync();
}
And another way to make the final object better by using a DTO class that matches the entity class and use automapper maybe for mapping.
For my purposes, using something like Automapper was a bit overkill. I just wanted to exclude a few navigation properties, so I just used the JsonIgnore attribute like so.
public int PhaseId { get; set; }
public string PhaseName { get; set; }
[System.Text.Json.Serialization.JsonIgnore]
public virtual ICollection<Project> Projects { get; set; }
Hope this helps someone else!
Related
I am trying to use model binding from query parameters to an object for searching.
My search object is
[DataContract]
public class Criteria
{
[DataMember(Name = "first_name")]
public string FirstName { get; set; }
}
My controller has the following action
[Route("users")]
public class UserController : Controller
{
[HttpGet("search")]
public IActionResult Search([FromQuery] Criteria criteria)
{
...
}
}
When I call the endpoint as follows .../users/search?first_name=dave the criteria property on the controller action is null.
However, I can call the endpoint not as snake case .../users/search?firstName=dave and the criteria property contains the property value. In this case Model Binding has worked but not when I use snake_case.
How can I use snake_case with Model Binding?
You need to add [FromQuery] attribute to the model properties individually
public class Criteria
{
[FromQuery(Name = "first_name")]
public string FirstName { get; set; }
}
Solution for .net core 2.1, 2.2, 3.0 and 3.1
Or without attributes you can do something like this which is cleaner I think (of course if the model properties are same as query parameters).
Meanwhile I use it in .net core 2.1, 2.2 and 3.0 preview & 3.1.
public async Task<IActionResult> Get([FromQuery]ReportQueryModel queryModel)
{
}
For anyone that got here from search engine like me:
To make it work on asp.net core 3.1+
public async Task<IActionResult> Get([FromQuery] RequestDto request);
public class RequestDto
{
[FromQuery(Name = "otherName")]
public string Name { get; set; }
}
Will read json property otherName into RequestDto.Name so basically you have to use FromQuery in 2 places.
Above answers are IMHO too complicated for such a simple thing already provided in asp.net framework.
In my case, I had an issue where my parameter name was option and in my class I also had the property called option so it was collapsing.
public class Content
{
public string Option { get; set; }
public int Page { get; set; }
}
public async Task<IActionResult> SendContent([FromQuery] Content option)
changed the parameter to something else:
public async Task<IActionResult> SendContent([FromQuery] Content contentOptions)
According to #Carl Thomas answer, here is the easier and the strongly typed way to have snake case FromQuery name:
CustomFromQuery
public class CustomFromQueryAttribute : FromQueryAttribute
{
public CustomFromQuery(string name)
{
Name = name.ToSnakeCase();
}
}
StringExtensions
public static class ObjectExtensions
{
public static string ToSnakeCase(this string o) => Regex.Replace(o, #"(\w)([A-Z])", "$1_$2").ToLower();
}
Usage
public class Criteria
{
[CustomFromQuery(nameof(FirstName))]
public string FirstName { get; set; }
}
If the
public async Task<IActionResult> Get([FromQuery] RequestDto request);
not work for anyone, you can try [FromRoute]
public async Task<IActionResult> Get([FromRoute] RequestDto request);.
In your dto you must keep the [FromQuery]
public class RequestDto
{
[FromQuery(Name = "otherName")]
public string Name { get; set; }
}
I'm using MVC5 and Visual Studio 2013 with entity framework. Basic problem is when we have a many-to-many relationships like this:
public class Person
{
public int ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public ICollection<Group> Groups { get; set; }
public Patient()
{
Groups = new HashSet<Group>();
}
}
public class Group
{
public Guid ID { get; set; }
public string Name { get; set; }
public ICollection<Person> People{ get; set; }
public Group()
{
People = new HashSet<Person>();
}
}
and we want to get the JSON representation for a record like this
Person person = db.People.Include(x => x.Groups).Where(i => i.ID == id).Single();
string json=JsonConvert.SerializeObject(person);
the JsonConvert throws a circular reference exception.
This SO question has an easy solution by configuring it to ignore circular reference exceptions:
var serializerSettings = new JsonSerializerSettings { PreserveReferencesHandling = PreserveReferencesHandling.Objects };
string json=JsonConvert.SerializeObject(person,serializerSettings);
My question is this: is this still the best way to deal with this situation? Those SO answers are now very old, and this seems like a very common situation. I've updated everything I can think of to update in my solution, but I still get the exception unless I do the extra configuration step.
Also, are there side effects to setting PreserveReferencesHandling = PreserveReferencesHandling.Objects? Is there any reason ever not do this?
using datamembers is a better solution I think.
Here you can chose what to serialize and what not.
Not adding attributes to the unwanted/circular references solves the problem of the circular serialization.
http://www.newtonsoft.com/json/help/html/DataContractAndDataMember.htm
Another way is to avoid eager loading, by removing include extension method from your statement. Or create another custom class for your dependent object and use projection using select(x => new CustomClass {PropertyOne = p.PropertyOne})
You just need to add
[ScriptIgnore(ApplyToOverrides = true)] into your text template (.tt) file (part of the EF data model).
Here a portion of my text template before
#>
<#=codeStringGenerator.NavigationProperty(navigationProperty)#>
<#
Once I inserted the code the line above the codeStringGenerator my classes auto generated and looked like this:
[ScriptIgnore(ApplyToOverrides = true)]
public virtual ICollection<Currency> Currencies { get; set; }
I also needed to modify the UsingDirectives function to insert "using System.Web.Script.Serialization;"
I'm using Entity Framework with Web API 2. I have a boat entity with properties like name, price etc. Those simple properties update fine when sent by Put to the web api controller. The boat entity also has a many to one relationship with an entity called BoatType. Boat types are "Yacht", "Motor Yacht" etc.
When a boat entity is updated in the controller the foreign key for boat type in the database doesn't get updated. Do I have to somehow manually update the child entity value or is there a way to get EF to do this automatically?
Here's an example PUT request sent to web API:
{
"$id":"1",
"Images":[],
"BoatType": {
"$id":"3",
"Boat":[],
"Id":1,
"DateCreated":"2015-09-15T13:14:39.077",
"Name":"Yacht"
},
"Id":2,
"Name":"Schooner",
"Description":"A harmless schooner",
"DateCreated":"2015-09-15T17:59:37.8",
"Price":65000
}
Here's the update function in web API:
[ResponseType(typeof(void))]
public async Task<IHttpActionResult> Put(int id, Boat boat)
{
if (id != boat.Id)
{
return BadRequest();
}
_db.Entry(boat).State = EntityState.Modified;
try
{
await _db.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!BoatExists(id))
{
return NotFound();
}
else
{
throw;
}
}
return StatusCode(HttpStatusCode.NoContent);
}
I've looked at similar questions like Entity Framework Code First Update Does Not Update Foreign Key, Entity Framework does not update Foreign Key object and Update foreign key using Entity Framework but none seem to have quite the same scenario (or the answers didn't help me understand my issue).
Here's the Boat and BoatType model classes (auto-generated by EF designer).
public partial class Boat
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Boat()
{
this.Images = new HashSet<Image>();
}
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public System.DateTime DateCreated { get; set; }
public Nullable<double> Price { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<Image> Images { get; set; }
public virtual BoatType BoatType { get; set; }
}
public partial class BoatType
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public BoatType()
{
this.Boat = new HashSet<Boat>();
}
public int Id { get; set; }
public System.DateTime DateCreated { get; set; }
public string Name { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<Boat> Boat { get; set; }
}
Ok, I figured out what the problem was. Using SQL Server Profiler to look at the SQL Update statement I saw that the foreign key for Boat.BoatType wasn't even in there - so I figured my model must be screwed up somewhere. When I created the model in the designer, I mistakenly set the relationship between Boat and BoatType as one to one. I later realised the mistake and changed the association to one (BoatType) to many (Boats) but that must have been AFTER I generated the database. D'oh! Something about the way EF handles associations meant that simply changing the association type in the diagram wasn't enough - I should have dropped/recreated the database constraint at that time.
Since I only had test data in the database what worked for me was to recreate the database using the "Generate database from model..." option in the designer.
Once I got the PUT working correctly the other thing I had to solve (which is not really on topic for this question but it's been discussed above so just in case it's useful to someone) was that Web API gave the error "A referential integrity constraint violation occurred: The property value(s) of 'Boat.BoatTypeId' on one end of a relationship do not match the property value(s) of 'Boat.BoatType.Id' on the other end.". The select list that allows the user to change the boat type is bound on the client using AngularJS to Boat.BoatType. So in the PUT data, Boat.BoatType had been updated to new values but Boat.BoatTypeId hadn't changed - hence the "referential integrity" error. So I just manually set the value of Boat.BoatTypeId to Boat.BoatType.Id before sending the PUT and all works as expected now.
I am not sure if I am doing this the right way or not, so need advice.
I have an entity, this entity has a child collection, and each child entity has another child collection. Something like this (simplified example)
public MyEntity() {
public long Id { get; set; }
public ICollection<MyChild> Children { get; set; }
}
public MyChild() {
public long Id { get; set; }
public long MyEntityId { get; set; }
public MyEntity MyEntity { get; set; }
public ICollection<MyGrandChild> Children { get; set; }
}
public MyGrandChild() {
public long Id { get; set; }
public long MyChildId { get; set; }
public MyChild MyChild { get; set; }
public string Name { get; set; }
}
Now in our application, the user retrieves this entity from our webApi into an angularJs application. The user then updates the entity (and sub entities) and passes the entity back to the webApi. I am using models to pass the objects from my webApi to the angularJs application, and they look something like this.
public MyEntityModel() {
public long Id { get; set; }
public ICollection<MyChildModel> Children { get; set; }
}
public MyChildModel() {
public long Id { get; set; }
public ICollection<MyGrandChildModel> Children { get; set; }
}
public MyGrandChildModel() {
public long Id { get; set; }
public string Name { get; set; }
}
Once the models are passed back to the webApi, I use Auto Mapper to convert them back to entity objects.
Now the bit I am confused about, i now pass the object to my service layer, my method looks similar to this
public Task<int> UpdateAsync(MyEntity updated) {
_context.Entry(updated).State = EntityState.Modified;
return _context.SaveChangesAsync();
}
If I add a new MyChild or MyGrandChild object to MyEntity after MyEntity exists or update MyChild or MyGrandChild object then the changes are not committed to the database? I changed my UpdateAsync method to this, but is this really needed?
public Task<int> UpdateAsync(MyEntity updated) {
_context.Entry(updated).State = EntityState.Modified;
foreach (var child in updated.Children) {
if (child.Id == 0) {
_context.Entry(child).State = EntityState.Added;
} else {
_context.Entry(child).State = EntityState.Modified;
}
foreach (var grand in child.Children) {
if (grand.Id == 0) {
_context.Entry(grand).State = EntityState.Added;
} else {
_context.Entry(grand).State = EntityState.Modified;
}
}
}
return _context.SaveChangesAsync();
}
Do I really have to loop through each collection, and sub collection, check if the id equals 0 and set its state accordingly?
Yes, you have to do that.
When you do all the work inside a DbContext scope it takes care to track al the changes happening in the entities, and you have to do nothing to let the DbContext know whathas changed.
But, in multi-layered applications, when you move the entities between layers they cannot be kept inside a DbContext scope, so you're responsible for tracking the cahnges.
Julie Lerman recommends implementing an interface to track the status of each entity. This interface has a property that keeps the entity status. This is modified on the client side and visited on the server to set each entity status: get the entities on the server side, attach them to the context, and modify its status according to the tracking interface property. (I can't find the reference, but it's covered in her Programming Entity Framework book, and in one of her Pluralsight courses).
Trackable Entities can also be of your interest.
If you want this to happen "automagically" you can use Breeze. This let you easyly expose an EF model on the client side, using JavaScript code. This code is able to track the changes (and do many other things like validating) in the client side, and send them back to the server to update the database. It's quite easy to get started. Basically you need to install the NuGet package for the server to implement a Breeze controllers, which is done with very little lines of code, and the NuGet package for the client, that implements the JavaScript code. It's advisable to use some MVVM JavaScript library, like Knockout or AngularJS, because the changes will be automatically tracked by subscribing to the observables created by these libraries.
I have the following entity:
public class SampleClass
{
public int Id { get; set; }
public object Args {get; set; }
}
Because Args can be of different types and doesnt need to be queryable, I want to store it in the Database as a json string.
I know the following workaround would solve my problem:
public class SampleClass
{
public int Id { get; set; }
public object Args { get { return Json.Deserialize(ArgsJson); } set { ArgsJson = Json.Serialize(value); } }
public string ArgsJson {get; set; }
}
But this is pretty ugly as it exposes information not related to the model and it contains logic again not related to the model.
What I would like to do, is something like that:
public class SampleClassMapper : EntityTypeConfiguration<SampleClass>
{
public SampleClassMapper()
{
this.Property(e => e.Args).MapAs<string>(arg => Json.Serialize(arg), str => Json.Deserialize(str));
}
}
Is there any cool way of doing so?
(I'm using .Net 4.0 with EntityFramework 5 and Sql Server 2008 if it helps)
The way that you do is the only one available for now in EF. Currently EF Code First don't have any easy way to change the object serialization but this can be done modifying the EDMX file at runtime.