Convention based Entity Framework Core returning the object associated with the foreign key when using Web API - ef-code-first

I'm using EF code first. By convention, I have added a foreign key and a reference to the foreign object (I believe this is needed). When I send a Get request to the API, it returns an IEnumerable. The problem is that each record returned also returns the complete object for the foreign key.
I've tried Googling the answer and for questions here on SO. I tried commenting out the reference to the other object but that didn't work.
public class Bill
{
// other properties
public Guid PersonId { get; set; } // this is the foreign key
public Person Person { get; set; } // this is the reference to the foreign object
}
This is what is returned when executing the Get request:
[
{
//other fields
"personId": "c28e52b0-1e40-46c4-812b-a61be7a69d53",
"person": {
//the entire other person object is returned here
}
},
]
How do I solve this without establishing a DTO for every model?
I'd like to hear if I'm using code first conventions improperly.

ASP.NET core 2.2 doesn't have return type Json(Object) as 2.1 did.
An ActionResult returning an anonymous type is one option, if you didn't want to use a DTO.
public IActionResult Get()
{
return new OkObjectResult(new { PersonId = 99, OtherProperty = "more stuff" });
}
Response body:
{
"personId": 99,
"otherProperty": "more stuff"
}
Use ObjectResult if you need to return various status codes:
return new ObjectResult(new { PersonId = 99, OtherProperty = "more stuff"}) {StatusCode = 200};

Related

ASP.NET - How to set optional field in ViewModel class?

Working on ASP.NET project, I am now facing a problem.
Frontend Data
fruits = [
{
type: "First",
size: 50,
weight: 120,
optionalParam1: "String",
optionalParam2: 152
},
{
type: "Second",
size: 12,
weight: 160,
optionalParam3: "Another String",
optionalParam4: 169
},
{
type: "Third",
size: 15,
weight: 190,
optionalParam1: "String for Third",
optionalParam5: [1, 2]
}
]
saleInfo = {
param1: 12,
param2: "string",
param3: 150
}
ViewModel Class
public class FruitViewModel {
public Dictionary<string, dynamic>[] fruits;
public Dictionary<string, dynamic> saleInfo;
public int total;
public Dictionary<string, dynamic> prevInfo;
}
Just want to store this information to the sql server and restore them from the sql db. But I am not sure if it's correct to use Dictionary for this.
Would you guide me how to receive this information and save to the sql db?
Also how to restore this information from the sql db and send to the frontend?
PS. Here the prevInfo parameter is the same as the saleInfo parameter, but as the name means, it's optional. It's null for request, only available for the response.
A list works fine.
For the first one create a class which mirrors what your front-end expects, something like:
public class FruitModel {
public string type { get; set; },
public int size { get;set; },
//the rest of properties you want
}
then your ViewModel starts to look like this:
public class FruitViewModel {
public List<FruitModel> fruits;
//change the rest to match
public Dictionary<string, dynamic> saleInfo;
public int total;
public Dictionary<string, dynamic> prevInfo;
}
the List will be serialised to Array on the front-end. You will need to do some manipulation as well since your data will be encapsulated into a holder object.
if you get the data in a variable called apiData then you will access the rest with apiData.fruits and the first item is apiData.fruits[0] for example.
if you want to make your life easy on the front-end, all you have to do is assign the response data straight to your variables, something like:
fruits = apiData.fruits;
nothing else needs to change then.
In VB, when using EF, you can make certain types of property optional by adding ? to the end of the field definition in your Model (i.e. make it nullable). Strings don't need this.
e.g.
Public Property optionalparam1 As String
Public Property optionalparam2 As Integer?
Public Property optionalparam3 As String
Public Property optionalparam4 As Integer?
Re. optional parameter 5, not sure about arrays and object elements, I am looking into that myself now. Good luck ;)
UPDATE: arrays are like Strings, they are references and so always nullable, so special treatment required.

Web API 2 does not process PATCH requests for Integers

I'm having a problem with Web API 2 (.net 4.5.1) in that it seems to ignore PATCH requests where the property is an integer, but processes other types without a problem (I've tested string and decimal).
I’ve setup an unsecured test API with a 'products' controller at http://playapi.azurewebsites.net/api/products. If you do a GET to that URL, you’ll get something like this product back:
{"Id": 1,"Name": "Xbox One","Category": "gaming","Price": 300,"Stock": 5}
‘Name’ and ‘Category’ are both strings, ‘Price’ is a Decimal and ‘Stock’ is an Integer.
If you send these requests, they both work (You’ll get a 200/OK with the updated entity):
PATCH, http://playapi.azurewebsites.net/api/products/1 with {"Price": 600.00}
PATCH, http://playapi.azurewebsites.net/api/products/1 with
{"Category": "Electronics"}
However, if you send this, it returns 200/OK, but does not make the update and the stock remains at the original value
PATCH, http://playapi.azurewebsites.net/api/products/1 with
{"Stock": 4}
My controller code is fairly standard boiler plate code (from the scaffolded ODATA controller but moved into a standard API controller):
// PATCH: api/Products/5
[AcceptVerbs("PATCH", "MERGE")]
public async Task<IHttpActionResult> PatchOrder(int id, Delta<Product> patch)
{
Validate(patch.GetEntity());
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var item = await db.Products.FindAsync(id);
if (item == null)
{
return NotFound();
}
patch.Patch(item);
try
{
await db.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!ProductExists(id))
{
return NotFound();
}
else
{
throw;
}
}
return Ok(item);
}
My model for 'Product' is as follows:
namespace PlayAPI.Models
{
public class Product
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public string Category { get; set; }
public double Price { get; set; }
public int Stock { get; set; }
}
}
When I debug the controller, I see that the ‘patch’ object has a _changedProperties collection which has no items in it when I do an integer request, but when I do any other kind of request it has the key that I changed.
Should web API support PATCH requests for integer properties? If so, do I need to do anything special on the server or client to make it work?
As a quick fix, Change the int to an Int64 on PlayAPI.Models.Product.
public Int64 Stock { get; set; }
It's my understanding that The Delta object used to patch the existing object doesn’t use JSON.net to convert and is silently throwing an Invalid cast exception when it parses JSON and then compares to the existing object from your database. You can read more about the bug over here: http://aspnetwebstack.codeplex.com/workitem/777
If you can't actually change the data type successfully, there may be a decent hack fix that you can use. Just attached unreadable data into the query string.
Here's a function you can call from within your Patch functions. As long as you aren't using the query string parameters specifically named what it's looking for, you should be just fine.
/// <summary>
/// Tries to attach additional parameters from the query string onto the delta object.
/// This uses the parameters extraInt32 and extraInt16, which can be used multiple times.
/// The parameter format is "PropertyName|Integer"
/// <para>Example: ?extraInt32=Prop1|123&extraInt16=Prop2|88&extraInt32=Prop3|null</para>
/// </summary>
[NonAction]
protected void SetAdditionalPatchIntegers<TEntity>(Delta<TEntity> deltaEntity, bool allowNull = true)
{
var queryParameters = Request.GetQueryNameValuePairs();
foreach (var param in queryParameters.Where(pair =>
pair.Key == "extraInt32" ||
pair.Key == "extraInt16"))
{
if (param.Value.Count(v => v == '|') != 1)
continue;
var splitParam = param.Value.Split('|');
if (allowNull &&
(String.IsNullOrWhiteSpace(splitParam[1]) ||
splitParam[1].Equals("null", StringComparison.OrdinalIgnoreCase)))
{
deltaEntity.TrySetPropertyValue(splitParam[0], null);
continue;
}
if (param.Key == "extraInt32")
{
int extraInt;
if (Int32.TryParse(splitParam[1], out extraInt))
{
deltaEntity.TrySetPropertyValue(splitParam[0], extraInt);
}
}
if (param.Key == "extraInt16")
{
short extraShort;
if (Int16.TryParse(splitParam[1], out extraShort))
{
deltaEntity.TrySetPropertyValue(splitParam[0], extraShort);
}
}
}
}
I really hate that there isn't a better answer, but at least something can be done about it.

Definining specific mapping rule for a field

I've started using ORMLite two days ago for refactoring an existing app....
I've some old stored procedure that returns columns with name that don't map 1:1 my dto object but I've managd to use [AliasAttribute] and it works fine.... at the same time I've some column that currently are mapped with some logic... for example
//Consider I've a dataset and I'm processing rows
int average = (int)row["AVERAGE"];
if(average > 50)
{
myDTO.Message = "Warning";
}
else
{
myDTO.Message = "OK";
}
Now we all agree it's not what it should be done at DataLayer but on that 5years old application we do so...is there a way I can tell in my DTO class (as I've done for Alias) to tell how to act when mapping the AVERAGE column?
Another question do ORM performs a trim on string or have I to perform it myself? again on some SP I've got no trim and I get something as "John DOE " ....now I do a .TrimEnd() when I got the value...
Thanks
Add the message as a property on your dto
public class MyDto
{
public int Average { get; set; }
public string Message
{
get { return Average > 50 ? "Warning" : "OK"; }
}
}

ASP.NET MVC Entity Framework - Entity Update - Overwriting Database Values with Null Values

I am currently looking for a design pattern or rather a best practice in implementing Repository<Entity>.Update() method for a ASP.NET MVC 4 application which uses Entity Framework 5 with Code First approach.
Problem:
The problem I encountered is that when an entity is queried from the database and shown on a view it may not have all the attributes populated. As a result when the repository.Update(entity) method is invoked, the entity passed to the Update() method may have un-bound properties having null values. However they may have some values in the database. As an example Customer.Misc in below code.
So the problem comes here. According to this approach all the properties which were not bound on the view are set to Null in the database after the first Update() method call.
class Customer
{
public int Id { get; set; }
public string Name { get; set; }
public string Misc { get; set; }
}
[HttpGet]
public ActionResult Update(int id)
{
Repository<Customer> repo = new Repository<Customer>();
return View(repo.GetById(id)); // View only binds Customer.Name
}
[HttpPost]
public ActionResult Update(Customer customer)
{
Repository<Customer> repo = new Repository<Customer>();
repo.Update(customer); // Customer.Misc is null
...
}
public void Update(TEntity entity)
{
var entry = DbContext.Entry<TEntity>(entity);
if (entry.State == EntityState.Detached)
{
ObjectContext.ApplyCurrentValues(EntitySetName, entity);
}
DbContext.SaveChanges();
}
Solutions I could think:
Bind all entity attributes on the view:
I think this is not feasible and at the same time it may lead to performance issues since all attributes get populated.
Implement a custom method to copy property values to avoid null values being copied.
EntityHelper.CopyNotNullValues(source, target) and ignore null values in the source entity. If we do this we might not be able to set any of the values to null if required.
Implement View Models and transform data back and forth with the Domain Model.
This is the best approach I could think of so far. All the attributes bound to the View Model will get populated always, on the Update POST, copy all View Model values to the Domain Model.
Really appreciate your thoughts on this.
In Entity Framework, using ChangeObjectState or ApplyCurrentValues will cause data loss. The only way to work around this issue in this case is attaching the input entity and mark the properties to be updated. See below example:
public void Update(TEntity entity, string[] updatedProperties)
{
DbContext.Entities.Attach(entity);
var entry = DbContext.Entry<TEntity>(entity);
for (int i = 0; i < updatedProperties.Length; i++)
{
entry.SetModifiedProperty(updatedProperties[i]);
}
DbContext.SaveChanges();
}
[HttpPost]
public ActionResult Update(Customer customer)
{
Repository<Customer> repo = new Repository<Customer>();
repo.Update(customer, new string[]{ "Name" }); // Only update name
...
}
It's the best solution I can think of. You wanna have least code and good performance. It's as difficult as finding an easy and well paid job.

Entity to Model and foreign key objects

I have an EF object called SportDivision. For simplicity's sake, I won't include every field, just the ones that are relevant:
[Table("SportDivision", Schema = "dbo")]
public class SportDivision: BaseReferenceEntity
{
public int Id { get; set; }
public string Name { get; set; }
public int SportId { get; set; }
[ForeignKey("SportId")]
public virtual Sport Sport { get; set; }
}
So it has a SportId and it's a foreign key that points to the table Sport.
Now, I can't just use an EF object in my views, so I have a model class that's mapped to SportDivision called SportDivisionModel:
public class SportDivisionModel: BaseReferenceModel
{
public int Id { get; set; }
public string Name { get; set; }
public int SportId { get; set; }
//Read only fields
public string Sport { get; set; }
}
I use automapper to transfer data from SportDivision to SportDivisionModel and vice versa. The mapping looks like this:
Mapper.CreateMap<SportDivision, SportDivisionModel>()
.ForMember(x => x.Sport, c => c.MapFrom(e => e.Sport.Name));
Mapper.CreateMap<SportDivisionModel, SportDivision>();
And I have a genericized service that CRUDs and translates data from entity to model or model to entity. Everything works fine except on Create, of which the function is shown below:
public TModel Create<TModel, TEntity>(TModel entry)
where TModel : BaseReferenceModel
where TEntity : BaseReferenceEntity
{
var dm = ServiceLocator.Current.GetInstance<ICrudService<TEntity>>();
var raw = Mapper.Map<TModel, TEntity>(entry);
var created = dm.CreateOrUpdate(raw);
return Mapper.Map<TEntity, TModel>(dm.FindById(created.Id));
}
In the very last line, where you see dm.FindById(created.Id), it returns a SportDivisionModel object with no Sport name. A null reference exception is found in .ForMember(x => x.Sport, c => c.MapFrom(e => e.Sport.Name));. It didn't load Sport after the entry was just created in the database.
I've debugged the code, and I see that the entry with a valid SportId is entered into the SportDivision table of my database, but when I try and bring it over to my MVC application, it doesn't get all the information.
This only is an issue on create. If I simply get data from the database without creating it beforehand, or if I edit the information, then the Sport field in my model object does get populated. I don't know why this is happening, and I can't use the .Include in my generic service call (because not all BaseReferenceEntity classes have a foreign key pointing to Sport).
Please advise. Thanks in advance.
I must play Sherlock Holmes and try to derive what could be the content of CreateOrUpdate and FindById from the indications in your question:
You say that you don't use Include because of the generic service. I assume that you also don't use explicit loading (Load) because you would face the same problem that you cannot really make it generic.
Conclusion: Because the Sport navigation property in the SportDivision gets loaded in certain scenarios (Edit) this can only happen due to lazy loading. The conclusion is backed by the fact that the Sport property is marked as virtual.
Lazy loading relies on proxies. If your SportDivision entity is a proxy then
either loading the Sport entity works
or you get an exception telling you that the context is already disposed (if you have disposed the context)
Number 2 is not the case -> Conclusion: Number 1 must be the case if the pre-condition is fulfilled
But Number 1 also isn't the case (loading Sport does not work)
Conclusion: The pre-condition that your SportDivision entity is a proxy is not true.
So: SportDivision is not a proxy. Could this mean that you have lazy loading in the context disabled? No: Because you are saying that editing works it means that when you load entities from the database they are loaded as proxies and support lazy loading.
Editing works, lazy loading isn't disabled but creating a new entity does not work in the way that the Sport entity is loaded when you proceed to use the newly created entity.
Conclusion: Your newly created entity (returned from CreateOrUpdate) is not a proxy and CreateOrUpdate looks similar to this:
public TEntity CreateOrUpdate(TEntity raw) where TEntity : class
{
if (blabla)
; //update
else
{
context.Set<TEntity>().Add(raw);
context.SaveChanges();
return raw;
}
}
and FindById is just:
public TEntity FindById(int id)
{
return context.Set<TEntity>().Find(id);
}
Since you are passing raw directly into the Add method of the DbSet<T> the question raises where does raw come from and how is it created.
Obviously AutoMapper creates the entity after this line: var raw = Mapper.Map<TModel, TEntity>(entry);
How does Automapper create an entity? Probably by calling new TEntity or by using some reflection code like Activator.CreateInstance or...
It doesn't really matter how, but for sure AutoMapper doesn't instantiate an Entity Framework proxy which had to be created by:
var entity = context.Set<TEntity>().Create();
If all this is true, I feel totally screwed by AutoMapper and generic excesses. If all this wouldn't be generic we could solve the problem by:
context.Set<SportDivision>().Add(raw);
context.SaveChanges();
context.Entry(raw).Reference(r => r.Sport).Load();
Instead we must try some ugly tricks now:
context.Set<TEntity>().Add(raw);
context.SaveChanges();
context.Entry(raw).State = EntityState.Detached;
// We hope that raw is now really out of the context
raw = context.Set<TEntity>().Find(raw.Id);
// raw must be materialized as a new object -> Hurray! We have a proxy!
return raw;
(I'm really not sure if the Detached trick above does work. Aside from that you are forced to reload an entity from the database you just have created and saved which is stupid somehow.)
Potential trick number 2 (without reloading from DB but for the price of being a further step more ugly):
context.Set<TEntity>().Add(raw);
context.SaveChanges();
context.Entry(raw).State = EntityState.Detached;
// We hope that raw is now really out of the context
var anotherRaw = context.Set<TEntity>().Create(); // Proxy!
anotherRaw.Id = raw.Id;
context.Set<TEntity>().Attach(anotherRaw);
context.Entry(anotherRaw).CurrentValues.SetValues(raw);
context.Entry(anotherRaw).State = EntityState.Unchanged;
return anotherRaw; // Proxy! Lazy loading will work!
Does AutoMapper have a feature of a "custom allocator or instantiator" and can custom user data (a context) be supplied? Then there would be a chance to let AutoMapper call context.Set<TEntity>().Create();. Or is it possible to instantiate the object by hand, pass it to AutoMapper and AutoMapper just updates the object's properties?
BTW: The line...
context.Entry(anotherRaw).CurrentValues.SetValues(raw);
...is kind of EF's built-in "AutoMapper". The parameter of SetValues is a general System.Object (could be your ...Model object) and the method maps property values from the supplied object to properties of attached entities by identical property names. Maybe you can leverage this feature somehow instead of using the mapping from model to entity done by AutoMapper.

Resources