EF6 Dbcontext.SaveChanges works from development machine but fails when published - asp.net-mvc

I have two repository functions for adding and removing favorite products for a customer.
public void AddProductToCustomer(int customerId, int productId) {
var customer = _customers.GetById(customerId,
new []{ModelContents.FavoriteProducts});
var product = _products.GetById(productId);
customer.FavoriteProducts.Add(product);
_context.SaveChanges();
}
and
public void RemoveProductFromCustomer(int customerId, int productId) {
var customer = _customers.GetById(customerId,
new[] { ModelContents.FavoriteProducts });
var product = _products.GetById(productId);
customer.FavoriteProducts.Remove(product);
_context.SaveChanges();
}
When I run locally against the production database it works fine but once I publish the code the _context.SaveChanges() fails to update the database. I have debugged this code remotely and there is no error thrown or returned from the function. Has anyone else seen this behavior before?
[UPDATE] By fails I mean that the database is not updated with the changes to customer.FavoriteProducts. Products are not added or removed from the list respectively and the total count of FavoriteProducts never changes, but only when the code is published, not when I am running it on my dev machine against the same database.
I hope that helps
[UPDATE] It occurred to me that people might wonder what the line new []{ModelContents.FavoriteProducts} refers to. This is a enumeration that tells the customer repository which navigation properties to populate for the model. The Customer is a large object and I don't always want all of its data. I have verified that customer.FavoriteProducts is populated in these cases.
[UPDATE] One thing that might be related to the issue is that the Product Model also has a Product.FavoritedBy Navigation Collection that contains the number of Customers that have Favorited that Product. Just like Customer.FavoriteProducts it is always up-to-date when I add or remove from a Customers FavoriteProducts collection except after the application is published to the server. The Entity Relationship is:
modelBuilder.Entity<CustomerUser>()
.HasMany(u => u.FavoriteProducts)
.WithMany(f => f.FavoritedBy)
.Map(t => t.MapLeftKey("UserId")
.MapRightKey("ProductId")
.ToTable("FavoriteProducts"));

Try the following in your AddProductToCustomer method:
_context.Entry(customer).State = EntityState.Modified;
_context.SaveChanges();

Related

How can i get all work items across all Team Services accounts?

I'm using the .NET libraries for accessing Visual Studio Team Services and i'm trying to work around a glaring design flaw on Microsoft's part. Apparently you can't have more than one collection per server/account, so i have to use several accounts, which in this example i'll refer to as collections, since Microsoft has even made clear they map to the same thing.
What i'm actually trying to achieve is to have a list with all my work items from all the collections i'm a member of. I have a QueryWorkItems() method that uses GetAllCollections() to get all my collections. That method has been tested and it works, it does return the two accounts i have. The top level method that triggers the whole thing is AssignedWorkItems(). My code is as follows:
public static List<TfsTeamProjectCollection> GetAllCollections()
{
// Get collections/accounts associated with user
string request = "https://app.vssps.visualstudio.com/_apis/Accounts?memberId=" + versionControl.AuthorizedIdentity.TeamFoundationId + "&api-version=3.2-preview";
string content = MakeRequestToAPI(request).Result;
dynamic results = JsonConvert.DeserializeObject<dynamic>(content);
List<TfsTeamProjectCollection> collections = new List<TfsTeamProjectCollection>();
// Iterate through all collections
Parallel.ForEach((IEnumerable<dynamic>)results.value, collection =>
{
TfsTeamProjectCollection col = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(new Uri((string)collection.accountUri));
collections.Add(col);
});
return collections;
}
public static List<WorkItem> QueryWorkItems(string query)
{
List<WorkItem> workItems = new List<WorkItem>();
List<TfsTeamProjectCollection> collections = GetAllCollections();
//Parallel.ForEach(collections, collection =>
foreach(var collection in collections)
{
WorkItemCollection items = collection.GetService<WorkItemStore>().Query(query);
// Add each work item to the overall list
Parallel.For(0, items.Count, i =>
{
Console.WriteLine(items[i].Title);
lock (workItems)
{
workItems.Add(items[i]);
}
});
}
return workItems;
}
public static List<WorkItem> AssignedWorkItems()
{
Init(); //initializes variables like projectName, workItemStore and VersionControlServer(versionControl)
string myItems = "select * from issue where [System.AssignedTo]=#me";
return QueryWorkItems(myItems);
}
When i call the AssignedWorkItems method i get a login prompt, even though i have a default connection already setup:
After i input my credentials though, in this line:
WorkItemCollection items = collection.GetService().Query(query);
i get the following error:
An unhandled exception of type 'Microsoft.TeamFoundation.TeamFoundationServiceUnavailableException'
occurred in Microsoft.TeamFoundation.Client.dll
Additional information: TF31002: Unable to connect to this Team
Foundation Server: https://xxxxxx.vssps.visualstudio.com/.
Team Foundation Server Url: https://xxxxxx.vssps.visualstudio.com/
Possible reasons for failure include:
The name, port number, or protocol for the Team Foundation Server is incorrect.
The Team Foundation Server is offline.
The password has expired or is incorrect.
Funny thing is everytime i run this the URL mentioned in the error switches back and forth between the two collections i have. Any idea as to why this is happening?
I can test the method QueryWorkItems successfully.
Based on the error message you got, it seems the VSTS URL stored in collections as the format https://account.vssps.visualstudio.com instead of https://account.visualstudio.com. So please confirm the URLs stored in collections for the method GetAllCollections are correct.
I used this GetAllCollections method to verify QueryWorkItems:
public static List<TfsTeamProjectCollection> GetAllCollections()
{
List<TfsTeamProjectCollection> collections = new List<TfsTeamProjectCollection>();
NetworkCredential cred1 = new NetworkCredential("username for Alternate authentication", "password for Alternate authentication");
TfsTeamProjectCollection tpc1 = new TfsTeamProjectCollection(new Uri("https://account1.visualstudio.com"), cred1);
collections.Add(tpc1);
NetworkCredential cred2 = new NetworkCredential("username for Alternate authentication", "password for Alternate authentication");
TfsTeamProjectCollection tpc2 = new TfsTeamProjectCollection(new Uri("https://account2.visualstudio.com"), cred2);
collections.Add(tpc2);
return collections;
}

UCommerce Prevent Users from adding new items during checkout

I have an Ecommerce website build with UCommerce. During the checkout process the user will be redirected to the payment portal for the payment.
I want to prevent users from adding new items in the basket while the user is in the payment portal. My current solution is to save the basket to a Session before redirecting the user to the payment portal.
Session["checkoutOrder"] = TransactionLibrary.GetBasket(!TransactionLibrary.HasBasket()).PurchaseOrder;
How can I overwrite the current basket with the one in the Session After the payment? This is to revert the basket to its original state before the payment.
I tried this:
[HttpPost]
public ActionResult ExecutePayment()
{
var order = Session["checkoutOrder"] as PurchaseOrder;
order.Save();
...
}
But I'm getting an error on order.Save():
Batch update returned unexpected row count from update; actual row count: 0; expected: 1
I'd just add to this as well that your Session["orderInProcess"] is an anti pattern in uCommerce. You may run into nasty exceptions as you're persisting NHibernate entities through requests which can/will lead to Session disposed exceptions. It may also lead to the initial exception that you're experiencing as you're actually by-passing the sesssion state of NHibernate.
Just use TransactionLibrary.GetBasket(!TransactionLibrary.HasBasket()).PurchaseOrder; every time you're retrieving your basket. NHibernate will take care of caching the order for you.
Then you can use order properties to save the state you're in.
var basket = TransactionLibrary.GetBasket(!TransactionLibrary.HasBasket()).PurchaseOrder;
basket["CheckoutInProcess"] = "True";
Best regards
Morten
I handled this differently since I have no way of reverting back the basket to its original state.
I decided to block the user from adding items in the basket when the payment is in process.
I created a session Session["orderInProcess"]=true before I redirect the user to the payment gateway.
Now every time the user tries to add a new item in the basket, I will check first if his current order is in process. like so:
[HttpPost]
public ActionResult AddToBasket(string sku, string quantity, string variant = null)
{
if (Session["orderInProcess"] != null)
{
if (bool.Parse(Session["orderInProcess"].ToString()))
{
return Json(new
{
Success = false,
ErrorMessage = "Order is currently in process."
});
}
}
.....
}
I hope this helps.

asp net mvc oracle database fail to retrieve data

I have an Oracle DB set up, and the Database has a bunch of Views.
The view GFL_BUYERS_ID_V has 2 columns of EMPLOYEE_ID and FULL_NAME. The database is already filled before I created the mvc project.
I Add the database successfully and added the a schema modelBuilder.HasDefaultSchema("examplename."); to my context because when I use SQL to retrieve data, I need to do:
SELECT * FROM "examplename".GFL_BUYERS_ID_V
for it to work, I assume that is what it's for. Apologies in advance I am new to .net, mvc and connecting databases.
The following crashes at the .Select and tells me that value cannot be null (I have no clue what is returning a null value, if I inspect values in context->GFL_BUYERS_ID_V when it breaks, in Local there are 0 values, and in Result View it contains all the appropriate one's, I assume this is relevant info but I don't know how to use it):
var model = new EmployeeModel();
using (var context = new OracleContext())
{
model.Buyers = context.GFL_BUYERS_ID_V.Select(s => new EmployeeModel.Buyer
{
BuyerId = s.EMPLOYEE_ID,
FullName = s.FULL_NAME
}).ToList();
}
return View(model);
this is my object:
public class EmployeeModel
{
public int EmployeeModelId { get; set; }
public List<Buyer> Buyers { get; set; }
public class Buyer
{
public int BuyerId { get; set; }
public string FullName { get; set; }
}
}
I know there is a connection to the database and I can query it via VB. I have fiddled enough with the connection string to know it is correct.
Edit: Scratch that I have no clue if my program is able to connect to the Database or not.
Screen shot of the error
If you would like me to provide more information, I will be happy to, really want to make this one work.
Edit 2: This seems like some sick joke clearly it shows a count: how
Is there an inner exception which is causing the ArgumentNullException?
You are definitely thinking along the right path, so I would break it down into manageable pieces:
1) confirm DB connectivity
2) confirm context is valid, which would most likely only be invalid if no DB connectivity
3) confirm GFL_BUYERS_ID_V is showing data
4) then do the projection to a different model
So the problem was in the Database server not the code. For some reason it used an old default schema variable that wasn't used for a long time.

Reloading bind entity cause "An object with the same key already exists in the ObjectStateManager"

This has been asked before, but none of the available answers seem to fit to my case.
In order to perform some validation, I have to reload from DB the same entity, which already bound to the model. The following causes the error. I'm almost losing my mind.
[HttpPost]
public ActionResult Edit(Tekes tekes, FormCollection fc)
{
...
Tekes myTekes = db.Tkasim.Find(tekes.TeksID);
<some validation here>
if (ModelState.IsValid)
{
db.Entry(tekes).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Details", new { id = tekes.TekesID });
}
}
Not sure why you have to grab the same entity from the data base. Anyway, the problem is that the Find statement adds the Tekes object to the context, subsequently the statement db.Entry(tekes).State = EntityState.Modified tries to do the same. It's not clear what the validation is all about, but I think you can solve this by replacing the Find by
var myTekes = db.Tkasim.AsNoTracking().Single(x => x.TeksId == tekes.TeksID);
AsNoTracking tells EF to fetch entities without adding them to the cache and the state manager.

Updating a many to many collection with EF code first

I'm using EF Code First with the following configuration
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<UserProfile>()
.HasMany(x => x.TeamLeaders)
.WithMany()
.Map(m => m.MapLeftKey("UserId")
.MapRightKey("TeamLeaderId")
.ToTable("UserTeamLeaders"));
}
TeamLeaders is an ICollection of Users i.e. it is a self referencing many-many relationship.
In plain English, a user can have more than one team leader. This configuration appears to be correct, as it creates the duel FK/PK link table as I would expect.
I have an MVC4 application that allows editing, adding and removal of team leaders from the collection.
In my controller, I originally had the following:
var original = context.UserProfiles
.Include("TeamLeaders")
.Single(x => x.UserId == model.UserId);
context.Entry(original).CurrentValues.SetValues(model);
However, that last line was failing to mark the TeamLeaders collection as updated, and when I called SaveChanges(), no changes were recorded.
Instead I wrote a simple CopyProperties method on my User class, which uses reflection to manually copy the properties across, so in my controller I now have:
var original = context.UserProfiles
.Include("TeamLeaders")
.Single(x => x.UserId == model.UserId);
//context.Entry(original).CurrentValues.SetValues(model);
original.CopyProperties(model);
However, this goes too far, and SaveChanges attempts to add a new user to the system matching the profile of the selected team leader.
Can anyone advise as to which part of this I am doing wrong? I am not sure whether I need to update the mappings, or change the way I copy properties from the view model to the model
You must modify the loaded TeamLeaders collection in the original user according to your changes so that change detection can recognize which leaders have been removed and which have been added. When you call SaveChanges then EF will write the appropriate DELETE and INSERT statements for the join table based on the detected changes. The simplest way would look like this:
var original = context.UserProfiles
.Include("TeamLeaders")
.Single(x => x.UserId == model.UserId);
original.TeamLeaders.Clear();
foreach (var teamLeader in model.TeamLeaders)
{
var user = context.UserProfiles.Find(teamLeader.UserId);
if (user != null)
original.TeamLeaders.Add(user)
}
context.SaveChanges();
Find will fetch the team leaders from the context if they are already loaded. If they aren't loaded it will query the database.
If you want to avoid additional queries you can attach the team leaders manually to the context:
var original = context.UserProfiles
.Include("TeamLeaders")
.Single(x => x.UserId == model.UserId);
original.TeamLeaders.Clear();
foreach (var teamLeader in model.TeamLeaders)
{
var user = context.UserProfiles.Local
.SingleOrDefault(o => o.UserId == teamLeader.UserId);
if (user == null)
{
user = new User { UserId = teamLeader.UserId };
context.UserProfiles.Attach(user);
}
original.TeamLeaders.Add(user)
}
context.SaveChanges();
Except the first query to load the original here is no further database query involved.
BTW: You should be able to use the strongly typed Include version with EF Code First:
Include(u => u.TeamLeaders)
You just need using System.Data.Entity; in your code file to get access to this version.

Resources