i am trying to update a m-to-m relation with a ListBox. My entity-model looks like this:
I have a ListBox with Checkboxes where the user can decide which Player is in the league and which is not (IsSelected-Property). There are two problems: At first i can't check and then uncheck a Player (it won't be deleted). Second Problem: the first try, everything works and when i do the selection again, i get the following exception:
_innerException {"An error occurred while updating the entries. See the inner exception for details."} System.Exception {System.Data.Entity.Core.UpdateException}
_innerException {"Violation of PRIMARY KEY constraint 'PLID'. Cannot insert duplicate key in object 'dbo.PlayerLeague'. The duplicate key value is (2, 2).\r\nThe statement has been terminated."} System.Exception {System.Data.SqlClient.SqlException}
using (BettingLeagueEntities entities = new BettingLeagueEntities())
{
foreach (PlayerCheckBoxList p in this.PlayerList)
{
if(p.IsSelected == true)
{
PlayerLeague pl = new PlayerLeague();
pl.League = this.ActiveLeague;
pl.Player = p.ActivePlayer;
var local = entities.Set<Player>().Local.FirstOrDefault(x => x.PID == p.ActivePlayer.PID);
if(local != null)
{
entities.Entry(local).State = System.Data.Entity.EntityState.Detached;
}
var localLeague = entities.Set<League>().Local.FirstOrDefault(x => x.LID == this.ActiveLeague.LID);
if (localLeague != null)
{
entities.Entry(localLeague).State = System.Data.Entity.EntityState.Detached;
}
if (entities.Entry(p.ActivePlayer).State == System.Data.Entity.EntityState.Detached)
{
entities.Player.Add(p.ActivePlayer);
entities.Entry(p.ActivePlayer).State = System.Data.Entity.EntityState.Modified;
}
if (entities.Entry(this.ActiveLeague).State == System.Data.Entity.EntityState.Detached)
{
entities.League.Add(this.ActiveLeague);
entities.Entry(this.ActiveLeague).State = System.Data.Entity.EntityState.Modified;
}
if(p.ActivePlayer.PlayerLeague.All(x => x.LID != this.ActiveLeague.LID))
{
p.ActivePlayer.PlayerLeague.Add(pl);
this.ActiveLeague.PlayerLeague.Add(pl);
}
}
else
{
PlayerLeague local = entities.Set<PlayerLeague>().Local.FirstOrDefault(x => x.LID == this.ActiveLeague.LID && x.PID == p.ActivePlayer.PID);
if(local != null)
{
entities.PlayerLeague.Attach(local);
entities.PlayerLeague.Remove(local);
}
entities.SaveChanges();
}
}
entities.SaveChanges();
}
I have no clue how to solve this, do you have any suggestions?
I have it! I tried to comment a little bit to make it understandable.
The first problem was that i checked if my PlayerLeague already exists too lately. I moved this condition in my first if(statement).
The second error was, that in my else block, my statement to find a playerleague returned alsways null. Now i check if there is any entity and if this is true, i delete it.
using (BettingLeagueEntities entities = new BettingLeagueEntities())
{
foreach (PlayerCheckBoxList p in this.PlayerList)
{
// Check if the Player is seleceted and if the ActivePlayer has the Active League
if (p.IsSelected == true && p.ActivePlayer.PlayerLeague.All(x => x.LID != this.ActiveLeague.LID))
{
// Define the new PlayerLeague
PlayerLeague pl = new PlayerLeague {PID = p.ActivePlayer.PID, LID = this.ActiveLeague.LID};
var localPlayer = entities.Set<Player>().Local.FirstOrDefault(x => x.PID == p.ActivePlayer.PID);
if (localPlayer != null)
{
entities.Entry(localPlayer).State = System.Data.Entity.EntityState.Detached;
}
if (entities.Entry(p.ActivePlayer).State == System.Data.Entity.EntityState.Detached)
{
entities.Player.Add(p.ActivePlayer);
entities.Entry(p.ActivePlayer).State = System.Data.Entity.EntityState.Modified;
}
var localLeague = entities.Set<League>().Local.FirstOrDefault(x => x.LID == this.ActiveLeague.LID);
if (localLeague != null)
{
entities.Entry(localLeague).State = System.Data.Entity.EntityState.Detached;
}
if (entities.Entry(this.ActiveLeague).State == System.Data.Entity.EntityState.Detached)
{
entities.League.Add(this.ActiveLeague);
entities.Entry(this.ActiveLeague).State = System.Data.Entity.EntityState.Modified;
}
p.ActivePlayer.PlayerLeague.Add(pl);
this.ActiveLeague.PlayerLeague.Add(pl);
}
else
{
// Check if there is a PlayerLeague for this Player and league
bool hasPlayerLeague =
entities.PlayerLeague.Any(x => x.LID == this.ActiveLeague.LID && x.PID == p.ActivePlayer.PID);
if (hasPlayerLeague && p.IsSelected == false)
{
// Find PlayerLeague
PlayerLeague pl =
entities.PlayerLeague.FirstOrDefault(
x => x.LID == this.ActiveLeague.LID && x.PID == p.ActivePlayer.PID);
// Attach and Remove PlayerLeague
entities.PlayerLeague.Attach(pl);
entities.PlayerLeague.Remove(pl);
}
entities.SaveChanges();
}
}
}
Related
I'm swapping my values in List Object on some conditions and update List Object value.
Currently, what I'm doing is
- Looping on each object through List
- Check If condition is net
- Swap values
public static void SwapMinMaxIfNull<T>(this IEnumerable<T> rows, string reportfor)
{
if (reportfor.Equals("Comparison"))
{
var row = rows as IEnumerable<RiskBoardDataToExport>;
try
{
if (rows.Any())
{
var Tests = row.Where(min => min.MinGaitSpeed == null && min.MaxGaitSpeed != null).ToList();
if (Tests != null)
{
foreach (RiskBoardDataToExport test in Tests)
{
test.MinGaitSpeed = test.MaxGaitSpeed;
test.MaxGaitSpeed = null;
}
}
// again check for next object
Tests = row.Where(min => min.MinTUGTime == null && min.MaxTUGTime != null).ToList();
if (Tests != null)
{
foreach (RiskBoardDataToExport test in Tests)
{
test.MinTUGTime = test.MaxTUGTime;
test.MaxTUGTime = null;
}
}
// again check for next object
Tests = row.Where(min => min.MinBergScoreSpeed == null && min.MaxBergScoreSpeed != null).ToList();
if (Tests != null)
{
foreach (RiskBoardDataToExport test in Tests)
{
test.MinBergScoreSpeed = test.MaxBergScoreSpeed;
test.MaxBergScoreSpeed = null;
}
}
//.. for brevity
}
}
}
Can I do it in better way? I know about PropertyInfo i.e. Can check property name and get value etc, but, not having any hint to get this done.
Thanks
It's not exactly what you're asking for, but you can combine the clauses in your Where statements and then have a few if statements in the body:
public static void SwapMinMaxIfNull(this IEnumerable<RiskBoardDataToExport> rows,
string reportfor)
{
if (rows = null) return;
if (reportfor.Equals("Comparison", StringComparison.OrdinalIgnoreCase))
{
foreach (var row in rows.Where(r =>
(r.MinGaitSpeed == null && r.MaxGaitSpeed != null) ||
(r.MinBergScoreSpeed == null && r.MaxBergScoreSpeed != null) ||
(r.MinBergScoreSpeed == null && r.MaxBergScoreSpeed != null)))
{
if (row.MinGaitSpeed == null)
{
row.MinGaitSpeed = row.MaxGaitSpeed;
row.MaxGaitSpeed = null;
}
if (row.MinTUGTime == null)
{
row.MinTUGTime = row.MaxTUGTime;
row.MaxTUGTime = null;
}
if (row.MinBergScoreSpeed == null)
{
row.MinBergScoreSpeed = row.MaxBergScoreSpeed;
row.MaxBergScoreSpeed = null;
}
}
}
}
As this is an operation where order of the items in the list does not matter, you can easily speed this up by parallelization (you can read up on that here).
So, what you should do, is handle this foreach loop in a parallel way and combine it with Rufus L's optimized code for the fastest result.
var rows = rows.Where(r =>
(r.MinGaitSpeed == null && r.MaxGaitSpeed != null) ||
(r.MinBergScoreSpeed == null && r.MaxBergScoreSpeed != null) ||
(r.MinBergScoreSpeed == null && r.MaxBergScoreSpeed != null))
Parallel.ForEach(rows, (row) => {
{
if (row.MinGaitSpeed == null)
{
row.MinGaitSpeed = row.MaxGaitSpeed;
row.MaxGaitSpeed = null;
}
if (row.MinTUGTime == null)
{
row.MinTUGTime = row.MaxTUGTime;
row.MaxTUGTime = null;
}
if (row.MinBergScoreSpeed == null)
{
row.MinBergScoreSpeed = row.MaxBergScoreSpeed;
row.MaxBergScoreSpeed = null;
}
}
Note that this requires the System.Threading.Tasks namespace, that's where the Parallel class is.
I have been asked to create a generic library that will be able to handle saving objects while they are not in trackable state using EF.
This is my object
[Table("Address")]
public class Address : Entity
{
public string Name { get; set; }
public virtual Country Country { get; set; }
}
And object Get<Country>(false) indicates that the object is as AsNoTracking state
var user = new User() { UserName = "Admin" };
user.Adress = new System.Collections.Generic.List<Address>() { new Address() { Name = "Line 3", Country = repository.Get<Country>(false).First() } };
user.Adress.First().Country.Name = "sdsdsd"; // Only update!!!
repository.Save(user);
My saving method
/// <summary>
/// Update or insert record, this will loop throw all object recursive and find out items state
/// and do update or insert or delete, depending on item primary key and ItemState (optional to be included in the object)
/// be aware that deleting an object will also result to delete all other objects that are connected to it.
/// be also sure to include all objects that are depending on the current object when you removing an item, or else a dependency exception will be thrown in
/// </summary>
/// <param name="iEntityList"></param>
/// <param name="saveChanges">false if handling commit outside the function</param>
/// <returns></returns>
protected IEntity Save(List<IEntity> iEntityList, bool saveChanges = true, ItemState? overrideState = null, bool isExternalData = false)
{
if (iEntityList == null || !iEntityList.Any())
return null;
if (overrideState != null)
iEntityList.ForEach(x => x.State = overrideState.Value);
var db = context.Set(iEntityList.First().GetType());
foreach (var entry in iEntityList)
{
var state = entry.State;
if (state == ItemState.Removed && entry.ID <= 0) // no need to be included in Transaction. it dose not exist in the db anyway
continue;
DbEntityEntry<IEntity> obj = context.Entry(entry);
if (state == ItemState.Added)
{
if (entry.ID > 0 && (obj.State == EntityState.Detached || obj.State == EntityState.Added || isExternalData))
{
if (isExternalData)
obj.State = EntityState.Unchanged;
var existingItem = db.Find(entry.ID);
obj = context.Entry(existingItem as IEntity);
if (isExternalData)
{
obj.State = EntityState.Detached;
continue;
}
obj.CurrentValues.SetValues(entry);
obj.State = EntityState.Modified;
}
foreach (var prop in entry.GetType().GetProperties().
Where(x =>
(x.GetCustomAttribute<ExecludeFromEntityFramework>(false) == null) &&
(x.PropertyType.IsGenericList() ||
(x.PropertyType.IsClass && x.PropertyType != typeof(string)))))
{
if (!prop.CanRead || !prop.CanWrite || prop.GetMethod.IsStatic || prop.SetMethod.IsStatic)
continue;
var propValue = entry.GetProperty(prop.Name).GetValue(entry, null);
if (propValue != null)
{
List<IEntity> value;
if (prop.PropertyType.IsGenericList())
value = (propValue as System.Collections.IList).Cast<IEntity>().ToList();
else value = (propValue as IEntity).Convert(prop.PropertyType).Cast<IEntity>().ToList();
if (value != null && value.Any())
{
if (prop.PropertyType.IsGenericList())
{
if (value.Any(x => x.ID <= 0 && x.State != ItemState.Removed) && prop.GetCustomAttribute<ExternalData>() == null)
obj.Entity.GetProperty(prop.Name, value.FindAll(x => x.ID <= 0 && x.State != ItemState.Removed).Convert(propValue.GetType()));
}
else
if (value.First().ID <= 0 && value.First().State != ItemState.Removed && prop.GetCustomAttribute<ExternalData>() == null)
obj.Entity.GetProperty(prop.Name, value.First());
Save(value, false, null, prop.GetCustomAttribute<ExternalData>() != null);
}
}
}
obj.State = entry.ID > 0 ? EntityState.Modified : EntityState.Added;
}
else
{
if (obj.State == EntityState.Detached || isExternalData)
{
if (isExternalData)
obj.State = EntityState.Unchanged;
var existingItem = db.Find(entry.ID);
obj = context.Entry(existingItem as IEntity);
if (isExternalData)
{
obj.State = EntityState.Detached;
continue;
}
obj.CurrentValues.SetValues(entry);
}
foreach (var prop in entry.GetType().GetProperties().
Where(x =>
(x.GetCustomAttribute<ExecludeFromEntityFramework>(false) == null) &&
(x.PropertyType.IsGenericList() ||
(x.PropertyType.IsClass && x.PropertyType != typeof(string)))))
{
if (!prop.CanRead || !prop.CanWrite || prop.GetMethod.IsStatic || prop.SetMethod.IsStatic)
continue;
var propValue = entry.GetProperty(prop.Name).GetValue(entry, null);
if (propValue != null)
{
List<IEntity> value;
if (prop.PropertyType.IsGenericList())
value = (propValue as System.Collections.IList).Cast<IEntity>().ToList();
else value = (propValue as IEntity).Convert(prop.PropertyType).Cast<IEntity>().ToList();
Save(value, false, ItemState.Removed, prop.GetCustomAttribute<ExternalData>() != null);
}
}
obj.State = EntityState.Deleted;
}
}
if (saveChanges)
{
this.SaveChanges();
return iEntityList.FirstOrDefault();
}
else
return iEntityList.FirstOrDefault();
}
The problem is that Country has the primary key ID so it should only be updated but for some reason when i indicate that address is new object Country state changes to new to so I will get new object of type Country in the database.
I only need to have new address and a reference to the country.
If Country.ID == 0 then mark country as new and add it to the database
i have attached object to context, despite i am getting the error Cannot remove an entity that has not been attached.
if (itemRemove != -1)
{
//var deleteDetails = DBContext.ProductCustomizationMasters.Where(p => p.ProductID == this.ProductID && p.CustomCategoryID == catId && p.CustomType == (short)catTypeId).Single();
var deleteDetails = DBContext.ProductCustomizationMasters.Single(p => p.ProductID == this.ProductID && p.CustomCategoryID == catId && p.CustomType == (short)catTypeId);
DBContext.ProductCustomizationMasters.Attach(deleteDetails);
DBContext.ProductCustomizationMasters.DeleteOnSubmit(deleteDetails);
RemoveCategoryItems(catId, catTypeId);
}
private void RemoveCategoryItems(int catId, CategoryType catTypeId)
{
switch (catTypeId)
{
case CategoryType.Topping:
(this.ToppingItems.Where(xx => xx.ToppingInfo.CatID == catId && xx.ProductID == this.ProductID).Single()).IsDefault = false;
FreeToppingItems.RemoveAll(x => x.ProductID == this.ProductID && x.ToppingInfo.CatID == catId);
break;
case CategoryType.Dressing:
(this.DressingItems.Where(xx => xx.DressingInfo.CatID == catId && xx.ProductID == this.ProductID).Single()).IsDefault = false;
FreeDressingItems.RemoveAll(x => x.ProductID == this.ProductID && x.DressingInfo.CatID == catId);
break;
case CategoryType.SpecialInstruction:
(this.InstructionItems.Where(xx => xx.InstructionInfo.CatID == catId && xx.ProductID == this.ProductID).Single()).IsDefault = false;
FreeInstructionItems.RemoveAll(x => x.ProductID == this.ProductID && x.InstructionInfo.CatID == catId);
break;
}
}
FIRST IDEA - You don't need to attach the item that you are going to delete. The item is already attached to the context and the state is being managed. Just skip the attach line and delete the object.
EDIT Since the attach doesn't appear to have been your problem, give this a shot:
if (itemRemove != -1)
{
var deleteDetails = DBContext.ProductCustomizationMasters.Single(p => p.ProductID == this.ProductID && p.CustomCategoryID == catId && p.CustomType == (short)catTypeId);
//Obviously, this isn't going to work directly, you need to actually assign the ID, Primary Key Field here...
var deleteMe = new ProductCustomizationMasters() { PrimaryKey = deleteDetails.PrimaryKey };
DBContext.Attach(deleteMe);
DBContext.DeleteOnSubmit(deleteMe);
DBContext.SubmitChanges();
RemoveCategoryItems(catId, catTypeId);
}
EDIT AGAIN - The code you have posted doesn't appear to be the source of your problem. There is something outside of this code that is setting an object up for deletion from the context ant has not been attached. I suggest working back through your code and inspecting all references to "DeleteOnSubmit" and making sure that those entities are attached when you mark them.
I'm using a timestamp column to check the concurrency in my entity. The exception is correctly thrown when data is not the same in 2 different contexts.
When such an exception occurs when saving, I call the following method to handle it:
public static void HandleOptimisticConcurrencyException(ObjectContext context, OptimisticConcurrencyException ex)
{
string msg = #"The data has changed while you were editing it.
If you save, your changes will override the previous ones.
If you don't, your changes will be lost.
Do you want to save your changes ?";
var ret = System.Windows.MessageBox.Show(msg, "Concurrency error...", MessageBoxButton.YesNo, MessageBoxImage.Warning);
if (ret == MessageBoxResult.Yes)
{
if (ex.StateEntries != null)
{
foreach (var item in ex.StateEntries)
{
context.Refresh(RefreshMode.ClientWins, item.Entity);
}
}
}
else
{
if (ex.StateEntries != null)
{
foreach (var item in ex.StateEntries)
{
context.Refresh(RefreshMode.StoreWins, item.Entity);
}
}
}
context.SaveChanges();
}
I checked, and the refresh is executed on each faulted entity.
However, the second SaveChanges() always rethrows an OptimisticConcurrencyException.
Am I doing something wrong ?
Thanks in advance
Edit
I've noticed that the problem arises because of a method call before the first SaveChanges()
try
{
this.UpdateFlags();
this.repository.Context.SaveChanges();
}
catch (OptimisticConcurrencyException ex)
{
ExceptionHelpers.HandleOptimisticConcurrencyException(this.repository.Context, ex);
}
If I comment out the UpdateFlags() call, I have no problem.
Here is the code for this method:
private void UpdateFlags()
{
DateTime now = DateTime.Now;
int masterId = (int)this.navigationContext.Item;
var master = this.repository.Context.Masters.Where(e => e.Id == masterId).FirstOrDefault();
foreach (var project in master.Projects)
{
// update flags for each project.
if (project.Dashboard == null)
{
project.Dashboard = new Dashboard();
}
var flags = project.Dashboard;
flags.ModifiedOn = now;
// Update DP flags
var dpFlag = (int)project.Tasks.Where(e => e.TaskDP != null)
.Select(e => this.CalculateCompletionStatus(e, now))
.DefaultIfEmpty(CompletionStatusType.Ok)
.Max();
flags.DP = dpFlag;
// Update TRS flags
var trsFlag = (int)project.Tasks.Where(e => e.TaskTRSs != null)
.Select(e => this.CalculateCompletionStatus(e, now))
.DefaultIfEmpty(CompletionStatusType.Ok)
.Max();
flags.TRS = trsFlag;
// Update REV flags
var revFlag = (int)project.Tasks.Where(e => e.TaskREV != null)
.Select(e => this.CalculateCompletionStatus(e, now))
.DefaultIfEmpty(CompletionStatusType.Ok)
.Max();
flags.REV = revFlag;
// Update DTP flags
var dtpFlag = (int)project.Tasks.Where(e => e.TaskDTP != null)
.Select(e => this.CalculateCompletionStatus(e, now))
.DefaultIfEmpty(CompletionStatusType.Ok)
.Max();
flags.DTP = dtpFlag;
// Update DEL flags
var delFlag = (int)project.Tasks.Where(e => e.TaskDEL != null)
.Select(e => this.CalculateCompletionStatus(e, now))
.DefaultIfEmpty(CompletionStatusType.Ok)
.Max();
flags.DEL = delFlag;
// Update FIN Flag
var finFlag = (int)project.SalesTasks.Select(e => this.CalculateCompletionStatus(e, now))
.DefaultIfEmpty(CompletionStatusType.Ok)
.Max();
flags.FIN = finFlag;
// Update Project flag
if (flags.REV == (int)CompletionStatusType.Client && project.DTPBeforeReview.HasValue && project.DTPBeforeReview.Value == false)
{
// Corner case : Review is late because of an external person (= grey) and DTP Before REV is not set
// => all moments after REV are not taken in account.
var projFlag = new List<int> { dpFlag, trsFlag, revFlag }.Max();
flags.ProjectStatus = projFlag;
}
else
{
var projFlag = new List<int> { dpFlag, trsFlag, revFlag, dtpFlag, delFlag, finFlag }.Max();
flags.ProjectStatus = projFlag;
}
}
}
However I don't see where the problem is, as this is made before the first SaveChanges()
Ok I think I found how to solve that.
The problem doesn't come from the first object who causes the first exception, but deeper in this object.
To solve that, I updated my method like this:
public static void HandleOptimisticConcurrencyException(ObjectContext context, OptimisticConcurrencyException ex)
{
string msg = #"The data has changed while you were editing it.
If you save, your changes will override the previous ones.
If you don't, your changes will be lost.
Do you want to save your changes ?";
var ret = System.Windows.MessageBox.Show(msg, "Concurrency error...", MessageBoxButton.YesNo, MessageBoxImage.Warning);
if (ret == MessageBoxResult.Yes)
{
if (ex.StateEntries != null)
{
foreach (var item in ex.StateEntries)
{
context.Refresh(RefreshMode.ClientWins, item.Entity);
}
}
}
else
{
if (ex.StateEntries != null)
{
foreach (var item in ex.StateEntries)
{
context.Refresh(RefreshMode.StoreWins, item.Entity);
}
}
}
do
{
try
{
context.SaveChanges();
break;
}
catch (OptimisticConcurrencyException ex2)
{
if (ret == MessageBoxResult.Yes)
{
foreach (var item in ex2.StateEntries)
{
context.Refresh(RefreshMode.ClientWins, item.Entity);
}
}
else
{
foreach (var item in ex2.StateEntries)
{
context.Refresh(RefreshMode.StoreWins, item.Entity);
}
}
}
}
while (true);
}
I'm open to any other better suggestion though...
Im getting angry with this error and cannot solve it.
Please, some Jedi master help me.
I'm trying to save trhee Entities: Region, Content and RegionalContent. Region is OK but Regional Content has to be associated with one Content and each Content may have Many RegionalContents(Translations). But I always get a DbUpdateException that has a UpdateException that has a SqlCeException that says something like:
*Impossible to insert a duplicated value with same index. Table name = XBLContents,Constraint name = PK_XBLContents_000000000000001C *
I'm debugging it for some days and could not find the error. Please, note that I'm still a little Padawan.
This is the code that saves the objects in they proper Tables:
Region region;
if (!db.Regions.Any(x => x.ID == Locale))
{
region = new Region { ID = Locale };
db.Regions.Add(region);
db.SaveChanges();
}
else
region = db.Regions.SingleOrDefault(x => x.ID == Locale);
for (int i = start; i < (start + 2); i++)
{
string guid = itens[i].Groups["guid"].Value;
Content c = new Content(guid);
if (!db.Contents.Any(x => x.GUID == guid))
{
c.Type = Type.ToString();
c.PopularInfo(Locale);
db.Contents.Add(c);
}
else
c = db.Contents.SingleOrDefault(x => x.GUID == c.GUID);
RegionalContent regionalcontent;
if (!db.RegionalInfos.Any(x => x.ContentId == guid && x.RegionId == Locale))
{
if (c.HTML == null)
c.PopularInfo(Locale);
regionalcontent = new RegionalContent(c, Locale);
regionalcontent.Region = region;
regionalcontent.Name = HttpUtility.HtmlDecode(itens[i].Groups["name"].Value);
db.RegionalInfos.Add(regionalcontent);
db.Contents.Add(c);
db.SaveChanges();
}
else
regionalcontent = db.RegionalInfos.SingleOrDefault(x => x.ContentId == guid && x.RegionId == Locale);
c.RegionalInfo.Clear();
regionalcontent.Region = region;
c.RegionalInfo.Add(regionalcontent);
Contents.Add(c);
}
You are calling SingleOrDefault when you know 1 already exists. Just use Single.
I would not call SaveChanges to the very end.
Are you sure the GUIDs are unique every time?