I have two tables without any cascade deleting. I want to delete parent object with all child objects. I do it this way
//get parent object
return _dataContext.Menu.Include("ChildMenu").Include("ParentMenu").Include("Pictures").FirstOrDefault(m => m.MenuId == id);
//then i loop all child objects
var picList = (List<Picture>)menu.Pictures.ToList();
for (int i = 0; i < picList.Count; i++)
{
if (File.Exists(HttpContext.Current.Server.MapPath(picList[i].ImgPath)))
{
File.Delete(HttpContext.Current.Server.MapPath(picList[i].ImgPath));
}
if (File.Exists(HttpContext.Current.Server.MapPath(picList[i].ThumbPath)))
{
File.Delete(HttpContext.Current.Server.MapPath(picList[i].ThumbPath));
}
//**what must i do here?**
//menu.Pictures.Remove(picList[i]);
// DataManager dm = new DataManager();
// dm.Picture.Delete(picList[i].Id);
//menu.Pictures.de
//_dataContext.SaveChanges();
//picList[i] = null;
}
//delete parent object
_dataContext.DeleteObject(_dataContext.Menu.Include("ChildMenu").Include("ParentMenu")
.Include("Pictures").FirstOrDefault(m => m.MenuId == id););
_dataContext.SaveChanges();
It is enough to set the <OnDelete Action="Cascade" /> for the master association end in the CSDL part of the model.
In this case your code will work.
My situation was slightly different, and it took a while to get it right so I thought it worth documenting. I have two related tables, Quote and QuoteExtension:
Quote (Parent, Primary Key QuoteId)
QuoteExtension (Calculated fields for Quote, Primary and Foreign Key QuoteId)
I didn't have to set the OnDelete action to get it to work - but Craig's comment (if I could vote that up more I would!) led me to discover the issue. I was attempting to delete the Quote when QuoteExtension was not loaded. Therefore I found two ways that worked:
var quote = ent.Quote.Include("QuoteExtension").First(q => q.QuoteId == 2311);
ent.DeleteObject(quote);
ent.SaveChanges();
Or:
var quote = ent.Quote.First(q => q.QuoteId == 2311);
if (quote.QuoteExtension != null)
ent.Refresh(RefreshMode.ClientWins, quote.QuoteExtension);
ent.DeleteObject(quote);
ent.SaveChanges();
Interestingly trying to delete QuoteExtension manually didn't work (although it may have if I had included ent.SaveChanges() in the middle - this tends to happen only at the end of a unit of work in this system so I wanted something that didn't rely on this.
Related
I'm trying to do some refactoring on a project. I want to replace the code between the lines and with the two commented lines above it so I can use it on another page where I need the quote object
//quote.LoadActive();
//QuoteID = quote.Id;
//--------------------------------------------------------
List<Quote> DBquotes = session.Query<Quote>()
.Customize(x => x.WaitForNonStaleResultsAsOfLastWrite())
.ToList();
foreach (Quote q in DBquotes)
{
if (q.IsActive)
{
QuoteID = q.Id;
quote = session.Load<Quote>(q.Id);
}
}
//--------------------------------------------------------
Here's the LoadActive() function inside the Quote class
public Quote LoadActive()
{
List<Quote> DBquotes = session.Query<Quote>()
.Customize(x => x.WaitForNonStaleResultsAsOfLastWrite())
.ToList();
foreach (Quote q in DBquotes)
if (q.IsActive)
return session.Load<Quote>(q.Id);
return new Quote();
}
But I don't know how to actually update the quote object. I can't update inside the function without doing it element by element and that's too tedious. If I try and do quote = quote.LoadActive() then it complains that quote hasn't been initialized. I'm sure there's a way to set up what I'm trying to do but I'm blanking and can't think of good keywords to google my question. I couldn't even come up with a good subject wording for this post!
EDIT: tl;dr I need to load the active quote into the 'quote' object to be used by the page. What's in between the lines works, I want to replace all with the commented lines above (or something similar).
You're Quote object should not be responsible for accessing the database. You should be using some Data Access class or even better yet, a Query Handler to access the DB.
I think you might be a little confused as to what the code is doing. Let me try to explain.
public Quote LoadActive()
{
//This call returns every Quote Document in the DB
//RavenDB by default will only return a Max of 128 w/out doing paging
List<Quote> DBquotes = session.Query<Quote>()
.Customize(x => x.WaitForNonStaleResultsAsOfLastWrite())
.ToList();
//This doesn't make any sense. You're saying the first quote that is active,
//make another expensive DB call and return the same object.
//I still don't know what you want to do, but you already have the quote object
//why would you go to the DB and get something that you already have?
foreach (Quote q in DBquotes)
if (q.IsActive)
return session.Load<Quote>(q.Id);//Unnecessary, you have the Quote already
//return q;
return new Quote(); //if there's no active quotes just return an Empty Quote Object
}
Are you trying to return all Active Quotes from the DB?
You can do this
var activeQuotes = session.Query<Quote>()
.Where(x => x.IsActive == true)
.ToList();//Remember, RavenDB's safe by default feature
//will only return 128 records, you have to
//implement paging
If you are trying to just return the first IsActive quote (which is what it looks like you want to do from your code) you can simply do:
public Quote LoadActive()
{
return session.Query<Quote>()
.Customize(x => x.WaitForNonStaleResultsAsOfLastWrite())
.FirstOrDefault(x => x.IsActive);
}
Edit based on comment:
This will allow you to accomplish what you want by
QuoteId = quote.LoadActive().Id;
Just for my curiosity (and future knowledge), how does Entity Framework 5 decide when to create a new object vs. referencing an existing one? I might have just been doing something wrong, but it seems that every now and then if I do something along the lines of:
using (TestDB db = new TestDB())
{
var currParent = db.Parents.Where(p => p.Prop == passedProp).FirstOrDefault();
if(currParent == null) {
Parent newParent = new Parent();
newParent.Prop = passedProp;
currParent = newParent;
}
//maybe do something to currParent here
var currThing = db.Things.Where(t => t.Prop == passedPropTwo).FirstOrDefault();
currThing.Parent = currParent;
db.SaveChanges();
}
EF will create a new Parent in the database, basically a copy of the currParent, and then set the Parent_ID value of currThing to that copy. Then, if I do it again (as in, if there's already two of those parents), it won't make a new Parent and instead link to the first one. I don't really understand this behavior, but after playing around with it for a while something like:
using (TestDB db = new TestDB())
{
var currParent = db.Parents.Where(p => p.Prop == passedProp).FirstOrDefault();
if(currParent == null) {
Parent newParent = new Parent();
newParent.Prop = passedProp;
currParent = newParent;
}
//maybe do something to currParent here
var currThing = db.Things.Where(t => t.Prop == passedPropTwo).FirstOrDefault();
currThing.Parent = db.Parents.Where(p => p.ID == currParent.ID).First();
db.SaveChanges();
}
seemed to fix the problem. Is there any reason this might happen that I should be aware of, or was there just something weird about the way I was doing it at the time? Sorry I can't be more specific about what the exact code was, I encountered this a while ago and fixed it with the above code so I didn't see any reason to ask about it. More generally, how does EF decide whether to reference an existing item instead of creating a new one? Just based on whether the ID is set or not? Thanks!
If your specific instance of your DBContext provided that specific instance of that entity to you, then it will know what record(s) in the database it represents and any changes you make to it will be proper to that(those) record(s) in the database. If you instantiate a new entity yourself, then you need to tell the DBContext what exactly that record is if it's anything but a new record that should be inserted into your database.
In the special scenario where you have multiple DBContext instances and one instance provides you this entity but you want to use another instance to work with and save the entity, then you have to use ((IObjectContextAdapter)firstDbContext).ObjectContext.Detach() to orphan this entity and then use ((IObjectContextAdapter)secondDbContext).ObjectContext.Parents.Attach() to attach it (or ApplyChanges() if you're also editing it - this will call Attach for you).
In some other special scenarios (your object has been serialized and/or you have self-tracking entities), some additional steps may be required, depending on what exactly you are trying to do.
To summarize, if your specific instance of your DBContext is "aware" of your specific instance of an entity, then it will work with it as if it is directly tied to that specific row in the database.
I encountered something really puzzling in my development work.
removePreviousFoodMenuItems(oldRefList);
shFood.setNewFoodMenuItems(newRefList);
em.merge(shFood);
em.flush(); //Error occurs
If I call removePreviousFoodMenuItems before merge, I will get a "Cannot merge an entity that has already been removed" exception at runtime. However, this should not occur because I have set shFood to reference a new set of food menu items (newRefList). So why is merge still trying to merge the oldRefList elements that have already been removed? This problem does not occur if I put removePreviousFoodMenuItems after the flush statement.
shFood.setNewFoodMenuItems(newRefList);
em.merge(shFood);
em.flush(); //Error does not occur
removePreviousFoodMenuItems(oldRefList);
Below is the code for the removePreviousFoodMenuItems
public void removePreviousFoodMenuItems(ArrayList<FoodMenuItem> oldRefList){
for (Object f : oldRefList) {
FoodMenuItem foodMenuItem = (FoodMenuItem) f;
foodMenuItem.setStakeholderFood(null);
foodMenuItem.setPhotoEntity(null);
em.remove(foodMenuItem);
//em.flush();
}//end for
}//end removePreviousFoodMenuItems
Would really appreciate some advice on this!
UPDATE: How the newRefList is created:
StakeholderFood stakeholder = em.find(StakeholderFood.class, stakeholderID);
ArrayList<FoodMenuItem> newRefList = new ArrayList<FoodMenuItem>();
for (Object o : menuItem) {
FoodMenuItem fmi = (FoodMenuItem) o;
FoodMenuItem newFmi = new FoodMenuItem();
String previousName = fmi.getItemName();
newFmi.setItemName(previousName);
newFmi.setItemPrice(fmi.getItemPrice());
newFmi.setPhotoEntity(fmi.getPhotoEntity());
//Upload the photos for each item attached to menuItem
Photo photo = fmi.getPhotoEntity();
if(photo!=null){
photo.setFoodmenuItem(newFmi); //set new relationship, break off with old
em.merge(photo); //This will merge newFmi as well Fix this tomorrow
em.flush(); //update the links immediately
}
if (photo != null && fmi.getContainsImage() == Boolean.FALSE) {
uploadFoodMenuItemImages(photo);
newFmi.setPhotoEntity(photo);
newFmi.setContainsImage(Boolean.TRUE);
newFmi.setRenderedImage(Boolean.FALSE);
newFmi.setRenderedImageAltText(Boolean.FALSE);
}//end photo
else {
newFmi.setRenderedImageAltText(Boolean.TRUE);
}
newFmi.setStakeholderFood(stakeholder);
newRefList.add(newFmi);
}//end for
You have one or more same instances of FoodMenuItem in both oldRefList and newRefList. Applying remove to all items in oldRefList then causes some of the entities in newRefList to become removed.
Consequence is that shFood holds such a list where at least one FoodMenuItem is removed. If perform flush before removal, then on the moment when flush takes place, there is no such a problem, because shFood does not reference to removed instances.
I'm a little puzzled over the possible cachedependencies in asp.net, and I'm not sure how to use them.
I would like to add items to the HttpRuntime.Cache in a way, that the elements should invalidate if I change other elements in the cache. The dependencies should be defined by the key.
I want a function like this:
public MyObject LoadFromCache(string itemDescriptor, IEnumerable<string> dependencies)
{
var ret = HttpRuntime.Cache[itemDescriptor] as MyObject;
if (ret == null)
{
ret = LoadFromDataBase(itemDescriptor);
//this is the part I'm not able to figure out. Adding more than one dependency items.
var dep = new CacheDependency();
dependencies.ForEach(o => dep.SomeHowAdd(o));
HttpRuntime.Cache.Add(
itemDescriptor,
ret,
dependencies,
System.Web.Caching.Cache.NoAbsoluteExpiration,
System.Web.Caching.Cache.NoSlidingExpiration,
Caching.CacheItemPriority.Normal,
null
);
}
return ret;
}
Help me out on this one.
I didn't know you could do this, but if you look at the CacheDependency constructor here, you can see that the second parameter is an array of cache keys, so that if any of those cached items change, the whole dependency will be changed and your dependent item will be invalidated as well.
So your code would be something like:
String[] cacheKeys = new string[]{"cacheKey1","cacheKey2"};
var dep = New CacheDependency("", cacheKeys);
HttpRuntime.Cache.Add(itemDescriptor, ret, dep ...);
Caching in ASP.NET looks like it uses some kind of associative array:
// Insert some data into the cache:
Cache.Insert("TestCache", someValue);
// Retrieve the data like normal:
someValue = Cache.Get("TestCache");
// But, can be done associatively ...
someValue = Cache["TestCache"];
// Also, null checks can be performed to see if cache exists yet:
if(Cache["TestCache"] == null) {
Cache.Insert(PerformComplicatedFunctionThatNeedsCaching());
}
someValue = Cache["TestCache"];
As you can see, performing a null check on the cache object is very useful.
But I would like to implement a cache clear function that can clear cache values
where I don't know the whole key name. As there seems to be an associative
array here, it should be possible (?)
Can anyone help me work out a way of looping through the stored cache keys and
performing simple logic on them? Here's what I'm after:
static void DeleteMatchingCacheKey(string keyName) {
// This foreach implementation doesn't work by the way ...
foreach(Cache as c) {
if(c.Key.Contains(keyName)) {
Cache.Remove(c);
}
}
}
Don't use a foreach loop when removing items from any collection type- the foreach loop relies on using an enumerator which will NOT allow you to remove items from the collection (the enumerator will throw an exception if the collection it is iterating over has items added or removed from it).
Use a simple while to loop over the cache keys instead:
int i = 0;
while (i < Cache.Keys.Length){
if (Cache.Keys(i).Contains(keyName){
Cache.Remove(Cache.Keys(i))
}
else{
i ++;
}
}
An other way to do it in .net core:
var keys = _cache.Get<List<string>>(keyName);
foreach (var key in keys)
{
_cache.Remove(key);
}