How to use a Database Generated Identity Key in Web Api OData - asp.net-web-api

I've managed to create number of readonly Web Api OData services following the tutorials here: http://www.asp.net/web-api/overview/odata-support-in-aspnet-web-api. I'm therefore employing the ODataConventionModel builder to create the model from a set of entities (incidentally coming from a Telerik ORM). This all seems to work fine and I can happily issue queries, view the metadata and so forth on the service.
I've now tried to turn my attention to the other CRUD operations - firstly Create and have stumbled into a problem! Namely, the Post method fires correctly (CreateEntity) but the entity parameter is null - by doing a check against the ModelState.IsValid, it shows that the problem is a null ID (key) value. This is unsurprising because the database uses a Database Generated Identity for the ID column and therefore the ID would be created when the entity is saved into the database context.
I've therefore tried all sorts of ways of marking the ID column as database generated, but haven't managed to find anything. Strangely, I can't seem to find even one post of someone asking for this - surely I can't be the only one?!
I noted that when looking at the EF modelbuilder (for example here: http://forums.asp.net/t/1848984.aspx/1) there appears to be a means of affecting the model builder with a .HasDatabaseGeneratedOption property, but no similar option exists in the System.Web.Http.OData equivalent.
So the questions therefore are:
Is there a means of altering the model builder (or something else) so that the controller will accept the object and deserialize the entity even with a null key value?
If so, how can I do this?
If not, any suggestions as to other options?
I realise that I could potentially just populate the object with an (in this case) integer value from the client request, but this seems a) semantically wrong and b) won't necessarilly always be possible as a result of the client toolkit that might be used.
All help gratefully received!
Many thanks,
J.

You need to create a viewmodel for insert which does not contain the ID parameter. Use Automapper to map the properties of the incoming insert-model to your data entities.
The problem that you're having is that ID is a required attribute in your data model because it is your PK, except during insert, where it shouldn't be specified.

In my case, my database-generated key is a Guid.
As a work-around, in my TypeScript client code, I submit (via http POST) the object with an empty Guid like this: Note: ErrorId is the key column.
let elmahEntry: ELMAH_Error = {
Application: 'PTUnconvCost',
Host: this.serviceConfig.url,
Message: message,
User: that.userService.currentUserEmail,
AllXml: `<info><![CDATA[\r\n\r\n${JSON.stringify(info || {})}\r\n\r\n]]></info>`,
Sequence: 1,
Source: source,
StatusCode: 0,
TimeUtc: new Date(Date.now()),
Type: '',
ErrorId: '00000000-0000-0000-0000-000000000000'
};
Then, in my WebApi OData controller, I check to see if the key is the empty guid, and if so, I replace it with a new Guid, like this:
// POST: odata/ELMAH_Error
public IHttpActionResult Post(ELMAH_Error eLMAH_Error)
{
if (eLMAH_Error.ErrorId == Guid.Empty)
{
eLMAH_Error.ErrorId = Guid.NewGuid();
}
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
db.ELMAH_Error.Add(eLMAH_Error);
try
{
db.SaveChanges();
}
catch (DbUpdateException)
{
if (ELMAH_ErrorExists(eLMAH_Error.ErrorId))
{
return Conflict();
}
else
{
throw;
}
}
return Created(eLMAH_Error);
}

Related

Entity being tracked despite AsNoTracking

I have an object, Client, with a navigation property that is a list of Order objects. Whenever I retrieve a Client object, I include the list of Orders, with AsNoTracking().
public new IQueryable<Client> FindByConditionNoTracking(Expression<Func<Client, bool>> expression)
{
return this.ClientContext.Set<Client>().Include(s => s.Orders)
.Where(expression).AsNoTracking();
}
In my UpdateClient repository method, I take in a Client object. I then attempt to retrieve that original client from the database (using Include to get the child Orders), map the Client param to the original, and save to the database. Over here, I do not use AsNoTracking, because I specifically want the changes to be tracked.
public new void Update(Client client)
{
var id = client.ClientId;
var original = this.ClientContext.Clients.Include(s => s.Orders).Where(s => s.ClientId == id)
.FirstOrDefault<Client>();
original = _mapper.Map(client, original);
this.ClientContext.Update(original);
}
The error I am getting is that an instance of Order with the same key value is already being tracked. A few problems with that:
Wherever the Client and the child Orders are retrieved for the purposes of display I use AsNoTracking.
The only place where I retrieve without AsNoTracking is where I get the original within this very method.
The bug isn't with the parent property. If I was improperly retrieving the Client elsewhere, wouldn't I have this error with the Client id itself? But the error seems to be only with the navigation property.
All insight is appreciated!
If anyone else runs into this: Automapper, when mapping collections, apparently recreates the entire collection. I solved the above issue by using Automapper.Collections in my mapping configuration. Thanks to Mat J for the tip!

Breeze - Can't Create Entity when PK is an autogenerated ID

I have used breeze's CreateEntity a few times when table's PK is a user-entered value. And a few times with SQL SERVER when PK is an IDENTITY. This is my first time trying to do it when PK is autogenerated ID (actually a "sequence") in ORACLE. It isn't working.
I do check first to make sure I have fetched the Metadata then create the new, empty entity that will be filled in with values by user.
My code to createEntity (newEntity is a knockout Observable):
function createEntity(newEntity) {
newEntity(manager.createEntity(entityNames.escctransactions, {})); <<<<< this fails
return;
}
The Error:
Cannot attach an object of type (ESCC_TRANSACTIONS:... ) to an EntityManager without first setting its key or setting its entityType 'AutoGeneratedKeyType' property to something other than 'None'
I know I need to set the AutoGeneratedKeyType to "Identity" but not sure how to do it. Tried this when I'm inititalizing the metadata, but still getting same error so it's obviously not working:
var entyType = manager.metadataStore.getEntityType("ESCC_TRANSACTIONS");
entyType.setProperties({ AutoGeneratedKeyType: AutoGeneratedKeyType.Identity });
I've seen something about doing it in a constructor but I've never used a constructor in JavaScript. Also something about changing it in a config?
Using Breeze 1.6, Knockout.js 3.4, .NET 4.5.2 framework
THANKS
Figured it out myself and it's working now. The code to set AutoGeneratedKeyType is as follows:
var entityType = manager.metadataStore.getEntityType("ESCC_TRANSACTIONS");
entityType.autoGeneratedKeyType = "Identity";
Or this works:
var entityType = manager.metadataStore.getEntityType("ESCC_TRANSACTIONS");
entityType.autoGeneratedKeyType = breeze.AutoGeneratedKeyType.Identity;
And in spite of the Breeze documentation for AutoGeneratedKeyType here:
http://breeze.github.io/doc-js/api-docs/classes/AutoGeneratedKeyType.html, it's not a capital "A" in Auto, it's a small "a".

Webapi Update Multiple Records

Which is the best approach for updating multiple records at a time using ASP.NET Web API and OData against an Entity Framework database?
Let's suppose we have a table in our database with a "State" field, and we want to do something similar to this SQL statement: "UPDATE Table set State = 1". Should I do a loop with GET and PUT for each record? I do not like it at all, I guess it must be a better way to accomplish this.
Thank you
It looks like you can't do this natively in OData, but there is one approach that will definitely work; just use a native Web API action to perform the update.
E.g.
[HttpPut]
[Route("resources/state")]
public IHttpActionResult UpdateState(int newState)
{
db.Database.SqlCommand("UPDATE Table SET State = #p0", newState);
}
You'd call this from a client using a PUT /resources/state?newState=1.
It might be clearer to make newState an enum:
public enum State
{
New = 0,
Processed = 1,
Error = 2,
etc.
}
[HttpPut]
[Route("resources/state")]
public IHttpActionResult UpdateState(State newState)
{
db.Database.SqlCommand("UPDATE Table SET State = #p0", (int)newState);
}
Then your call becomes PUT /resources/state?newState=Processed which is a little clearer.
OData supports such kinds of operations. You can use OData Action to update the state.
Blog: odata-actions-and-functions
Sample: ODataActionSample

OData V4 AddObject SetLink odata.bind does not trasmit related entities

I've created a OData V4 Service described in the articles on the ASP.NET Homepage.
I basically have a table Events where I assign Guests to. I need additional information to this many to many relationship, so I have created a EventGuest table.
Inserting Events and inserting Guests via OData just works fine. It just doesn't want to work, as soon as I want to insert related entities.
This is my Controller-Code:
public async Task<IHttpActionResult> Post(EventGuest eventGuest)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
_db.EventGuest.AddOrUpdate(eventGuest);
await _db.SaveChangesAsync();
return Created(eventGuest);
}
And this is how I want to insert the relationship. For the Client Code I use the official T4 template.
var ev = container.Event.FirstOrDefault();
var guest = container.Guest.FirstOrDefault();
var evGuest = new EventGuest();
evGuest.Guid = Guid.NewGuid();
container.AddObject("EventGuest", evGuest);
container.SetLink(evGuest, "Event", ev);
container.SetLink(evGuest, "Guest", guest);
container.SaveChanges();
The request sent to the Server doesn't look too bad for me:
{"#odata.type":"#Entities.EventGuest","CreationTimestamp":"0001-01-01T00:00:00Z","Guid":"adf500e3-e3a1-4841-883e-2322ed863321","ID":0,"Event#odata.bind":"http://localhost/odata/Event(1)","Guest#odata.bind":"http://localhost/odata/Guest(1)"}
So the Server tries to use #odata.bind, but unfortunately in the POST-Method of the Controller the referenced entities "Guest" and "Event" are null.
Have you tried following the example at the MSDN Documentation? I noticed it always uses AddLink rather than SetLink, even in the SetLink documentation.
MSN Article is here: DataServiceContext.SetLink Method

Proper way to Edit an entity in MVC 3 with the Entity Framework using Data Model First approach?

A majority of the examples I see now are either using the Code First Approach or using an older version of MVC and the Entity Framework.
Assume I have a movie to update and I get to the Edit View, in the Edit method with the Post verb, what is the proper way to update a Movie? The first Edit Method below gets me to the Edit View with the populated Movie values and the second one is the one I want to use to update, I have tried some things, but nothing updates the data.
public ActionResult Edit(int id)
{
var movie = (from m in _db.Movies1
where m.Id == id
select m).First();
return View(movie);
}
[HttpPost]
public ActionResult Edit(Movie movie)
{
try
{
// TODO: Add update logic here
//What do I need to call to update the entity?
_db.SaveChanges();
return RedirectToAction("Index");
}
catch
{
return View();
}
}
Assuming that _db is derived from ObjectContext you have two options:
Change the state of the entity to Modified:
_db.Movies1.Attach(movie);
_db.ObjectStateManager.ChangeObjectState(movie, EntityState.Modified);
_db.SaveChanges();
This marks all properties of movie as modified and will send an UPDATE statement to the database which includes all column values, no matter if the values really changed or not.
Reload the original entity from the database and apply the changes to it:
var originalMovie = (from m in _db.Movies1
where m.Id == movie.Id
select m).First();
// You actually don't need to assign to a variable.
// Loading the entity into the context is sufficient.
_db.Movies1.ApplyCurrentValues(movie);
_db.SaveChanges();
ApplyCurrentValues will mark only those properties as modified which really did change compared to the original and the UPDATE statement which will be sent to the database only includes the changed column values. So, the UPDATE statement is potentially smaller than in the first example but you have to pay the price to reload the original entity from the database.
Edit
How does the second code example work?
When you run a query using the context (_db) Entity Framework does not only retrieve the entity from the database and assign it to the left side of the query (originalMovie) but it actually stores a second reference internally. You can think of this internal context "cache" as a dictionary of key-value pairs - the key is the entity primary key and the value is the entity itself, the same object as originalMovie refers to.
ApplyCurrentValues(movie) looks up this entity in the context's internal dictionary: It takes the key property value Id of the passed in movie, searches for an entity with that key in the internal dictionary and then copies property by property from the passed in ("detached") movie to the internal ("attached") entity with the same key. EF's change tracking mechanism marks the properties as Modified which were actually different to create later the appropriate UPDATE statement.
Because of this internal reference to the original entity you do not need to hold your own reference: That's the reason why originalEntity is not used in the code. You can in fact remove the assignment to the local variable altogether.
The example would not work if you disable change tracking when you load the original entity - for example by setting _db.Movies1.MergeOption = MergeOption.NoTracking;. The example relies on enabled change tracking (which is the default setting when entities are loaded from the database).
I cannot say which of the two examples has better performance. That might depend on details like size of the entities, number of properties which have been changed, etc.
It's worth to note though that both approaches do not work if related entities are involved (for example movie refers to a category entity) and if the relationship or the related entity itself could have been changed. Setting the state to Modified and using ApplyCurrentValues both affect only scalar and complex properties of movie but not navigation properties.
Your second edit method should look something like this:
[HttpPost]
public ActionResult Edit(int id, FormCollection collection)
{
var movie = (from m in _db.Movies1
where m.Id == id
select m).First();
if (TryUpdateModel(movie))
{
_db.SaveChanges();
return (RedirectToAction("Index"));
}
return View(movie);
}

Resources