I have 3 Entities (MasterDoc, Folder, User) that i link together in another Entity (Folder_User).
public class Folder_User
{
public int Id { get; set; }
public MasterDoc MasterDoc { get; set; }
public User User { get; set; }
public Folder Folder { get; set; }
}
After ive inserted an object of type Folder_User into the database, i can (on the same DbContext) query it and retreive the child objects.
public static List<Folder_User> GetAllFolder_USer(User user, DataModel dbContext)
{
List<Folder_User> list = null;
var query = from f in dbContext.Folder_User
where f.User.Id == user.Id
select f;
list = new List<Folder_User>(query);
return list;
}
But after a page-refresh (new dbcontext) when i run the same query the objects reference to MasterDoc is null.
*I have tried turn of lazy loading but nothing seems to fix it.
*Have also checked the database and the table is correctly containing a row with a MasterDoc Id.
You need to Include the MasterDoc in the query:
public static List<Folder_User> GetAllFolder_USer(User user, DataModel dbContext)
{
var query = dbContext.Folder_User.
Include(f => f.MasterDoc).
Where(f => f.User.Id == user.Id);
return query.ToList();
}
Related
What I want to do: When a user selects a product, populate a data grid with every Product. If that Product / Event combination have an associated EventProduct, fill in other pieces of the data grid with that data. If not, create a new EventProduct and default all properties to 0. On saving the event, if the EventProduct properties have changed or been populated, save that EventProduct to the DB as a new EventProduct.
My current approach:
I have three classes: Event, Product, and EventProduct as defined here (truncated).
public partial class Event
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Event()
{
EventProducts = new HashSet<EventProduct>();
}
[Key]
public int index { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<EventProduct> EventProducts { get; set; }
}
public partial class Product
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Product()
{
EventProducts = new HashSet<EventProduct>();
}
[Key]
public int index { get; set; }
[StringLength(200)]
public string name { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<EventProduct> EventProducts { get; set; }
}
public partial class EventProduct
{
public EventProduct()
{
Event = new Event();
Product = new Product();
quantity_allocated = 0;
quantity_sold = 0;
quantity_sampled = 0;
}
public int index { get; set; }
public int EventID { get; set; }
public int ProductID { get; set; }
public int? quantity_allocated { get; set; }
public int? quantity_sold { get; set; }
public decimal? quantity_sampled { get; set; }
public virtual Event Event { get; set; }
public virtual Product Product { get; set; }
}
I'm populating the table by querying and joining my Products to my EventProducts, and creating a new Associative Object which has a Product and an EventProduct in a one-to-one relationship. I'm setting my itemsource equal to the following:
public static List<ProductEventProduct> GetProductEventProduct(Event e, DatabaseModel dbContext)
{
var query = from product in dbContext.Products
join eventProduct in dbContext.EventProducts
on new { pIndex = product.index, eIndex = e.index }
equals new { pIndex = eventProduct.Product.index, eIndex = eventProduct.Event.index } into temp
from eventProduct in temp.DefaultIfEmpty()
select new ProductEventProduct
{
Product = product,
EventProduct = eventProduct
};
var dataSource = query.ToList();
foreach (ProductEventProduct entry in dataSource)
{
if (entry.EventProduct == null)
{
entry.EventProduct = new EventProduct()
{
EventID = e.index,
ProductID = entry.Product.index,
Product = entry.Product,
Event = e
};
}
}
return dataSource;
}
And when I have a single, manually input (direct into my data source) EventProduct it works as intended, and users can edit the Allocated amount (sold and sampled are locked in this view):
My problem is with saving. Right now I'm iterating through each row of the data grid, and if it's been changed or if the value is not null, create an EventProduct from that and add that EventProduct to my Database Context:
List<Associations.ProductEventProduct> entries = (List<Associations.ProductEventProduct>)EventProductDataGrid.ItemsSource;
IEnumerable<Associations.ProductEventProduct> changedEntries = entries.Where(association =>
association.EventProduct.quantity_allocated != 0 ||
association.EventProduct.quantity_sampled != 0 ||
association.EventProduct.quantity_sold != 0);
foreach (Associations.ProductEventProduct entry in changedEntries)
{
// if there are no event products in the database that have the same product and event, it's new so save it to DB
if (!(dbContext.EventProducts.Any(ep =>
ep.EventID == entry.EventProduct.EventID && ep.ProductID == entry.Product.index)))
{
dbContext.EventProducts.Add(entry.EventProduct); // line where I get the error described below
dbContext.SaveChanges();
}
else // if it is an EventProduct which exists in the database already
{
EventProduct modifyEvent = dbContext.EventProducts.Single(ep => ep.Event.index == entry.EventProduct.Event.index && ep.Product.index == entry.Product.index);
modifyEvent.quantity_allocated = entry.EventProduct.quantity_allocated;
modifyEvent.quantity_sampled = entry.EventProduct.quantity_sampled;
modifyEvent.quantity_sold = entry.EventProduct.quantity_sold;
}
}
dbcontext.SaveChanges();
But when adding my EventProduct to my DBContext, I get the error, "'A referential integrity constraint violation occurred: A primary key property that is a part of referential integrity constraint cannot be changed when the dependent object is Unchanged unless it is being set to the association's principal object. The principal object must be tracked and not marked for deletion.'". Which doesn't make sense to me, since both its references to Product and Event are populated, valid, and correct in my debugger.
I've been stuck on various pieces of this issue for days now and I know that my approach is wrong, any advice would be enormously appreciated.
I imagine your problem is that the EventProduct you are adding to your DbContext refers to an Event or Product (or both) that already exist in the database but are not currently being tracked by the DbContext. When calling dbContext.EventProducts.Add(entry.EventProduct); it has the effect that it's trying to add the entry.EventProduct.Event and entry.EventProduct.Product in the DbContext as if they are new entities.
If you know that entry.EventProduct.Event and entry.EventProduct.Product already exists in the database, then you can add them to the change tracker letting EF know that they already exist and haven't changed:
// Let EF know the entities already exist
dbContext.Set<Event>().Attach(entry.EventProduct.Event);
dbContext.Set<Product>().Attach(entry.EventProduct.Product);
// Now add the EventProduct letting it refer to the existing Event and Product
dbContext.EventProducts.Add(entry.EventProduct);
dbContext.SaveChanges();
Note: as per the documentation the entities you attach will be given the state Unchanged which means if you do have changes to the Event or Product that you want to update in the database you should instead use DbContext.Entry() and set the returned Entry.State to Modified.
I have two entities Users and Roles
public partial class User
{
public int Id { get; set; }
public string FirstName { get; set; }
public string SecondName { get; set; }
public virtual ICollection<Role> Roles { get; set; }
}
public partial class Role
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public virtual ICollection<User> Users { get; set; }
}
And i have table(RoleUser) in Database contain only userid and role id.
I want to modify the user ,Delete the rows that exist in RoleUser Table and insert new record.
When i used this following code it delete the rows in RoleUser Table and also the roles itself
public void Update(User usr)
{
var existingParent = _context.Users
.Where(p => p.Id == usr.Id)
.Include(p => p.Roles)
.SingleOrDefault();
if (existingParent != null)
{
// Update parent
_context.Entry(existingParent).CurrentValues.SetValues(usr);
// Delete children
foreach (var existingChild in existingParent.Roles.ToList())
{
if (!usr.Roles.Any(c => c.Id == existingChild.Id))
_context.Roles.Remove(existingChild);
}
}
}
The question is how to delete the records that exist in RoleUser Table and insert new records without deleting the entities itself??
This is code for remove
_context.Users.find(userId).Roles.Remove(_context.Roles.find(roleId));
_context.SaveChange();
And this is code for add
_context.Users.find(userId).Roles.Add(_context.Roles.find(roleId));
_context.SaveChange();
It will save userid and roleid to table many-to-many
Hope help you
You need to do this to remove a record only in Many to Many table. Let me know if it works for you.
IObjectContextAdapter contextAdapter = (IObjectContextAdapter)_context;
ObjectStateManager stateManager = contextAdapter.ObjectContext.ObjectStateManager;
stateManager.ChangeRelationshipState(existingParent, existingChild, "Roles", EntityState.Deleted);
_context.SaveChanges();
To add new values:
_context.Entry(existingParent).Collection("Roles").CurrentValue = values;
Where values is your list of data to add (should be IEnumerable or ICollection, so List<Role> is ok).
values must contains object linked to database.
foreach (Role entry in newValues) {
values.Add(_context.Set(typeof(Role)).Find(entry.Id));
}
I have an object with a child collection as such:
public class ClaimGroup : BaseModel
{
public int Id { get; set; }
[Required,StringLength(100)]
public string Name { get; set; }
public int Order { get; set; }
public bool Include { get; set; }
public virtual ICollection<ClaimGroupItem> Items { get; set; }
}
The ClaimGroupItem is:
public class ClaimGroupItem : BaseModel
{
[Key,Column(Order = 0),DatabaseGenerated(DatabaseGeneratedOption.None)]
public int ClaimGroupId { get; set; }
[ForeignKey("ClaimGroupId")]
public virtual ClaimGroup ClaimGroup { get; set; }
[Key,Column(Order = 1),DatabaseGenerated(DatabaseGeneratedOption.None)]
public int MenuItemId { get; set; }
[ForeignKey("MenuItemId")]
public virtual MenuItem MenuItem { get; set; }
public string ClaimValue { get; set; }
}
As you can see, it has a composite primary key: MenuItemId and ClaimGroupId.
On updating, it creates duplicates of the ClaimGroupItem objects, with POCO objects being set correctly, but then it creates dynamic proxy items with the same values, hence duplicating the objects.
E.g.:
var items = viewModel.Items.Where(c => !string.IsNullOrEmpty(c.ClaimValue));
The items collection above containts 10 ClaimGroupItemViewModel objects as shown in the image below.
However, when I map the viewModel objects to the Model objects, the collection then has 20 objects, 10 of which are proxy items as seen below:
itemToSave.Items = (from i in items
select new ClaimGroupItem
{
ClaimValue = i.ClaimValue,
MenuItemId = i.MenuItemId,
})
.ToList();
Then when the object goes to save, I get the following error:
_repository.Update<ClaimGroup>(itemToSave);
Violation of PRIMARY KEY constraint 'PK_dbo.ClaimGroupItems'. Cannot
insert duplicate key in object 'dbo.ClaimGroupItems'. The duplicate
key value is (20, 6). The statement has been terminated.
The error makes sense, EF is trying to save 10 duplicate objects.
Why is Entity Framework creating 10 new objects and hence duplicates?
Here is the code on POST that gets the whole list of 79 items in viewModel.Items, then we select just the ones with claimvalue not null. There are NO duplicates at this stage.
[HttpPost, ValidateAntiForgeryToken]
public ActionResult Group([Bind(Include = "Id,Name,Order,Include,Items")] ClaimGroupViewModel viewModel)
{
if (ModelState.IsValid)
{
ClaimGroup itemToSave = _repository.Get<ClaimGroup>(viewModel.Id);
itemToSave.Include = viewModel.Include;
itemToSave.Name = viewModel.Name;
itemToSave.Order = viewModel.Order;
var items = viewModel.Items.Where(c => !string.IsNullOrEmpty(c.ClaimValue));
// There is just 10 items in items variable at this point
itemToSave.Items = (from i in items
select new ClaimGroupItem
{
ClaimValue = i.ClaimValue,
MenuItem = new MenuItem { Id = i.MenuItemId}
})
.ToList();
_repository.Update<ClaimGroup>(itemToSave);
return RedirectToAction("Groups", new { updated = true });
}
return View(viewModel);
}
If you don't need Proxies, during construction of your DBContext set ProxyCreationEnabled = false and check. I have seen cases when we don't even need Proxies and EF by default creates one for all Entities.
I finally got it working.
It was as simple as calling the
itemToSave.Items.Clear()
method to make sure it doesn't load the old proxy objects. What a pain that was to figure out! Here is my working code. Thanks for everyone's help.
[HttpPost, ValidateAntiForgeryToken]
public ActionResult Group([Bind(Include = "Id,Name,Order,Include,Items")] ClaimGroupViewModel viewModel)
{
if (ModelState.IsValid)
{
ClaimGroup itemToSave = _repository.Get<ClaimGroup>(viewModel.Id);
itemToSave.Include = viewModel.Include;
itemToSave.Name = viewModel.Name;
itemToSave.Order = viewModel.Order;
itemToSave.Items.Clear();// This needs to be done, otherwise it will try and load the list from the DB again.
itemToSave.Items = (from i in viewModel.Items
where !string.IsNullOrEmpty(i.ClaimValue)
select new ClaimGroupItem
{
ClaimValue = i.ClaimValue,
MenuItemId = i.MenuItemId,
})
.ToList();
_repository.Update<ClaimGroup>(itemToSave);
return RedirectToAction("Groups", new { updated = true });
}
return View(viewModel);
}
I have the following classes (I am only showing the properties that matter):
public class TypeLocation
{
[Key]
public int Id {get; set;}
public string Country {get; set;}
public string State {get; set;}
public string County {get; set;}
public string City {get; set;}
public List<Offer> Offers {get; set; }
public TypeLocation()
{
this.Offers = new List<Offer>();
}
}
public class Offer
{
public int Id { get; set; }
public ICollection<TypeLocation> LocationsToPublish { get; set; }
}
This creates the following in the database:
My Question/Problem
The TypeLocations table is prepopulated with a static list of country/state/county/city records, one or many of them can be associated with the Offer in LocationsToPublish property. When I try to add a location, using the following code, Entity Framework adds a NEW record in the TypeLocation table and then makes the association by adding a record in the OfferTypeLocations table.
public static bool AddPublishLocation(int id, List<TypeLocation> locations)
{
try
{
using (AppDbContext db = new AppDbContext())
{
Offer Offer = db.Offers
.Include("LocationsToPublish")
.Where(u => u.Id == id)
.FirstOrDefault<Offer>();
//Add new locations
foreach (TypeLocation loc in locations)
{
Offer.LocationsToPublish.Add(loc);
}
db.SaveChanges();
}
return true;
}
catch
{
return false;
}
}
I don't want a new record added to the TypeLocations table, just a relational record creating an association in the OfferTypeLocations table. Any thoughts on what I am doing wrong?
Solution
Thanks to #Mick who answered below, I have found the solution.
public static bool AddPublishLocation(int id, List<TypeLocation> locations)
{
try
{
using (AppDbContext db = new AppDbContext())
{
Offer Offer = db.Offers
.Include("LocationsToPublish")
.Where(u => u.Id == id)
.FirstOrDefault<Offer>();
//Add new locations
foreach (TypeLocation loc in locations)
{
//SOLUTION
TypeLocation ExistingLoc = db.AppLocations.Where(l => l.Id == loc.Id).FirstOrDefault<TypeLocation>();
Offer.LocationsToPublish.Add(loc);
}
db.SaveChanges();
}
return true;
}
catch
{
return false;
}
}
What happens is, using the existing AppDbContext, I retrieve an existing record from the TypeLocations table (identified here as AppLocations) and then Add it to the LocationsToPublish entity.
The key is that I was to use the current AppDbContext (wrapped with the Using() statement) for all the work. Any data outside of this context is purely informational and is used to assist in the record lookups or creation that happen within the AppDbContext context. I hope that makes sense.
The TypeLocations are being loaded from a different AppDbContext, they are deemed new entities in the AppDbContext you're constructing within your method. To fix either:-
Assuming the locations were detatched them from the instance of the AppDbContext outside of your method, you can attach these entities to the new context.
OR
Pass in the AppDbContext used to load the locations into your AddPublishLocation method.
I'd choose 2:-
public static bool AddPublishLocation(AppDbContext db, int id, List<TypeLocation> locations)
{
try
{
Offer Offer = db.Offers
.Include("LocationsToPublish")
.Where(u => u.Id == id)
.FirstOrDefault<Offer>();
//Add new locations
foreach (TypeLocation loc in locations)
{
Offer.LocationsToPublish.Add(loc);
}
db.SaveChanges();
return true;
}
catch
{
return false;
}
}
I'm creating a product listing for an online store. It's pretty standard stuff, a page of product thumbnails with brief details, price and a link through to full details.
I'm using a repository pattern, so I have a central data repository which gives me back tables from a SQL server. I've cut a lot of the code out for the sake of brevity, but just so you get the idea:
public class SqlProductsRepository : IProductsRepository
{
private Table<Product> productsTable;
public SqlProductsRepository(string connectionString)
{
var context = new DataContext(connectionString);
productsTable = context.GetTable<Product>();
// More tables set up here
}
public IQueryable<Product> Products
{
get { return productsTable; }
}
// More properties here
}
I have the following objects mapped to tables:
[Table(Name = "Products")]
public class Product
{
[Column(IsPrimaryKey = true)]
public string ProductCode { get; set; }
[Column]
public string Name { get; set; }
[Column]
public decimal Price { get; set; }
public List<ShopImage> Images = new List<ShopImage>();
}
[Table(Name = "Images_Products")]
public class Image_Product
{
[Column]
public int ImageID { get; set; }
[Column]
public string ProductCode { get; set; }
[Column]
public int DisplayOrder { get; set; }
}
[Table(Name = "Images")]
public class Image
{
[Column(Name = "ImageID")]
public int ImageID { get; set; }
[Column]
public bool Caption { get; set; }
}
If I perform the following query:
// 'db' is the repository (member variable of the controller class)
IQueryable<Product> products = from p in db.Products
join ip in db.Image_Product on p.ProductCode equals ip.ProductCode
where ip.DisplayOrder == 0
select p;
I get a nice IQueryable full of Product objects. However, what I want to do is populate each object's Images list property with a single Image object, with its ID set from the joined Image_Product table.
So I end up with a list of Products, each with one Image in its Images property, which has the ID of the image for that product in the database where DisplayOrder is 0.
I tried this projection, which I thought made sense:
IQueryable<Product> products = from p in db.Products
join ip in db.Image_Product on p.ProductCode equals ip.ProductCode
where ip.DisplayOrder == 0
select new Product {
ProductCode = p.ProductCode,
Price = p.Price,
Images = new List<Image> {
new Image { ImageID = ip.ImageID }
}
};
Which compiles, but throws a runtime error: Explicit construction of entity type 'xxx.Product' in query is not allowed.
Yet elsewhere in the project I do this:
var pages = from i in db.TemplatePageNavigationItems
orderby i.DisplayOrder
select new NavigationItem {
ID = i.PageID,
ParentID = i.ParentID,
Text = i.Name,
Title = i.Name,
Url = (i.Folder == null) ? "" : i.Folder
};
And get no complaints! I assume it's something to do with the first query returning an IQueryable<Product> but I'm not sure why.
Two questions really: why is this not allowed in the first situation, and what should I be doing in order to get my desired result?
As the error says, you can't construct explicit entity types (Product is just that) in your query which should return IQueryable<Product>. Your pages query will return IEnumerable<NavigationItem> and NavigationItem does not seem to be an entity type defined in the database.
You could try returning IEnumerable<Product> in your first query or define a separate type and return IEnumerable of that instead, if you need to project explicit, custom tailored instances of an object.