Refresh EF 6 DBContext - c#

I am working with Entity Framework 6 and I have an product object that has a list of variants like so:
public class Product
{
public int ProductId { get; set;}
public virtual ICollection<Variant> Variants { get; set;}
... other properties
}
public class Variant
{
public int VariantId { get; set;}
public int ProductId { get; set;}
public virtual Product Product { get; set;}
public int StoreId { get; set;}
... other properties
}
And I use this to get the products from the context:
public static GetProducts()
{
using (MyDBContext context = new MyDBContext())
{
return context.Products.Include(p => p.Variants);
}
}
Now this all works fine and when I get the Product it comes back with the variants. However, this morning I foolishly used the Product from the context instead of a DTO and filtered the variants based on a StoreId and now whenever I get a Product, it only returns the variants for that store (even though I never committed any changes).
I have checked the db and all the variants are still there so how do I reset my context so that I get all variants again.
I have tried the following:
resetting iis
cleaning and rebuilding the solution
changing the object to return an extra property
reloading the product using:
foreach (Product product in context.Products)
{
context.Entry(product).Reload();
}
But nothing seems to work, is there anything else I need to do to reset the context?

As it turns out it was a config error with the variant entity rather than the filtered context. I was only using the VariantID as the Key but as there were multiple variants with the same id, it was overriding the following variants with the same values as the first one with that id when it was mapping the objects back after the query (making it look as if there was only the store I had filtered).
I fixed this by making the key unique to the store and the variant id:
HasKey(v => new {v.VariantId, c.StoreId});

Related

Use JSON data set as seed for EntityFramework database

I'm trying to expose static objects using Microsoft.EntityFrameworkCore in a .netcoreapp 2.1.
The data I want to expose is in a .json file, and I can deserialize it without any issue into its corresponding c# classes.
Here's a sample of the structure : https://pastebin.com/SKCKsDJi
For the sake of clarity i suggest you read it using your favourite json reader
And here are the c# version of those objets :
public class FoodItem
{
public int Id { get; set; }
public string Name { get; set; }
public FoodType Type { get; set; }
public string Picture { get; set; }
public float Price { get; set; }
public string Currency { get; set; }
public IEnumerable<Ingredient> Ingredients { get; set; }
public bool IsVegetarian { get; set; }
}
public class FoodType
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Ingredient
{
public int Id { get; set; }
public string Name { get; set; }
}
Lets keep it simple though, there are ingredients, types and items, items are basically sandwiches, which are of a certain type and contain a list of ingredients. All 3 have id's to match them. This is where my problem lies, or so I think.
Everything works fine if I'm just using "Types" for example, in my dbcontext. As soon as I try to add either ingredients or items, or all 3 (which I need, but baby steps), I have the following error.
InvalidOperationException: The instance of entity type 'Ingredient' cannot be tracked because another instance with the key value '{Id: 1}' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.
This is caused when :
public EatupController(EatupContext context)
{
_context = context;
var completeModel = JsonConvert.DeserializeObject<EatUpDataModel>(EatUpDataSet.Complete);
_context.Types.AddRange(completeModel.Types); //Works
_context.Ingredients.AddRange(completeModel.Ingredients); //Crashes here.
//If removed, all is fine but data is incomplete
//_context.Types.AddRange(completeModel.Types); //Unused
//_context.Items.AddRange(completeModel.Items); //Unused
_context.SaveChanges();
}
I don't understand why it's complaining about duplicate ID's, because they're all identical. Except, when I'm referencing the ingredient X in an item, obviously some items will use ingredients used by other items (many sandwiches have tomatoes). But surely that type of relation is allowed.
At first I had id's starting at 0 for all different types of objets, so ingredients ranged from 0 to about 100, items from 0 to 60, and types from 0 to 7. But since I had that error I edited all Id's and I still have the error, which is very confusing.
From what I read, this might also be due to using the context in different threads but this is not the case. If I remove the line that crashes, it stops crashing and I can see the data in the context correctly. In this case, only the types. If I add only the items or the ingredients in the context, it crashes for the same reason, just in another object (ingredient or item).
Where should I go from here? I don't even have a bad solution I could try to implement. My worst idea was to manually change the Id's (which is silly to me, it should work with the older ones), but even that failed.

Adding records in multiple table using EF6 code first

I am using EF6 code first approach, i have 3 tables:
ProductPromotionalOffersPromotionalOfferProduct
I have few products already added inside the products table, now i want that on adding new promotional offer, i should be able to link some products (more than 1) to promotionalofferproducts, but in my case it adds new product
here are my Entities
Product
public class Product
{
[Key]
public long ProductId { get; set; }
[Required, StringLength(100)]
public string Name { get; set; }
}
PromotionalOffer
public class PromotionalOffer
{
[Key]
public long PromotionalOfferID { get; set; }
[Required]
[StringLength(100)]
public string Name { get; set; }
public virtual List<Product> Products { get; set; }
public void Add()
{
db.PromotionalOffers.Add(this);
db.SaveChanges();
}
}
The 3rd table was automatically created by EF based on List<Product> products in PromotionalOffer class.
And below is my client code:
IPromotionalOffer pOffer = Factory.Instance.Create<IPromotionalOffer>();
Domain.Product p = new Domain.Product
{
ProductId = 1,
Name = "Colgate",
};
pOffer.Name = "Holi";
pOffer.Products.Add(p);
pOffer.Add();
Though this adds entry in PromotionalOfferProducts it also creates entry in Products table (though it should not add in Product table).
The Problem is you are adding a new product
because you create a new instance of Product, instead of using an existing one.
So to solve this, Load your product with entityframework, instead of instantiating your own instance of product.
so in general entityframework terms if you did not use dependecy injection container something like this:
var loadedProduct = dbContext.Product.Find(yourProductId);
IPromotionalOffer pOffer = Factory.Instance.Create<IPromotionalOffer>();
pOffer.Name = "Holi";
pOffer.Products.Add(loadedProduct);
pOffer.Add();
the reason is, your entity classes is not actually used by entityframework directly, entityframework inherits from your product class and make its own class that it can add more properties to, in order to track the entity. If you create a new instance yourself, entityframework cant track it, and thus assumes its a new product, instead of an existing one, since it is tracking the existing ones that are being loaded.
More ideally i think you need to change your entity mappings so that you can do this the other way around, thus load your product from entity framework, and then add a promotionalOffer to your product, instead of adding a product to your promotional offer.

Entity Framework Core: remove a relationship but not delete the entities

How would you delete a relationship assuming you had the 2 entities, but did not have the 'relationship' entity?
Assuming the following entities...
Model classes:
public class DisplayGroup
{
[Key]
public int GroupId { get; set; }
public string Description { get; set; }
public string Name { get; set; }
public ICollection<LookUpGroupItem> LookUpGroupItems { get; set; }
}
public class DisplayItem
{
[Key]
public int ItemId { get; set; }
public string Description { get; set; }
public string FileType { get; set; }
public string FileName { get; set; }
public ICollection<LookUpGroupItem> LookUpGroupItems { get; set; }
}
public class LookUpGroupItem
{
public int ItemId { get; set; }
public DisplayItem DisplayItem { get; set; }
public int GroupId { get; set; }
public DisplayGroup DisplayGroup { get; set; }
}
Here is the code for deleting a relationship. Note: I do not want to delete the entities, they just no longer share a relationship.
public void RemoveLink(DisplayGroup g, DisplayItem d)
{
_dataContext.Remove(g.LookUpGroupItems.Single(x => x.ItemId == d.ItemId));
}
The method above causes an error:
System.ArgumentNullException occurred
Message=Value cannot be null.
It looks like this is the case because LookUpGroupItems is null, but these were called from the database. I would agree that I do not want to load all entity relationship objects whenever I do a Get from the database, but then, what is the most efficient way to do this?
Additional NOTE: this question is not about an argument null exception. It explicitly states how to delete a relationship in Entity Framework Core.
The following is not the most efficient, but is the most reliable way:
public void RemoveLink(DisplayGroup g, DisplayItem d)
{
var link = _dataContext.Find<LookUpGroupItem>(g.GroupId, d.ItemId); // or (d.ItemId, g.GroupId) depending of how the composite PK is defined
if (link != null)
_dataContext.Remove(link);
}
It's simple and straightforward. Find method is used to locate the entity in the local cache or load it the from the database. If found, the Remove method is used to mark it for deletion (which will be applied when you call SaveChanges).
It's not the most efficient because of the database roundtrip when the entity is not contained in the local cache.
The most efficient is to use "stub" entity (with only FK properties populated):
var link = new LookUpGroupItem { GroupId = g.GroupId, ItemId = d.ItemId };
_dataContext.Remove(link);
This will only issue DELETE SQL command when ApplyChanges is called. However it has the following drawbacks:
(1) If _dataContext already contains (is tracking) a LookUpGroupItem entity with the same PK, the Remove call will throw InvalidOperationException saying something like "The instance of entity type 'LookUpGroupItem' cannot be tracked because another instance with the key value 'GroupId:1, ItemId:1' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached."
(2) If database table does not contain a record with the specified composite PK, the SaveChanges will throw DbUpdateConcurrencyException saying "Database operation expected to affect 1 row(s) but actually affected 0 row(s). Data may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=527962 for information on understanding and handling optimistic concurrency exceptions." (this behavior is actually considered a bug by many people including me, but this is how it is).
Shorty, you can use the optimized method only if you use short lived newly create DbContext just for that operation and you are absolutely sure the record with such PK exists in the database. In all other cases (and in general) you should use the first method.

Can I prevent EF CF from trying to modify an 'Identity' column in database? Getting "Modifying a column with the 'Identity' pattern is not supported."

I have a one-to-many relationship in my domain similar to the following:
public class Movie
{
public int MovieID { get; set; }
public string MovieName { get; set; }
public virtual ICollection<MovieCategory> Categories { get; set; }
}
public class MovieCategory
{
[Key, ForeignKey("Movie")]
public int MovieID { get; set; }
[Key, ForeignKey("Category")]
public int CategoryID { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public DateTime DateCreated { get; set; }
public virtual Movie Movie { get; set; }
public virtual Category Category { get; set; }
}
The DateCreated field is being set in SQL via GETDATE() upon inserting new movie (and checking off some categories in a bunch of checkboxes). That part works fine.
However, when editing a movie, I need to be able to update the selection of movie categories (which means adding some and/or removing others, based on what the user has done with the selection in checkboxes). I tried this:
public ActionResult Edit(MovieEditViewModel model)
{
var movie = db.Movies.Find(model.MovieID);
//clear out all previously selected categories
movie.Categories.Clear();
//add the list of selected categories from just-edited movie
foreach(var catID in model.selectedCategories)
{
movie.Categories.Add(new MovieCategory { CategoryID = catID });
}
db.Entry(movie).State = EntityState.Modified;
db.SaveChanges();
}
I guess EF is smart enough to figure out which categories need to be deleted, which ones need to be added, and which ones need to be modified.
The only problem is, for the ones it's modifying, it tries to modify every field, including the DateCreated field, and since that one is only supposed to be touched during insert (i.e. 'Identity'), it causes this error:
"Modifying a column with the 'Identity' pattern is not supported. Column: 'DateCreated'. Table: 'CodeFirstDatabaseSchema.MovieCategory'."
Any ideas how I can get past this? All I'm trying to do is allow somebody to edit a movie, where one may or may not modify the list of movie categories during the edit, and the table MovieCategory in DB needs to reflect the edited selection accurately.
I also tried adding this, but no change:
foreach(var category in movie.Categories)
{
db.Entry(category).Property(c => c.DateCreated).IsModified = false;
}
DatabaseGeneratedOption.Identity is for identity columns. These are auto generated as well, but they're never intended to be modified because these columns are commonly used as primary keys.
You should change the option to
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime DateCreated { get; set; }
When you do this, EF will ignore the property in insert and update statements and read its value from the database right after these statements.
If you do this, your code should run fine.
Side note, the statement
db.Entry(movie).State = EntityState.Modified;
is redundant, because it only affects the movie's scalar properties, not its Categories. And movie itself is not modified.

Fluent NHibernate returns an IList<T> of NULL records

Ok, I have done just about everthing that I can think of. I have been all over the search engine of choice for the last 2 days and have yet to find a solution for this.
UPDATE:: I have even gone so far as to flatten the classes as seen below.
Here's what I have...
NOTE:: Class names and property names changed due to sensitivity of data!
I have an entity object named "MyData". It looks like this...
public class MyData
{
public virtual int id { get; set;}
public virtual int StepId { get; set;}
public virtual Decimal ProjectedValue { get; set;}
public virtual String Stage { get; set;}
public virtual String CreatedBy { get; set;}
public virtual DateTime CreatedDate { get; set;}
public virtual int RunId { get; set;}
public virtual Int32 DataKey { get; set;}
public virtual DateTime ForecastDate { get; set;}
public virtual String UnitMeasure { get; set;}
public virtual String FixedFlag { get; set;}
public virtual String DataSource { get; set;}
public virtual String ResourceType { get; set;}
public virtual String DataType { get; set;}
public override bool Equals(object obj)
{
//Not implemented
return false;
}
public override int GetHashCode()
{
return base.GetHashCode();
}
public MyData()
{
}
}
Here is the code being used for the map.
Table("MYDATA");
CompositeId()
.KeyProperty(mtm => mtm.RunId, "RUN_ID")
.KeyProperty(mtm => mtm.DataKey, "CC_KEY")
.KeyProperty(mtm => mtm.ForecastDate, "FORECAST_DATE")
.KeyProperty(mtm => mtm.UnitMeasure, "UOM")
.KeyProperty(mtm => mtm.FixedFlag, "FIXED_FLAG")
.KeyProperty(mtm => mtm.DataSource, "DATA_SOURCE")
.KeyProperty(mtm => mtm.ResourceType, "RESOURCE_TYPE")
.KeyProperty(mtm => mtm.DataType, "DATA_TYPE")
Map(mtm => mtm.StepId, "STEP_ID").Not.LazyLoad();
Map(mtm => mtm.ProjectedValue, "PROJECTED_VALUE");
Map(mtm => mtm.Stage, "STAGE").Not.LazyLoad();
Map(audit => audit.CreatedBy, "CREATED_BY").Not.Nullable();
Map(audit => audit.CreatedDate, "CREATED_DATE");
This class has met the requirements for mapping by NHibernate and Fluent. We are converting NHibernate to Fluent.
When I add the two restrictions to the ICriteria object and call the method to get the data back, I get (in this particular example) over 15000 records back but they are all NULL. There are no properties, no values, nothing. However, the number of returned rows are the EXACT SAME as if I run the select in the database. Here is the code that I use to select the data from the app. Please keep in mind that this exact same code works fine for other objects as we are actually reusing this method!
ICriteria c = _session.CreateCriteria(typeof(T));
foreach (string searchField in searchCriteria.Keys)
{
c.Add(Restrictions.Eq(searchField, searchCriteria[searchField]));
}
IList<T> l = c.List<T>();
When I step through the code, the collection "l" has the rows, but they are all NULL objects. They are, however, of Type.
A couple of final closing thoughts...
Other objects in the code base use this same ICriteria object and return data fine.
The NHibernate mapping of this object matches the Fluent mapping identically.
This application containing this code runs quite well, especially for the amount of
data being consumed and created.
This database table in Oracle has NO PK. (I didn't design it, I just inherited it!)
HELP!! I am completely confused by this return and can't find anything wrong.
I finally figured it out after 3 days of thinking that I'd done something wrong.
Basically, the problem existed in the mapping of the CompositeId object. The objects listed above were edited in an attempt to remove the CompositeId object all together. Instead of ID being a simple INT, it was an object containing 8 other properties.
Well, the CC_KEY column (DataKey property) for the specified RunId was set to NULL in the database. When I tried to query the object and create a compositeKey, the null object was causing the failure on EACH row of the incoming data. This prevented FluentNHibernate from creating the CompositeId object. No Id, no object! This allowed the system to return me the correct number of results but, when attempting to create each object with a null CompositeKey.KeyColumn value, it failed.
Long story short, if you're creating a compositeId for a mapped object, make SURE that all of your data exists and there are no NULL values!

Categories