SaveChanges on Many to Many relationship fails - c#

Here is my base :
And that it contains one movie (IDs comes from an external REST API) :
Movie movie = Movie.CreateMovie(999, "MyFirstMovie");
movie.Kinds.Add(Kind.Create(123,"Adventure"));
movie.Kinds.Add(Kind.Create(124,"Comic"));
movie.Actors.Add(Person.Create(321,"John Wayne"));
movie.Directors.Add(Person.Create(120,"John Woo"));
_context.AddToMovies(movie);
_context.SaveChanges();
Now, when I'm trying to insert a new movie, I got often an exception that said that I'm inserting an entity that already exists in the base.
Suppose I got another "Adventure" movie :
// Here all data comes from an external source and have no control over it.
using(Stream stream = myExternalStream)
{
Movie movie = Unserialize(stream);
_context.AddToMovies(movie);
}
// throws the exception because the kind "Adventure" already exists
_context.SaveChanges();
How can I avoid this exception?

I think you will have the check if the Kind already exists in the DB. If yes, replace the kind in the movie.Kinds collection by the kind loaded from the database. If no, keep the deserialized kind and create a new Kind entity, for example like so:
using(Stream stream = myExternalStream)
{
Movie movie = Unserialize(stream);
var newKindList = new List<Kind>();
foreach(var kind in movie.Kinds)
{
var kindInDB = _context.Kinds
.SingleOrDefault(k => k.KindId == kind.KindId);
if (kindInDB != null)
newKindList.Add(kindInDB);
else
newKindList.Add(kind);
}
movie.Kinds = newKindList;
_context.AddToMovies(movie);
}
_context.SaveChanges();

Retrieve the movie kind, in stead of creating it each time. Or is that to obvious?

Related

Why do I get the "reference not set to an instance of an object?

I am working with the new CosmosDB SDK v3 https://learn.microsoft.com/en-us/azure/cosmos-db/sql-api-sdk-dotnet-standard and a very simple insert, I have verified all the objects are indeed not null and have reasonable values but I still get the error message:
[1/12/2019 10:35:04] System.Private.CoreLib: Exception while executing function: HAPI_HM_Seasons. Microsoft.Azure.Cosmos.Direct: Object reference not set to an instance of an object.
I dont see why this is I must be missing something really basic here but I cant put my finger on it.
The code is as below:
List<SeasonInformation> seasonInformationList = new List<SeasonInformation>();
foreach(JObject document in listOfSeasons)
{
SeasonInformation seasonInformation = new SeasonInformation
{
id = Guid.NewGuid().ToString(),
Brand = brand,
IntegrationSource = source,
DocumentType = Enums.DocumentType.Season,
UpdatedBy = "HAPI_HM_Seasons",
UpdatedDate = DateTime.Now.ToString(),
UpdatedDateUtc = string.Format("{0:yyyy-MM-ddTHH:mm:ss.FFFZ}", DateTime.UtcNow),
OriginalData = document
};
seasonInformationList.Add(seasonInformation);
}
database = cosmosClient.GetDatabase(cosmosDBName);
container = database.GetContainer(cosmosDBCollectionNameRawData);
log.LogInformation(string.Format("HAPI_HM_Seasons BASIC setup done at {0:yyyy-MM-ddTHH:mm:ss.FFFZ}", DateTime.UtcNow));
log.LogInformation(string.Format("HAPI_HM_Seasons import {1} items BEGIN at {0:yyyy-MM-ddTHH:mm:ss.FFFZ}", DateTime.UtcNow, seasonInformationList.Count));
foreach(var season in seasonInformationList)
{
ItemResponse<SeasonInformation> response = await container.CreateItemAsync(season);
}
I have verified that the List is populated and that the season variable in the loop contains the correct data so I am a bit stuck here.
The exception happens in the last foreach loop where I try CreateItemAsync into CosmosDB
As a best practice, you need to use Async method with await in all the Cosmosdb methods just to make sure that they are getting executed and you get the response,
and modify your CreateItemAsync as follows,
ItemResponse<SeasonInformation> response = await container.CreateItemAsync(season, new PartitionKey(season.whatever));
Here is the Sample Repository

Getting ID of item after posting to a document library

I'm currently consuming information from Sharepoint 2010 using WCF Data Services from C# (an asp.net mvc4 application).
With regular Sharepoint lists items, when saving the changes to the context, the id of the new item is automatically populated on the original object. Meaning:
SomeContext context = /* creation of the context*/;
var someEntity = new SomeEntity {...};
context.AddToSomeEntityItems(someEntity);
context.SaveChanges();
var newId = someEntity.Id; //this will have the new id
However, if what you are creating is an item for a document library, the id doesn't seem to be updated on the saved entity. For example:
SomeContext context = /* creation of the context*/;
var someOtherEntity = new SomeOtherEntity {...};
Stream data = /* some stream*/;
context.AddToSomeOtherEntityItems(someOtherEntity);
context.SetSaveStream(someOtherEntity, data, true, "SomeMIMEType", "SomeSlugPath");
context.SaveChanges();
var newId = someOtherEntity.Id; //this will instead always have 0
I initially thought this was a bug in the first version of WCF Data Services so I updated to the latest, 5.4.0. But the behavior seems to be the same.
I would prefer to have to avoid strange lookups that ultimately could fail during heavy load, such as using .OrderByDescending(x => x.Id).First() to get the recently created item.
When looking at the actual network traffic using Fiddler, I can see that the initial upload of the binary data actually does return the information for the item along with its ID. And if I configure other relationships to that item using SetLink prior to saving changes they are linked correctly, so the context does handle the value accordingly.
Is there any way to get WCF Data Services to update the ID on the entity when the item is for a document library?
Typically, I set the Id manually before inserting it with
someOtherEntity.Id = Guid.NewGuid();
That way you can TDD it more easily as well. I think it is best to leave the responsibilities to your code than to rely upon an external storage system to tell you what the Id is.
What I am doing is parsing out the id from the response headers:
var responses = context.SaveChanges();
var created = responses.FirstOrDefault(r => r.StatusCode == (int)HttpStatusCode.Created);
if (response != null)
{
var location = response.Headers.First(h => h.Key == "Location").Value;
//location will be http://sharepoint.url/site/_vti_bin/ListData.svc/{listname}(id)
//you can parse out the id from the end of that string
}
It's not exactly elegant, but it works.

Fetching collection inside nhibernate session

One property has many photos. One photo belong to one property.
Inside my mvc controller I'm getting as parameter array of integers. These integers represents id of Photo which I want to delete.
I'm using nhibernate session and transaction to interact with db.
public ActionResult DeleteImgs(int[] data)
{
Property p = null;
using (ISession session = ....)
{
using(ITransaction transaction session.BeginTransaction())
{
Photo photo = session.Get<Photo>(data[0]);
p = session.Get<Property>(photo.Id);
// found images and delete them
foreach(int id in data)
{
Photo ph = session.Get<Photo>(id);
//remove property from association so I can delete photo
ph.Property = null;
session.Delete(ph);
session.SaveOrUpdate(ph);
}
//load property now with collection of remaining photos
// here IS THE PROBLEM, Even there is photos inside collection
// in debug I'm getting empty collection
p = session.Query<Property>().
.Fetch(x=>x.Photos).ToList() //empty?
.FirstOrDefault;
transaction.Commit();
}
}
return View();
}
Since I'm sending just IEnumrable of photos to the view problem is solved like this,
instead of sending lazy load property photos collection I'm sending IEnumerable of Photos like this
IEnumerable<Photo>photos = session.Query<Photo>().Where(x => x.Property == p).ToList();

Why is this entity relation constraint error showing?

I do :
var t = cboTrailer.SelectedItem as Trailer;
using (var db = new CAPSContainer())
{
db.Attach(t);
db.Trailers.DeleteObject(t);
db.SaveChanges();
}
This deletes a trailer object from the context and pushes those changes. But it fails and I get :
Entities in 'CAPSContainer.Trailers' participate in the 'CustomerTrailer' relationship. 0 related 'Customer' were found. 1 'Customer' is expected.
The issue is that the relationship between Customers and Trailers is like ---> Customer 1-* Trailer ... So if I delete the trailer it shouldnt be an issue.
So why the error?
EDIT:
I tried with both v4 and 4.4 dlls of EF. It seems if I attach the Customer first it all works, but closer inspection shows that even though no error occurs the trailer still remains.
var c = cboCustomer.SelectedItem as Customer;
var t = cboTrailer.SelectedItem as Trailer;
using (var db = new CAPSContainer())
{
db.Attach(c);
c.Trailers.Remove(t);
db.SaveChanges();
}
PopulateTrailers();
--> still shows in list + even after app restart its still there...
EDIT 2:
This almost works :
using (var db = new CAPSContainer())
{
db.Attach(c);
db.Attach(t);
c.Trailers.Remove(t);
db.Trailers.DeleteObject(t);
db.SaveChanges();
}
It gives (on 2nd delete) :
An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.
Its crazy that in the same project just before when I was using a single object context I did ..
MyContext.DeleteObject(t) and that was it. Now that same line is replaced with 5 lines and still there is an error.
You may need to attach the customer that relates to the trailer you are deleting so that EF can remove the relationship as well.
Edit
In addition to removing the trailer from the Customer, you need to delete it from the context as well. Removing it from the customer only removes the relationship.
var c = cboCustomer.SelectedItem as Customer;
var t = cboTrailer.SelectedItem as Trailer;
using (var db = new CAPSContainer())
{
db.Attach(c);
c.Trailers.Remove(t);
db.Trailers.DeleteObject(t);
db.SaveChanges();
}
PopulateTrailers();
When you load the source elements of the cboCustomer, do you include the Trailer?
like:
cboCustomer.Items = db.Customers.Include("Trailers").ToList();
and then delete the elements with:
using (var db = new CAPSContainer())
{
db.Attach(c);
c.Trailers.Remove(t);
db.SaveChanges();
}

How to save changes in Linq-to-SQL?

So, here is my hopefully unique spin on this common problem.
I do my query, get my objects then pass the object into a form where it populates the form with the data from the object (this is not passed in by reference).
I then edit the values of the object that was queried (via the form) and then return a new object constructed from the values in the form.
I then want to update this to the database. Attach does nothing (runs but does not update). SubmitChanges also does nothing (and both do nothing when used together).
What am I missing?
Update: here is the code I am using:
// In constructor
_dataMap = new DataMapDataContext();
_addresses = _dataMap.AddressItems
.Where(address => address.InsertUserName == _currentUser.Name).ToList();
public void EditButtonClick()
{
using (AddAddressForm form = new AddAddressForm(_addresses[_currentAddress]))
{
form.Text = "Edit Address";
if (DialogResult.OK == form.ShowDialog())
{
_addresses[_currentAddress] = form.Item;
_dataMap.SubmitChanges();
DisplayItem();
}
}
}
You'll need to get the record from the database, update it's values and then call SubmitChanges()
using(MyDataContext db = new MyDataContext())
{
// get the record
Product dbProduct = db.Products.Single(p => p.ID == 1);
// set new values
dbProduct.Quantity = 5;
dbProduct.IsAvailable = false;
// save them back to the database
db.SubmitChanges();
}
Turns out I was doing almost everything right.
I just needed to pass in the object I was editing by reference. That way when it got changed, it was not a new object that was returned, but the same one (that Linq-to-SQL already knew about.)
These are the two lines from the code above that got changed:
AddressItem itemToEdit = _addresses[_currentAddress];
using (AddAddressForm form = new AddAddressForm(ref itemToEdit))

Categories