I have 2 tables: an Orders table and an OrderActivity table. If no activity has been taken on an order, there will be no record in the OrderActivity table. I currently have the OrderActivity table mapped as an optional nav property on the Order entity and I handle updates to OrderActivity like this:
if (order.OrderActivity == null)
{
order.OrderActivity = new OrderActivity();
}
order.OrderActivity.LastAccessDateTime = DateTime.Now;
Is it possible to consolidate this such that the columns of the OrderActivity table are mapped to properties on the Orders entity, and will default if there is no OrderActivity record? Configuration for entity splitting only appears to work if records exist in both tables. If it is not possible, what is the best practice to obscure the child entity from my domain model? My goal is to keep the model as clean as possible while interacting with a DB schema that I have no control over.
You can create the mapping and specify the type of LastAccessDate as Nullable<DateTime>. The mapping will create one-to-one with LastAccessDate being optional.
public class Order {
[Key]
public int Id { get; set; }
public string Name { get; set; }
public DateTime? LastAccessDate { get; set; }
}
modelBuilder.Entity<Order>().Map(m => {
m.Properties(a => new { a.Id, a.Name });
m.ToTable("Order");
}).Map(m => {
m.Properties(b => new { b.Id, b.LastAccessDate });
m.ToTable("OrderActivity");
});
In this case, specifying LastAccessDate property is optional when inserting new orders.
var order = new Order();
order.Name = "OrderWithActivity";
order.LastAccessDate = DateTime.Now;
db.Orders.Add(order);
db.SaveChanges();
order = new Order();
order.Name = "OrderWithoutActivity";
db.Orders.Add(order);
db.SaveChanges();
Note this will always create one entry in each table. This is necessary because EF creates INNER JOIN when you retrieve Orders and you want to get all orders in this case. LastAccessDate will either have a value or be null.
// Gets both orders
var order = db.Orders.ToList();
// Gets only the one with activity
var orders = db.Orders.Where(o => o.LastAccessDate != null);
Related
Is there a way to automatically enforce parent entity to be timestamped as having been modified, if any of its dependent child items are added/deleted/modified? The key word is automatically. I know this can be done by manipulating the DbEntry's EntityState or by manually setting the timestamp field in the parent, but I need this done on a number of parent-child entities in a system, so the desire is to have EF (or a related component) automatically do this somehow.
More Background and Examples
Let's say we have an Order and Order Items (1-many). When order items are added/removed from an order, the parent order itself needs to be updated to store the last modified timestamp.
public interface IModifiableEntity
{
DateTime LastModifiedOn { get; set; }
}
public class Order : IModifiableEntity
{
// some Order fields here...
// timestamp for tracking when the order was changed
public DateTime LastModifiedOn { get; set; }
// list of order items in a child collection
public ICollection<OrderItem> OrderItems { get; set; }
}
public class OrderItem
{
public int OrderId { get; set; }
// other order item fields...
}
Somewhere in application logic:
public void AddOrderItem(OrderItem orderItem)
{
var order = _myDb.Orders.Single(o => o.Id == orderItem.OrderId);
order.OrderItems.Add(orderItem);
_myDb.SaveChanges();
}
I already have a pattern in place to detect modified entities and set timestamps automatically via EF's SaveChanges, like this:
public override int SaveChanges()
{
var timestamp = DateTime.Now;
foreach (var modifiableEntity in ChangeTracker.Entries<IModifiableEntity>())
{
if (modifiableEntity.State == EntityState.Modified)
{
modifiableEntity.Entity.UpdatedOn = timestamp;
}
}
return base.SaveChanges();
}
That works great if any direct fields on an IModifiableEntity are updated. That entity's state will then be marked as Modified by EF, and my custom SaveChanges() above will catch it and set the timestamp field correctly.
The problem is, if you only interact with a child collection property, the parent entity is not marked as modified by EF. I know I can manually force that via context.Entry(myEntity).State or just by manually setting the LastModifiedOn field when adding child items in application logic, but that wouldn't be done centrally, and is easy to forget.
I DO NOT want to do this:
public void AddOrderItem(OrderItem orderItem)
{
var order = _myDb.Orders.Single(o => o.Id == orderItem.OrderId);
order.OrderItems.Add(orderItem);
// this works but is very manual and EF infrastructure specific
_myDb.Entry(order).State = EntityState.Modified;
// this also works but is very manual and easy to forget
order.LastModifiedOn = DateTime.Now;
_myDb.SaveChanges();
}
Any way I can do this centrally and inform EF that a "root" entity of a parent-child relationship needs to be marked as having been updated?
I have an EF Core model that has a binary field
class SomeModel {
string Id;
string otherProperty;
byte[] blob;
};
Usually, when I query the DB, I want to return a list of this Model - and then, on subsequent calls, query just a single entity, but return the blob.
I can't see a way in either data or code first to prevent EF Core paying the cost of retrieving the blob field always.
I really want to be able to say something like:
var list = await Context.SomeModels.ToListAsync();
// later
var item = await Context.SomeModels
.Where(m=>m.Id==someId)
.Include(m=>m.blob)
.FirstOrDefaultAsync();
I think I might have to put the blobs into a 2nd table so I can force a optional join.
The only way you could get a separate loading is to move the data to a separate entity with one-to-one relationship.
It doesn't need to be a separate table though. Although the most natural choice looks to be owned entity, since owned entities are always loaded with the owners, it has to be a regular entity, but configured with table splitting - in simple words, share the same table with the principal entity.
Applying it to your sample:
Model:
public class SomeModel
{
public string Id { get; set; }
public string OtherProperty { get; set; }
public SomeModelBlob Blob { get; set; }
};
public class SomeModelBlob
{
public string Id { get; set; }
public byte[] Data { get; set; }
}
Configuration:
modelBuilder.Entity<SomeModelBlob>(builder =>
{
builder.HasOne<SomeModel>().WithOne(e => e.Blob)
.HasForeignKey<SomeModelBlob>(e => e.Id);
builder.Property(e => e.Data).HasColumnName("Blob");
builder.ToTable(modelBuilder.Entity<SomeModel>().Metadata.Relational().TableName);
});
Usage:
Code:
var test = context.Set<SomeModel>().ToList();
SQL:
SELECT [s].[Id], [s].[OtherProperty]
FROM [SomeModel] AS [s]
Code:
var test = context.Set<SomeModel>().Include(e => e.Blob).ToList();
SQL:
SELECT [e].[Id], [e].[OtherProperty], [e].[Id], [e].[Blob]
FROM [SomeModel] AS [e]
(the second e.Id in the select looks strange, but I guess we can live with that)
I have simplified the code below to show the root of the problem. My real code is using GenericRepository and UnitOfWork pattern but I get the same exception with this simplified code too.
I am using Entity Framework 6, Code First
It uses the following POCO entities
public class Order
{
public int Id {get;set;}
public virtual List<OrderProducts> OrderProducts {get;set;}
...
}
public class Product
{
public int Id {get;set;}
...
}
public class OrderProduct
{
public int OrderId {get;set;}
public int ProductId {get;set;}
public int Quantity
public virtual Order Order { get; set; }
public virtual Product Product{ get; set; }
}
The user is able to create a new product and add it to the order products on the same screen.
//Pull an order from the database:
var existingOrder = db.Orders.FirstOrDefault(x => x.Id == inputModel.OrderId);
//Iterate the OrderProductInputModels (IMs) in the Inputmodel
foreach (var orderProductIM in inputModel.OrderProductIMs )
{
var orderProduct = existingOrder.OrderProducts.SingleOrDefault(o => o.Id == orderProductIM.Id);
//if its an existing order product (already in db)
if (orderProduct != null)
{
//just update its property values
}
//if it has been added
else
{
//we need to create a new product first
var newProduct= new Product() { <set some properties> };
orderProduct= new OrderProduct()
{
Product=newProduct,
Order=existingOrder
}
//Add the OrderProduct to the order
existingOrder.OrderProducts.Add(orderProduct);
}
db.SaveChanges();
On save changes, I get the following error.
[System.Data.Entity.Infrastructure.DbUpdateConcurrencyException] = {"Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. Refresh ObjectStateManager entries."}
Why is this?
I expected entity framework to see that the existingOrders nested properties were newly added and unattached, update the order and create the new OrderProduct and Product.
Should it not be other way around in your if clause as you are checking for null ( then only it is a new order product else update. Issue is here:
//if its an existing order product (already in db)
if (orderProduct == null)
{
//just update its property values
}
//if it has been added
else
{
When you are looping around all the OrderProducts, you are constantly updating the database but the existingOrder object is not getting refreshed. Update that or add all the objects first and then update the database.
Finally solved it by creating a test project and reverse code first engineering the database. Noticed that OrderProduct entity was not generated. On inspecting the database, the primary key was not set. Once I set the primary key in the database, the issue was resolved. Thanks for all the suggestions.
I have one to many relationship between ItemPrice and ItemPriceHistory classes my mapping follows below:
public class ItemPrice
{
public long ID {get; set;}
public List<ItemPriceHistory> ItemPriceHistories { get; set; }
public ItemPrice()
{
ItemPriceHistories = new List<ItemPriceHistory>();
}
}
public class ItemPriceHistory
{
public ItemPrice ItemPrice { get; set; }
public long ID {get; set;}
public bool IsCurrent { get; set; }
}
modelBuilder.Entity<ItemPriceHistory>()
.HasRequired(h => h.ItemPrice)
.WithMany(p => p.ItemPriceHistories)
.Map(h => h.MapKey("ItemPrice_ID"));
I am trying to update previous ItemPriceHistory Entries and try to add a new ItemPriceHistory Entry.
var dbItemPrice = repo.Std.Get<ItemPrice>()
.SingleOrDefault(c => c.ID == id);
if (dbItemPrice == null)
{
return Request.CreateResponse(HttpStatusCode.NotFound);
}
//query for matching ItemPriceHistory
var dbPriceHistories = repo.Std.Get<ItemPriceHistory>()
.Include(h=>h.ItemPrice, repo.Std)
.Where(h => h.ItemPrice.ID == ItemPrice.ID)
.OrderByDescending(h => h.ModifiedDate);
#region new history entry
var newHistoryEntry = new ItemPriceHistory();
newHistoryEntry.IsCurrent = true;
newHistoryEntry.ItemPrice = dbItemPrice;
//copy the current pirce list and update it with new history entry
var currentPriceHistoryList = dbPriceHistories.ToList();
currentPriceHistoryList.ForEach(h => { h.IsCurrent = false; });
currentPriceHistoryList.Add(newHistoryEntry);
//new price list
var newHistoryList = new List<ItemPriceHistory>();
currentPriceHistoryList.ForEach(h => newHistoryList.Add(new ItemPriceHistory
{
ItemPrice = h.ItemPrice,
IsCurrent = h.IsCurrent,
}
));
#endregion
//delete current price histories
dbItemPrice.ItemPriceHistories.Clear();
// add histories to database
newHistoryList.ForEach(h => dbItemPrice.ItemPriceHistories.Add(h));
Context.SaveChanges();
When it calls SaveChanges(), I get the following error:
{"An error occurred while saving entities that do not expose foreign
key properties for their relationships. The EntityEntries property
will return null because a single entity cannot be identified as the
source of the exception. Handling of exceptions while saving can be
made easier by exposing foreign key properties in your entity types.
See the InnerException for details."}
InnerException: {"A relationship from the 'ItemPriceHistory_ItemPrice'
AssociationSet is in the 'Deleted' state. Given multiplicity
constraints, a corresponding 'ItemPriceHistory_ItemPrice_Source' must
also in the 'Deleted' state."}
I do not want to delete my ItemPrice_Source. I just want to delete current ItemPriceHistories and update previous ItemPriceHistories and add new ItemPriceHistory entry. How can I safely update ItemPriceHistory entries along with new ItemPriceHistory entry?
Thanks!
I use an example that shoudl fit into your STD generic repository class.
Looks like you are using this pattern.
Did you try something like
public virtual IQueryable<T> GetList(Expression<Func<T, bool>> predicate, bool withTracking = false)
{
if (withTracking)
return QuerySet.Where(predicate);
return QuerySet.Where(predicate).AsNoTracking();
}
// then
var iph_list = RepItemPriceHist.GetList(h=>h.ItemPrice.Id == VarID)
// psuedo code... foreach history entity entitity.Iscurrent = false;
so now these entities should be changed and managed by the statemanager.
add teh new entries....
// add histories to database
newHistoryList.ForEach(h => dbItemPrice.ItemPriceHistories.Add(h));
Context.SaveChanges();
I didnt understand why you need to CLEAR the collection
Arent you updating existing records and adding new ones ?
Good luck
Can't you just load the ItemPrice including all current ItemPriceHistories, then set the IsCurrent flag of those histories to false and add a new history with IsCurrent = true? Change tracking should update the current histories and insert a new one to the database:
var dbItemPrice = repo.Std.Get<ItemPrice>()
.Include(i => i.ItemPriceHistories, repo.Std)
.SingleOrDefault(i => i.ID == id);
if (dbItemPrice == null)
return Request.CreateResponse(HttpStatusCode.NotFound);
foreach (var history in dbItemPrice.ItemPriceHistories)
history.IsCurrent = false;
dbItemPrice.ItemPriceHistories.Add(new ItemPriceHistory { IsCurrent = true });
Context.SaveChanges();
I have two entities with a fairly standard Many to Many relationship that I created in EF 5 Code First. These are Service and ServiceItem. The Service entity contains a collection of ServiceItems and the ServiceItem contains a collection of Services. I can create, change and save data to either of the entities basic properties with no problems. When I try to add a ServiceItem to a Service or a Service to a ServiceItem it seems to work, but nothing is saved. I have verified that all the proper database tables are created, including a ServiceItemService table with the cross keys. The database ServiceItemService table doesn't get any entry when I add the items. There is no error and everything else seems to work perfectly.
I am a bit stumped and could use some help. Below are the classes.
The Service class;
public class Service
{
//Default constructor
public Service()
{
//Defaults
IsActive = true;
ServicePeriod = ServicePeriodType.Monthly;
ServicePeriodDays = 0;
ServiceItems = new Collection<ServiceItem>();
}
public int ServiceID { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public ICollection<ServiceItem> ServiceItems { get; set; }
public string TermsOfService { get; set; }
public ServicePeriodType ServicePeriod { get; set; }
public int ServicePeriodDays { get; set; }
public bool IsActive { get; set; }
}
The ServiceItem class;
public class ServiceItem
{
public ServiceItem()
{
IsActive = true;
}
public int ServiceItemID { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public ICollection<Service> Services { get; set; }
public string UserRole { get; set; }
public bool IsActive { get; set; }
}
This is the Fluent mapping I did while trying to debug this issue. The same problem happened before and after adding this mapping.
public DbSet<Service> Services { get; set; }
public DbSet<ServiceItem> ServiceItems { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Service>()
.HasMany(p => p.ServiceItems)
.WithMany(r => r.Services)
.Map(mc =>
{
mc.MapLeftKey("ServiceItemID");
mc.MapRightKey("ServiceID");
mc.ToTable("ServiceItemService");
});
}
Here is the code I use to save the Service item that includes 2-3 ServiceItems in the Service.ServiceItems collection. I have carefully verified that the ServiceItems were in the proper collection.
db.Entry(dbService).State = EntityState.Modified;
db.SaveChanges();
The dbService object doesn't seem to get affected in any way. The ServiceItems are still in the proper collection, but no update are made to the ServiceItemService database table. Any advice would be very welcome.
-Thanks
It is expected that nothing happens.
What you want to change or add is a relationship between the entities Service and ServiceItem. But you cannot manipulate relationships by setting the state of an entity to Modified. This only updates scalar and complex properties but no navigation properties (= relationships). (For example setting the state of a Service entity to Modified will mark Service.Title and Service.Description, etc. as modified and ensure that those properties are saved to the database. But it doesn't care about the content of Service.ServiceItems.)
The only exception where you can change a relationship by setting the state to Modified are Foreign Key Associations. These are associations that have foreign key properties exposed in your model entity and they can only occur for one-to-many or one-to-one associations. Many-to-many relationships are always Independent Associations which means they can never have a foreign key property in an entity. (Because the FKs are in the join table, but the join table is not an entity and "hidden" from your model classes.)
There is a way to directly manipulate relationships for a many-to-many association but it requires to go down to the ObjectContext and its RelationshipManager which is - in my opinion - pretty advanced and tricky.
The usual and straight-forward way to add and remove relationship entries to/from a many-to-many association is by just adding items to and removing items from the collections while the entities are attached to the context. EF's change tracking mechanism will recognize the changes you have done and generate the appropriate INSERT, UPDATE and DELETE statements when you call SaveChanges.
The exact procedure depends on if you also want to save Service and/or ServiceItem as new entities or if you only want to add relationships between existing entities. Here are a few examples:
service should be INSERTed, all serviceItems should be INSERTed and the relationships between the entities should be INSERTed into the join table as well:
using (var context = new MyContext())
{
var service = new Service();
var serviceItem1 = new ServiceItem();
var serviceItem2 = new ServiceItem();
service.ServiceItems.Add(serviceItem1);
service.ServiceItems.Add(serviceItem2);
context.Services.Add(service);
context.SaveChanges();
}
Adding the "root" service of the object graph is enough because EF will recognize that all other entities in the graph are not attached to the context and assume that they have to be INSERTed into the database.
service already exists and should NOT be INSERTed, all serviceItems should be INSERTed and the relationships between the entities should be INSERTed into the join table as well:
using (var context = new MyContext())
{
var service = new Service { ServiceID = 15 };
context.Services.Attach(service);
var serviceItem1 = new ServiceItem();
var serviceItem2 = new ServiceItem();
service.ServiceItems.Add(serviceItem1);
service.ServiceItems.Add(serviceItem2);
context.SaveChanges();
}
EF recognizes here (when SaveChanges is called) that service is attached but the other entities are not. No INSERT for service happens but the serviceItem1/2 will be INSERTed together with the relationship entries.
service already exists and should NOT be INSERTed, all serviceItems already exist and should NOT be INSERTed, but the relationships between the entities should be INSERTed into the join table:
using (var context = new MyContext())
{
var service = new Service { ServiceID = 15 };
context.Services.Attach(service);
var serviceItem1 = new ServiceItem { ServiceItemID = 23 };
context.ServiceItems.Attach(serviceItem1);
var serviceItem2 = new ServiceItem { ServiceItemID = 37 };
context.ServiceItems.Attach(serviceItem2);
service.ServiceItems.Add(serviceItem1);
service.ServiceItems.Add(serviceItem2);
context.SaveChanges();
}
For completeness: How to remove relationships between existing entities?
using (var context = new MyContext())
{
var service = context.Services
.Include(s => s.ServiceItems) // load the existing Items
.Single(s => s.ServiceID == 15);
var serviceItem1 = service.ServiceItems
.Single(s => s.ServiceItemID == 23); // query in memory, no DB query
var serviceItem2 = service.ServiceItems
.Single(s => s.ServiceItemID == 37); // query in memory, no DB query
service.ServiceItems.Remove(serviceItem1);
service.ServiceItems.Remove(serviceItem2);
context.SaveChanges();
}
The two relationship rows in the join table that link service 15 with serviceItem 23 and 37 will be deleted.
Alternativly instead of calling Attach you can load the existing entities from the database. It will work as well:
var service = context.Services.Single(s => s.ServiceID == 15);
And the same for existing ServiceItems.