I've stumbled upon a strange problem which I can't wrap my head around.
I have two DataContexts in two different forms. I created two "Customer" entities (Customer A and Customer Z) in one form and saved them through the form's context. I then went back to the other form, where I manage orders. In this form, I can create an order and search for a customer (or his address).
public void CustomerAddressSearched()
{
// Get all delivery addresses matching keyword
List<Address> addressList = customerModel.GetMatchingDeliveryAddresses(view.OrderSearchKeyword);
// If there is no matching address
if (addressList.Count == 0)
{
MessageBox.Show(Properties.Resources.SearchNoResult);
}
// If there is a single matching address
else if (addressList.Count == 1)
{
activeOrder.Address = addressList[0];
PopulateOrderAddressControls(addressList[0]);
activeOrderIsSaved = false;
}
// If there is more than one matching address, open search result view for user to pick address
else
{
using (var form = new CustomerSearchView(addressList))
{
if (form.ShowDialog() == DialogResult.OK)
{
CustomerViewObject selectedCustomer = (CustomerViewObject)form.selectedCustomer;
activeOrder.Address = addressList.Where(a => a.Id == selectedCustomer.AddressId).FirstOrDefault();
activeOrder.AddressId = selectedCustomer.AddressId;
PopulateOrderAddressControls(activeOrder.Address);
activeOrderIsSaved = false;
}
}
}
EnableOrDisableControls();
}
I searched for Customer and got a list with both customers. I went back to the customer form and changed the name of Customer A to Customer B, saved, then went back and searched again. Unfortunately, I got Customer A and Z as a result, not Customer B. Checked the database, Customer B is in it. I then picked Customer A for the order I created and saved it. Created a new one, searched again, and this time I got Customer B instead of A. I somehow need to use the "old" version once, otherwise the new one doesn't show. If this makes any sense.
So I checked
List<Address> addressList = customerModel.GetMatchingDeliveryAddresses(view.OrderSearchKeyword);
to see if there was something wrong with the way I retrieve the latest customer data.
public List<Address> GetMatchingDeliveryAddresses(string keyword)
{
List<Address> addressList = new List<Address>();
foreach (var c in context.Customers)
{
if (c.DateDeleted == null)
{
var result = c.Address.Where(a => (a.LastName.Contains(keyword) || a.Company.Contains(keyword)) && a.IsDeliveryAddress == true)
.OrderByDescending(a => a.DateEffective)
.FirstOrDefault();
if (result != null)
{
addressList.Add(result);
}
}
}
return addressList;
}
Sure enough, when debugging and stopping at the foreach loop, I noticed the latest version (Customer B) was not loaded (if I didn't use it at least once or restarted the program/created a new context).
I put
var list = context.Addresses.ToList();
right before the foreach loop just to check if he wouldn't load the new customer address at all, but all of a sudden it worked, the foreach loop loaded the newest customer version. I removed the line again and it stopped working. Put it back in, worked again. I guess my question is, why is the line above somehow refreshing my context, or whatever is happening?
Cheers!
Edit
I started went to the customer directory where I had two customers (Customer E and Customer 3). I renamed them to Customer ASD and Customer 666. Went back to the order form and searched for Customer. It ran the following code:
public List<Address> GetMatchingDeliveryAddresses(string keyword)
{
List<Address> addressList = new List<Address>();
foreach (var c in context.Customers)
{
var result = c.Address.Where(a => (a.LastName.Contains(keyword) || a.Company.Contains(keyword)) && a.IsDeliveryAddress == true)
.OrderByDescending(a => a.DateEffective)
.FirstOrDefault();
System.Console.WriteLine("PRE Name: " + result.LastName);
}
var list = context.Addresses.ToList();
foreach (var c in context.Customers)
{
var result = c.Address.Where(a => (a.LastName.Contains(keyword) || a.Company.Contains(keyword)) && a.IsDeliveryAddress == true)
.OrderByDescending(a => a.DateEffective)
.FirstOrDefault();
System.Console.WriteLine("POST Name: " + result.LastName);
}
With the following output:
PRE Name: Customer E
PRE Name: Customer 3
POST Name: Customer ASD
POST Name: Customer 666
I don't get it.
The question is getting too many comments, so I'm gonna post an answer here, cause I'm pretty sure that's the reason why you have this.. problem.
From what you've wrote in the comments: The context used to retrieve the customers is created when the form is initialized. I'm doing a context per form. it's getting clear that you are creating the context along with the form itself (probably in the constructor) and this is bad for many reasons. There are a lot of articles why you should NOT do that, but here we are talking for one certain problem and that's obviously the fact that your context is not changing until you close the form.
Now for your last comment - Turns out you were right, the code never made it to the dispose call for the customer directory context. But I still don't understand why it triggers such strange behaviour and how the line I added "fixed" it.
The reason is that DbContext is keeping track on all the changes that you make until the current context is alive, but it's just an expression tree (you could check what an expression tree is if you like) and in order to have some real data from that you need to execute certain calls. One of which is ToList() which forces the execution of the changes, but the quotes are really in place here, due to the fact that it's not how you are supposed to do it.
To keep it short. If you are not going to use any sort of data access layer, and just gonna use the DbContext directly inside your forms you should remove the context creation from the constructor (or where it is right now) and create new context each time (most probably for each method) that need to use. And in order to get rid of this problem you should wrap up tour code in using statement like so :
public List<Address> GetMatchingDeliveryAddresses(string keyword)
{
using( var context = new MyEntitiesContext())
{
List<Address> addressList = new List<Address>();
foreach (var c in context.Customers)
{
var result = c.Address.Where(a => (a.LastName.Contains(keyword) || a.Company.Contains(keyword)) && a.IsDeliveryAddress == true)
.OrderByDescending(a => a.DateEffective)
.FirstOrDefault();
System.Console.WriteLine("PRE Name: " + result.LastName);
}
foreach (var c in context.Customers)
{
var result = c.Address.Where(a => (a.LastName.Contains(keyword) || a.Company.Contains(keyword)) && a.IsDeliveryAddress == true)
.OrderByDescending(a => a.DateEffective)
.FirstOrDefault();
System.Console.WriteLine("POST Name: " + result.LastName);
}
}
And that should be all.
Related
I don't have a problem currently, but I want to make sure, that the performance is not too shabby for my issue. My search on Microsofts documentation was without any success.
I have a Entity of the name Reservation. I now want to add some statistics to the program, where I can see some metrics about the reservations (reservations per month and favorite spot/seat in particular).
Therefore, my first approach was the following:
public async Task<ICollection<StatisticElement<Seat>>> GetSeatUsage(Company company)
{
var allReservations = await this.reservationService.GetAll(company);
return await this.FetchGroupedSeatData(allReservations, company);
}
public async Task<ICollection<StatisticElement<DateTime>>> GetMonthlyReservations(Company company)
{
var allReservations = await this.reservationService.GetAll(company);
return this.FetchGroupedReservationData(allReservations);
}
private async Task<ICollection<StatisticElement<Seat>>> FetchGroupedSeatData(
IEnumerable<Reservation> reservations,
Company company)
{
var groupedReservations = reservations.GroupBy(r => r.SeatId).ToList();
var companySeats = await this.seatService.GetAll(company);
return (from companySeat in companySeats
let groupedReservation = groupedReservations.FirstOrDefault(s => s.Key == companySeat.Id)
select new StatisticElement<Seat>()
{
Value = companySeat,
StatisticalCount = groupedReservation?.Count() ?? 0,
}).OrderByDescending(s => s.StatisticalCount).ToList();
}
private ICollection<StatisticElement<DateTime>> FetchGroupedReservationData(IEnumerable<Reservation> reservations)
{
var groupedReservations = reservations.GroupBy(r => new { Month = r.Date.Month, Year = r.Date.Year }).ToList();
return groupedReservations.Select(
groupedReservation => new StatisticElement<DateTime>()
{
Value = new DateTime(groupedReservation.Key.Year, groupedReservation.Key.Month, 1),
StatisticalCount = groupedReservation.Count(),
}).
OrderBy(s => s.Value).
ToList();
}
To explain the code a little bit: With GetSeatUsage and GetMonthlyReservations I can get the above mentioned data of a company. Therefore, I fetch ALL reservations at first (with reservationService.GetAll) - this is the point, where I think the performance will be a problem in the future.
Afterwards, I call either FetchGroupedSeatData or FetchGroupedReservationData, which first groups the reservations I previously fetched from the database and then converts them in a, for me, usable format.
As I said, I think the group by after I have read ALL the data from the database MIGHT be a problem, but I cannot find any information regarding performance in the documentation.
My other idea was, that I create a new method in my ReservationService, which then already returns the grouped list. But, again, I can't find the information, that the EF adds the GroupBy to the DB Query or basically does it after all of the data has been read from the database. This method would look something like this:
return await this.Context.Set<Reservation>.Where(r => r.User.CompanyId == company.Id).GroupBy(r => r.SeatId).ToListAsync();
Is this already the solution? Where can I check that? Am I missing something completely obvious?
I've been a developer (also in a professional capacity) for a little while now and really never focused on clean / well structured code. As completely self taught I guess I'm missing some of the fundamentals. Reading books never fills the gaps. So I hope to get some great experience from this post.
So to the point, I have a method that returns an object (Campaign) based on conditional logic.
If I can get the object via the "CampaignViewMode" then it must have been "viewed" so therefore GET
ELSE Get last inserted
1, Get if it has been recently viewed (Cookie)
2, Else just get the last Inserted.
Pretty basic but the code has a serious "code smell" (repetition). In an ideal world I'd like to remove the conditional.
public Campaign GetDefaultCampaign()
{
Campaign campaign = null;
using (UserRepository userRepo = new UserRepository())
{
var user = userRepo.GetLoggedInUser();
if (user != null)
{
string campaignViewMode = "";
if (HttpContext.Current.Request.Cookies["CampaignViewMode"] != null)
{
campaignViewMode = HttpContext.Current.Request.Cookies["CampaignViewMode"].Value.ToString();
}
//Get Last worked on/viewed
campaign = _context.tbl_Campaign
.Where(x => x.Name == campaignViewMode & x.tbl_UserToCampaign
.Where(z => z.UserId == user.UserId & z.CampaignId == x.CampaignId)
.Select(u => u.UserId)
.FirstOrDefault() == user.UserId)
.Select(y => new Campaign()
{
CampaignId = y.CampaignId,
Name = y.Name,
WebName = y.WebName,
DateAdded = y.DateAdded
}).FirstOrDefault();
//Or get last inserted
if (campaign == null)
{
campaign = _context.tbl_Campaign
.Where(x => x.Name == campaignViewMode & x.tbl_UserToCampaign
.Where(z => z.UserId == user.UserId & z.CampaignId == x.CampaignId)
.Select(u => u.UserId)
.OrderByDescending(d => d.DateAdded).FirstOrDefault() == user.UserId)
.Select(y => new Campaign()
{
CampaignId = y.CampaignId,
Name = y.Name,
WebName = y.WebName,
DateAdded = y.DateAdded
}).FirstOrDefault();
}
}
}
return campaign;
}
Could you kindly point me in the right direction of removing the conditional or at the very last reduce the "smell" ?
Fully appreciate your time!
Regards,
There's alot going on here. Here's what I'd do.
Don't new up instances (as you do with Repository). Code against abstracts (IRepository) which is provided by a DI container which is injected into the class constructor.
Remove the duplication that maps your data model to your returned model (Select(x=> new Campaign()). Extract this out as a method or a separate responsibility entirely.
Remove the huge nested if(user!=null). Check for this right up front and return if it is null.
Refactor the two fetching operations behind an interface (IGetCampaigns) and create two classes; one that fetches the latest inserted, and one that fetches the last viewed/worked on. Inject one into the other to form a decorator chain, wired up by your DI container.
Probably a lot to take in if you're unfamiliar with these concepts; happy to go through this offline if you'd like.
I can't figure out why the maintainability index (as calculated in Visual Studio) for this method is only 40, I literally have to remove almost all the lines except the first two to get above 60:
public void getNewPasswordDetails(Int32 newPasswordId)
{
int UserId = HttpContext.Current.User.Identity.GetUserId().ToInt();
bool userIsAdmin = HttpContext.Current.User.IsInRole("Administrator");
//get a list of userIds that have UserPassword records for this password
var UserIDList = DatabaseContext.UserPasswords.Where(up => up.PasswordId == newPasswordId).Select(up => up.Id).ToList();
//Retrive the password -if the user has access
Password newPassword = DatabaseContext.Passwords
.Include("Creator")
.Where(pass => !pass.Deleted
&& (
(UserIDList.Contains(UserId))
|| (userIsAdmin && ApplicationSettings.Default.AdminsHaveAccessToAllPasswords)
|| pass.Creator_Id == UserId)
)
.Include(p => p.Parent_UserPasswords.Select(up => up.UserPasswordUser))
.SingleOrDefault(p => p.PasswordId == newPasswordId);
if (newPassword != null)
{
//map new password to display view model
AutoMapper.Mapper.CreateMap<Password, PasswordItem>();
PasswordItem returnPasswordViewItem = AutoMapper.Mapper.Map<PasswordItem>(newPassword);
//generate a string based view of the new category
string passwordPartialView = RenderViewContent.RenderViewToString("Password", "_PasswordItem", returnPasswordViewItem);
//broadcast the new password details
PushNotifications.sendAddedPasswordDetails(passwordPartialView, returnPasswordViewItem.Parent_CategoryId, returnPasswordViewItem.PasswordId);
}
else
{
//we dont have access any more, so tell UI to remove the password
PushNotifications.sendRemovePasswordAccess(new PasswordDelete()
{
PasswordId = newPasswordId
});
}
}
The more complex code is, the harder it is to maintain. Right? So let us look at a complex section of code which was presented. I will examine it as if I was a developer looking at the code for the first time:
DatabaseContext.Passwords
.Include("Creator")
.Where(pass => !pass.Deleted
&& ((UserIDList.Contains(UserId)) // Why Check #1
|| (
userIsAdmin // Why Check #2
&& // Why Check #3
ApplicationSettings.Default.AdminsHaveAccessToAllPasswords
)
|| pass.Creator_Id == UserId)
)
.Include(p => p.Parent_UserPasswords.Select(up => up.UserPasswordUser))
.SingleOrDefault(p => p.PasswordId == newPasswordId);
Before entering the loop one already knows these state facts:
(UserIDList.Contains(UserId) as Why Check #1
userIsAdmin as why Check #2
(userIsAdmin && ApplicationSettings.Default.AdminsHaveAccessToAllPasswords) as why check #3
Yet the developer has relegated those checks to occur for every password in Passwords. Why? Isn't there a better way of expressing that ?
Complexity occurs due to the application (coding) of the program logic, for each logical fork in the determination of the business logic directly adds to the complexity of the code and subsequently future maintainability; hence the rating received.
Of course one has certain complexities in code just by its nature, that is going to happen and should be expected. The question is, can that complexity be minimized to the point where maintenance of the code can be better achieved.
So I have a little issue in sorting some data I have. In a Telerik Grid, I have a column called Requestor that displays the name of a person or Unit (group of people). The problem is, Requestor has two sources it can get it's data from. Here are the two sources.
1.) RequestorId: This is a foreign key to a table called Customer. Here, I store all the data for the user, including their full name. This field can be null btw.
2.) UnitId: This is another foreign key to a table called Units. Here, I store all the data for the Units, particularlly their names. This field can be null btw.
Here is the logic:
//Entity class that contains all the data for my grid
var purchaseOrders = _purchaseOrders.GetPurchaseOrders();
//Key is Id of PurchaseOrders, Value is name of requestor
var dictionary = new Dictionary<int, string>();
foreach (var purchaseOrder in purchaseOrders)
{
if (purchaseOrder.requestorId != null)
dictionary.add(purchaseOrder.Requestor.Fullname);
else
dictionary.add(purchaseOrder.unit.Fullname);
}
dictionary.orderby(x => x.value).ToDictionary(x => x.Key, x.Value);
var tempPurchaseOrders = new List<PurchaseOrder>();
foreach (var item in dictionary)
{
tempPurchaseOrders.Add(purchaseOrders.Where(x => x.Id == item.Key).FirstOrDefault());
}
purchaseOrders = tempPurchaseOrders.AsQueryable();
return purchaseOrders;
This logic returns an ordered list based on what I want to do, however, the problem is the amount of time it takes to process. It takes 1 minute to process. That's horrible obviously. Is there anyway to optimize this? I cut down the source after I return for the grid because there is no logical way to really cut it down beforehand.
Any help would be appreciated. Thanks.
Edit: I found out I no longer am required to use the RequestName field. That limits the data to two areas now. Still a minute to process though.
Did you try something like this:
return _purchaseOrders.GetPurchaseOrders().Select(i => new
{
OrderColumn = i.requestorId != null ? purchaseOrder.Requestor.Fullname : purchaseOrder.unit.Fullname,
// map other columns
})
.OrderBy(i => i.OrderColumn);
A bit like SÅ‚awomir Rosiek's solution (but entity framework won't accept that statement):
return _purchaseOrders.GetPurchaseOrders()
.OrderBy(o => o.unit.Fullname).ToList();
(since you don't use RequestName anymore).
Especially when GetPurchaseOrders() is an IQueryable from EF you delegate the sorting to the database engine because the sort expression becomes part of the SQL statement.
So I came up with my own solution. I first tried what both SÅ‚awomir Rosiek and Gert Arnold did. Unfortunately, like Gert mentioned, the first answer would not go through. The second one had similar issues.
In the end, I created a class to store the data from both Requestors and Units. It consisted of the following:
internal class RequestorData
{
public int entityId { get; set; }
public string Name { get; set; }
public bool isRequestorId { get; set; }
}
Next, I did the following.
//Entity class that contains all the data for my grid
var purchaseOrders = _purchaseOrders.GetPurchaseOrders();
var tempPurchaseOrders = new List<PurchaseOrder>();
var requestors = new List<RequestorData>();
var customers = purchaseOrders.Select(po => po.Requestor).Distinct().ToList();
var units = purchaseOrders.Select(po => po.Unit).Distinct().ToList();
foreach (var customer in customers)
{
if (customer != null)
requestors.Add(new RequestorData { entityId = customer.Id, Name = customer.FullName, isRequestorId = true });
}
foreach (var unit in units)
{
if (unit != null)
requestors.Add(new RequestorData { entityId = unit.Id, Name = unit.FullName, isRequestorId = false });
}
requestors = requestors.OrderBy(r => r.Name).ToList();
foreach (var requestor in requestors)
{
var id = requestor.entityId;
if (requestor.isRequestorId)
tempPurchaseOrders.AddRange(purchaseOrders.Where(po => po.RequestorId == id).ToList());
else
tempPurchaseOrders.AddRange(purchaseOrders.Where(po => po.UnitId == id).ToList());
}
purchaseOrders = tempPurchaseOrders.AsQueryable();
return purchaseOrders;
I ran this new rendition and have a 5-6 second time of wait. That's not perfect but much better than before. Thanks for all the help.
This is what I Have in my WCF service
public long Generic_Save(Product p, ObjectSet os)
{
if (p.Id == 0)
{
os.AddObject(p);
}
else
{
// UPDATE
Product original = os.Single<Project>(o => o.Id == p.Id);
original.Name = p.Name;
original.Items = p.Items; // doesn't work !
}
dataEntities.SaveChanges();
return p.Id;
}
Product p is an object from the WCF Call, with an EntityKey etc.. but it's not attached to the current dataEntities..
What I want to do is to save the object Product p directly, not to get the original from the ObjectSet os before and modify the values -> Product original = os.Single<Project>(o => o.Id == p.Id);
How can I do that?
[EDIT]
I have try this to add new items and it's working
foreach (Item item in p.Items)
{
try
{
dataEntities.Items.ApplyCurrentValues(item);
}
catch (Exception)
{
Items i = new Items();
// Set prop here or make a method CopyTo()
i.Prop = item.Prop;
dataEntities.AddToItems(i);
}
}
dataEntities.SaveChanges();
Badly. It is possible only with Product p itself (update detached entity) but it is really hard with items. The problem is that you must manually say EF exactly which item has changes, which is new and also which were deleted. The longer discussion of the problem is here. Common solutions are:
Do it exactly as you did at the moment but instead of assigning items, manually compare original and received items and modify loaded items accordingly. If you do not add or remove items on the client this should be just about calling ApplyCurrentValues for each item.
Use Self tracking entities (only for .NET clients).