Using NopCommerce 3.8, Visual Studio 2015 proff.
I have created a plugin that is responsible for making restful calls to my Web API that exposes a different DB to that of Nop.
The process is run via a nop Task, it successfully pulls the data back and i can step through and manipulate as i see fit, no issues so far.
Issue comes when i try to update a record on the product table, i perform the update... but nothing happens no change, no error.
I believe this is due to the Context having no idea about my newly instantiated product object, however I'm drawing a blank on what i need to do in relation to my particular example.
Similar questions usually reference a "model" object that is part of the parameter of the method call, "model" has the method ToEntity which seems to be the answer in similar question in stack.
However my example doesn't have the ToEntity class/method possibly because my parameter is actually a list of products. To Clarify here my code.
Method in RestClient.cs
public async Task<List<T>> GetAsync()
{
try
{
var httpClient = new HttpClient();
var json = await httpClient.GetStringAsync(ApiControllerURL);
var taskModels = JsonConvert.DeserializeObject<List<T>>(json);
return taskModels;
}
catch (Exception e)
{
return null;
}
}
Method in my Service Class
public async Task<List<MWProduct>> GetProductsAsync()
{
RestClient<MWProduct> restClient = new RestClient<MWProduct>(ApiConst.Products);
var productsList = await restClient.GetAsync();
InsertSyncProd(productsList.Select(x => x).ToList());
return productsList;
}
private void InsertSyncProd(List<MWProduct> inserted)
{
var model = inserted.Select(x =>
{
switch (x.AD_Action)
{
case "I":
//_productService.InsertProduct(row);
break;
case "U":
UpdateSyncProd(inserted);
.....
Then the method to bind and update
private void UpdateSyncProd(List<MWProduct> inserted)
{
var me = inserted.Select(x =>
{
var productEnt = _productRepos.Table.FirstOrDefault(ent => ent.Sku == x.Sku.ToString());
if(productEnt != null)
{
productEnt.Sku = x.Sku.ToString();
productEnt.ShortDescription = x.ShortDescription;
productEnt.FullDescription = x.FullDescription;
productEnt.Name = x.Name;
productEnt.Height = x.Pd_height != null ? Convert.ToDecimal(x.Pd_height) : 0;
productEnt.Width = x.Pd_width != null ? Convert.ToDecimal(x.Pd_width) : 0;
productEnt.Length = x.Pd_depth != null ? Convert.ToDecimal(x.Pd_depth) : 0;
productEnt.UpdatedOnUtc = DateTime.UtcNow;
}
//TODO: set to entity so context nows and can update
_productService.UpdateProduct(productEnt);
return productEnt;
});
}
So as you can see, I get the data and pass data through to certain method based on a result. From that list in the method I iterate over, and pull the the entity from the table, then update via the product service using that manipulated entity.
So what am I missing here, I'm sure its 1 step, and i think it may be either be because 1) The context still has no idea about the entity in question, or 2) Its Incorrect calls.
Summary
Update is not updating, possibly due to context having no knowledge OR my methodology is wrong. (probably both).
UPDATE:
I added some logger.inertlog all around my service, it runs through fine, all to the point of the call of update. But again I check the product and nothing has changed in the admin section.
plugin
I have provided the full source as i think maybe this has something to do with the rest of the code setup possibly?
UPDATE:
Added the following for testin on my execute method.
var myprod = _productRepos.GetById(4852);
myprod.ShortDescription = "db test";
productRepos.Update(myprod);
This successfully updates the product description. I moved my methods from my service into the task class but still no luck. The more i look at it the more im thinking that my async is killing off the db context somehow.
Turned of async and bound the getbyid to a new product, also removed the lambda for the switch and changed it to a foreach loop. Seems to finally update the results.
Cannot confirm if async is the culprit, currently the web api seems to be returning the same result even though the data has changed (some wierd caching by deafult in .net core? ) so im creating a new question for that.
UPDATE: It appears that the issue stems from poor debugging of async. Each instance I am trying to iterate over an await call, simply put im trying to iterate over a collection that technically may or may not be completed yet. And probably due to poor debugging, I was not aware.
So answer await your collection Then iterate after.
Related
I just migrated my application from ASP.Net Core 2.2 to 3.1, and I have a bug I don't understand. It's a fairly straightforward MVC app with a mySQL DB, and I'm using EF Core - Identity to manage what they call "policy-based" authorization, aka 'claims-based' auth.
The main Class/Model is a Family Profile. A given User can have 2 levels of claims to different Families - they can be either a Parent or a Caregiver. The User dashboard View shows a few lists: A list of children in any Parent-Family, a list of Caregivers authorized for Parent-Families, and a list of Caregiver-Families.
This is the Controller code that sends all that info to the view. The problem is that the foreach loop is generating an error in the browser:
"InvalidOperationException: Cannot set MySqlCommand.CommandText when there is an open DataReader for this command; it must be closed first."
It only happens when the User has a Caregiver relationship with a Family, since the conditional prevents you from entering the foreach, but part of my confusion is why this is an issue inside this loop when it seems like I am making very similar requests to the DB in other places.
I looked at this question and this one, and it seems like maybe the 2nd one is more relevant, so I tried adding the await piece and making the query method Async inside the foreach loop (it had been just 1 line in there), but it didn't fix the problem. Maybe I put it in the wrong place? The first question helped me understand what's going on under the hood, but I don't know where in my code I would actually implement any of it.
Thanks in advance for any help!
public async Task<ActionResult> Index()
{
var userId = this.User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
var currentUser = await _userManager.FindByIdAsync(userId);
var userFamilies = (_db.Families.Where(entry => (entry.ParentId == currentUser.Id))).ToList();
if (_db.Caregivers.Select(entry => (entry.CaregiverId == currentUser.Id)) != null)
{
var userCareFamilies = new List<Family>();
var careFamilies = _db.CaregiverFamilies
.Where(c => c.CaregiverId == currentUser.Id);
if (careFamilies.Count() != 0)
{
foreach (CaregiverFamily cf in careFamilies)
{
var thisFamily = await _db.Families.FirstOrDefaultAsync(f => f.FamilyId == cf.FamilyId);
userCareFamilies.Add(thisFamily);
}
}
ViewBag.CareFamilies = userCareFamilies;
}
The problem is with this line:
var careFamilies = _db.CaregiverFamilies
.Where(c => c.CaregiverId == currentUser.Id);
Because you are not materializing the query (via ToList etc), it keeps the connection open while you're iterating through the results.
Try using
var careFamilies = _db.CaregiverFamilies
.Where(c => c.CaregiverId == currentUser.Id).ToList();
On a side note, you're mixing Async (FirstOrDefaultAsync) and Sync (ToList) methods. I'd suggest sticking to one or the other.
I had a similar issue with this question, when updating a record using EF6.
I really thought I had cracked the whole updating thing, but now have to almost identical functions updating in what I think was an identical way. One works, the other doesn't. I have fixed the one that doesn't work by using Jamie's comment in the above question, but I'd like to understand if the function that works, really shouldn't and so is on borrowed time and I should make more like the 'fixed' one. Or, why the 'fixed' one didn't work in the first place. I even moved them into the same controller so that the database (DB) context was guaranteed the same. Have I missed something and they are not identical (functionally) at all?
It might also help some others out there that struggle with this as I did.
The function that works (cut down) is:
[HttpPost]
[Route("UpdateAddBusinessService")]
public async Task<IHttpActionResult> UpdateAddBusinessService(BusinessServiceDTO servicetoupdateoradd)
{
... pre check stuff...
try
{
if (servicetoupdateoradd.Id != null) // This is an existing service to be updated - if Is Null then create new
{
BusinessService businessService = await db.BusinessServices.FindAsync(servicetoupdateoradd.Id);
if (businessService != null)
{
Mapper.Map(servicetoupdateoradd, businessService);
db.Entry(businessService).State = EntityState.Modified;
await db.SaveChangesAsync();
return Ok("Service Updated");
}
else
The function that doesn't work is:
[HttpPost]
[Route("UpdateImage")]
public async Task<IHttpActionResult> UpdateImage(ImageDTO imageDTO)
{
... pre check stuff ...
try
{
// First find the image
// Image imagetoupdate = await db.Images.FindAsync(imageDTO.Id); <<-This FAILS.
Image imagetoupdate = db.Images.AsNoTracking().Single(x => x.Id == imageDTO.Id); <<- This WORKS
if (imagetoupdate != null)
{
imagetoupdate = Mapper.Map<ImageDTO, Image>(imageDTO); // Move the stuff over..
db.Entry(imagetoupdate).State = EntityState.Modified;
await db.SaveChangesAsync();
return Ok();
}
I wondered (as you will no doubt), if my Mapper function was doing anything, but I suspect not (without digging too deep, but I guess it could be), my Mapper.Config functions for the two DTO's are very similar:
cfg.CreateMap<Image, ImageDTO>();
cfg.CreateMap<ImageDTO, Image>();
and:
cfg.CreateMap<BusinessService, BusinessServiceDTO>();
cfg.CreateMap<BusinessServiceDTO, BusinessService>();
I would really just like to understand the 'correct' way of doing this so it doesn't bite me again. Thanks in advance.
EDIT: I was asked (quite reasonably) if the 'pre-check stuff' does anything to fetch the data, it doesn't, but there is a subtle difference, that I might have missed...
This is from the BusinessService function that works:
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
string userid = User.Identity.GetUserId(); //Check user is valid
if (servicetoupdateoradd.UserId != userid)
{
var message = "User Id Not found - Contact support";
HttpResponseMessage err = new HttpResponseMessage() { StatusCode = HttpStatusCode.ExpectationFailed, ReasonPhrase = message };
return ResponseMessage(err);
}
This is from the UpdateImage function that didn't work:
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
string userid = User.Identity.GetUserId();
SGGUser user = db.Users.Find(userid); // Use this find and not UserManager becuase its a different context and buggers up the file save
if (user == null)
{
var message = "User Id Not found - Contact support";
HttpResponseMessage err = new HttpResponseMessage() { StatusCode = HttpStatusCode.ExpectationFailed, ReasonPhrase = message };
return ResponseMessage(err);
}
I see that in this one, though I don't fetch the relevant data, I do use the 'db' context.. could that be it?? The Image object does contain a reference to the user, so maybe that does some magic in the background code?
Appologies, I just didn't want to clutter up the question too much...
This line:
Mapper.Map(servicetoupdateoradd, businessService);
and this line:
imagetoupdate = Mapper.Map<ImageDTO, Image>(imageDTO); // Move the stuff over..
look similar, but do two different things.
The first line will tell Automapper to copy values from the first object over to the second object reference using the mapping rules.
The second line will tell Automapper to make a completely new reference to the entity with the mapped over values from the provided object and return it.
So in the first case, the entity reference is preserved to the one the DbContext knows about. The reference was loaded from a DbContext and should be tracking changes so you shouldn't even need to set it's entity state. In the second case, Automapper is creating an entirely new reference and assigning it over top the original reference. EF is treating that as a completely new instance and trying to attach it, resulting in it complaining because the context had already loaded that entity, you just overwrote the reference.
It should work if you change the second instance to:
Mapper.Map(imageDTO, imagetoupdate);
I'm very new to BreezeJS and i'm doing something wrong, but not sure what. I am using a 3rd party API for my GET requests and using my own server backend to process the SaveChanges to fire off each request individually to the 3rd party as I can't customize the post/put requests to the exact syntax and post data format I need.
Our Model is dynamic (meaning customers can add new attributes/fields which then flow through from the rest api to our client) so that's why the code looks like it does below, this is the controller:
[HttpPost]
public SaveResult SaveChanges(JObject saveBundle)
{
var context = JsonConvert.DeserializeObject<List<dynamic>>(saveBundle.SelectToken("entities").ToString());
foreach (var entity in context)
{
foreach (JProperty obj in entity)
{
if (obj != null)
{
// nothing right now but in future persist somehow
}
}
}
// Construct the save result to inform the client that the server has completed the save operation
var keyMappings = new List<KeyMapping>();
return new SaveResult()
{
Entities = context.Cast<object>().ToList(),
Errors = null,
KeyMappings = keyMappings
};
}
Call stack looks like such:
TypeError: undefined is not a function
at http://localhost:5749/Scripts/breeze.debug.js:14114:51
at http://localhost:5749/Scripts/breeze.debug.js:235:26
at Array.map (native)
at __map (http://localhost:5749/Scripts/breeze.debug.js:234:15)
at proto.visitAndMerge (http://localhost:5749/Scripts/breeze.debug.js:14111:16)
at http://localhost:5749/Scripts/breeze.debug.js:12806:48
at __using (http://localhost:5749/Scripts/breeze.debug.js:395:16)
at Object.processSavedEntities (http://localhost:5749/Scripts/breeze.debug.js:12794:13)
at saveSuccess (http://localhost:5749/Scripts/breeze.debug.js:12776:67)
at deferred.promise.then.wrappedCallback (http://localhost:5749/Scripts/angular.js:11046:81) undefined
I traced it to this line in proto.visitAndMerge (line 14114 in breeze.debug.js):
if (node.entityAspect.entityState.isDeleted()) {
If you think I'm doing something idiotic I'm all ears too. The third party API can be modified to do a GET accordingly but there is nothing to handle the SaveChanges bundle so as far as I know this is what I need to do.
Any advice would be great.
For reference, I was trying to follow this pattern: Breezejs SaveChanges: is it possible to return a custom SaveResult object, somehow?
I figured it out. entityAspect is sent along with the request. So converting it to a dynamic object created an entityAspect property which I then sent back to the client. This entityAspect needed to be removed so that it would not interfere with the JavaScript object.
Here's some code, maybe it helps someone someday:
var entities = JsonConvert.DeserializeObject<List<dynamic>>(saveBundle.SelectToken("entities").ToString());
foreach (var entity in entities)
{
JObject objEntityAspect = entity["entityAspect"];
JToken objEntityState = objEntityAspect["entityState"];
if (objEntityState.Value<string>() == "Modified")
{
// make a post with the instance id
}
entity.Remove("entityAspect");
}
entity.Remove("entityAspect") was the key component needed.
I have a method that is ran in the main application thread, but creates new Task for long-running operation and awaits it's result.
public async Task<CalculatorOutputModel> CalculateXml(CalculatorInputModel input)
{
// 1
return await Task.Factory.StartNew(
new Func<CalculatorOutputModel> (() =>
{
// 2
using (var ms = Serializer.Serialize(input))
{
ms.Position = 0;
using (var rawResult = Channel.RawGetFrbXmlOutputs(ms)) // 3
{
var result = Parser.Parse(rawResult);
return result;
}
}
}),
CancellationToken.None,
TaskCreationOptions.None,
TaskScheduler.FromCurrentSynchronizationContext());
}
My problem is that the AppContext in points (1) and (2) are "a bit" different. In point 1 there is usual application context in Current property, that has all the data I need. In point 2, as you understand, there is null reference in AppContext.Current, so I can't access any data. The problem with accessing context in point 2 seems to be easy, just "catch" current context in local variable or pass it as the parameter. The problem for me is more difficult because I need to access the context somewhere in the deep of line marked as "3".
The class itself is derived from System.ServiceModel.ClientBase<TChannel> and the place where I need to get access to the context is the class that implements IClientMessageInspector.
class CalculatorMessageInspector : IClientMessageInspector
{
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
if (AppContext.Current != null)
{
// never goes here
}
}
}
Just to clarify, here is the call stack (I can't pass my context inside required method):
So:
I can't pass context to Channel's method because it does not make any sense;
I can't save required parameters from context in Channel because it is proxy class;
I can't save required parameters in CalculatorMessageInspector because it is created within the place where current context is already null.
Can anyone advise any method how can I stay within the same context in another thread? Or, at least, how can I pass parameter from the place marked "2" inside the methods hierarchy? Maybe, I can use SynchronizationContext somehow to achieve it? Thanks a lot for any suggestions.
Update
AppContext.Current consider to be the same as HttpContext.Current != null ? HttpContext.Current.Items[AppContextKey] : null
Update 2
So it seems, that in this case there is no common solution to persist the context. Hence, the only applicable solution in this concrete situation (that is quite specific) is to capture required parameters using closure and save then in an object, that will be available in required method (for me worked adding properties to the implementator of IEndpointBehavior, but that solution is a bit odd). Thereby, the most applicable solution is to throw away the asynchronous wrapping over the sync call, so the AppContext never "goes away". Mark Stephen's answer as the right then.
To use async and await on ASP.NET, you must target .NET 4.5 and turn off quirks mode. The best way to do this is to set /configuration/system.web/httpRuntime#targetFramework="4.5" in your web.config.
Also, you shouldn't use Task.Run or Task.Factory.StartNew on ASP.NET. So your code should look more like this:
public CalculatorOutputModel CalculateXml(CalculatorInputModel input)
{
using (var ms = Serializer.Serialize(input))
{
ms.Position = 0;
using (var rawResult = Channel.RawGetFrbXmlOutputs(ms))
{
var result = Parser.Parse(rawResult);
return result;
}
}
}
The snippet below is from a Windows 8 store app in c# and xaml.
I have put this code together from variou samples on the web so this may not be the neatest way of doing this. Most of it is from the Grid template supplied in VS2012 and I have hooked up my web api as the source of the data
Please explain the following
When i call the Get method all works fine and i get data back into the xaml view
When i uncomment the Take(10) in the same method i get no data back.
It seems any attempt to put an extension method of a LINQ variety just stops the data being returned and also gives no indication why, it complies fine!
Any help appreciated
Thanks
Mark
public class TeamDataSource
{
private static TeamDataSource _sampleDataSource = new TeamDataSource();
private ObservableCollection<TeamDataItem> _items = new ObservableCollection<TeamDataItem>();
public ObservableCollection<TeamDataItem> Items
{
get { return this._items; }
}
public TeamDataSource()
{
this.Initialize();
}
public static IEnumerable<TeamDataItem> Get()
{
var thisdata = _sampleDataSource.Items;
return thisdata;//.Take(10);
}
private async void Initialize()
{
using (var client = new DataServiceClient())
{
List<TeamDataItem> list = await client.Download<List<TeamDataItem>>("/teams");
foreach (var i in list.OrderByDescending(t => t.Points).ThenByDescending(t => t.GoalDiff))
{
TeamDataItem team = i;
_items.Add(team);
}
}
}
}
Your problem is that Take doesn't immediately enumerate the items. It defers enumeration until either foreach is called on it or GetEnumerator is called on it. In this case the collection it is enumerating is disposed (as soon as the Get content ends) and so when it finally enumerates the items, there are no items anymore. Try adding thisdata.GetEnumerator(); as a line before your return statement.
From here:
This method is implemented by using deferred execution. The immediate
return value is an object that stores all the information that is
required to perform the action. The query represented by this method
is not executed until the object is enumerated either by calling its
GetEnumerator method directly or by using foreach in Visual C# or For
Each in Visual Basic.
Seems it was quite obvious in the end. As I was using aync and await, the call was immediately returning before the data had arrived. Therefore nothing for the Take(4) to work on.
Only problem now is when can i tell the task has completed?