I have this Context with the following method override :
public class MyContext : DbContext
{
public DbSet<Contract> Contracts{get;set;}
public override int SaveChanges()
{
Contract ctr = new Contract
{
ContractId = "CT99999991",
ContractNumber = "9000",
LastModifiedDate = DateTime.Now,
GracePeriod = DateTime.Now,
ShipByDate = DateTime.Now,
ExpirationDate = DateTime.Now
};
this.Contracts.Add(ctr);
return base.SaveChanges();
}
}
No matter what I tried, I never succeed in making this part of the code succeed.
I would love to save the upper Contract in the database on SaveChanges event occurence.
Is there something I'm overlooking ?
This isn't something I've tried to do myself, but it's possible that because you're inside the SaveChanges() method already, the context's changelist has already been created. So when you add your new Contract to the context's Contracts collection, you would have to make your context rebuild its list of changes that need to be persisted back to the database before calling base.SaveChanges()
What you should instead do is to hook into the SavingChanges event of the ObjectContext. This will allow you to save to an archive/audit table. Here is an article that explains how to do it. I've done this before and it works really well.
I was hooking into the custom Context class ( class MyContext : DbContext ) and it wasn't working.
I finally resorted to hooking into the WCF Data Service class (class MyDataService : DataService ) and it works well.
Related
I've got an aggregate for a specific type of entity which is stored in a collection inside the aggregate. Now I'd like to add a new entry of that type and update the aggregate afterwards, however Entity Framework never updates anything!
Model
public class MyAggregate {
protected ICollection<MyEntity> AggregateStorage { get; set; }
public void AddEntity(MyEntity entity) {
// some validation
AggregateStorage.Add(entity);
}
}
API Controller
[UnitOfWork, HttpPost]
public void UpdateMyEntity(int aggregateId, MyEntityDto dto) {
var aggregate = _aggregateRepository.Find(aggregateId);
aggregate.AddEntity(...// some mapping of the dto).
_aggregateRepository.Update(aggregate);
}
EF Update
EntitySet.Attach(aggregate);
Context.Entry(aggregate).State = EntityState.Modified;
(Please note that there's an unit of work interceptor on the API action who fires DbContext.SaveChanges() after successful execution of the method.)
Funny thing is, the update never get's executed by EF. I've added a log interceptor to the DbContext to see what's going on sql-wise and while everything else works fine, an update statement never occurs.
According to this answer in detached scenario (either aggregate is not loaded by EF or it is loaded by different context instance) you must attach the aggregate to context instance and tell it exactly what did you changed, set state for every entity and independent association in object graph.
You must either use eager loading and load all data together at the beginning and
instead of changing the state of aggregate, change the state of entities:
foreach(var entity in aggregate.AggregateStorage)
{
if(entity.Id == 0)
Context.Entry(entity).State = EntityState.Added;
}
Controller:
var d = new Repository<Department>(new MyDBEntities());
var newDepartment = new Department();
newDepartment.Name = "Software Department";
d.Insert(newDepartment); //I insert data here
Model:
public class Repository<T> where T : class
{
protected DbSet<T> DbSet;
public Repository(DbContext dataContext)
{
DbSet = dataContext.Set<T>();
}
public void Insert(T entity)
{
DbSet.Add(entity);
}
}
I need to insert data to my database.However if i insert data , it does not work , whenever i check my database, data never appears in my database, also i can not see anything like "SaveChanges();"in DbSet.
Where i miss exactly ?
Thanks
After Inserting data. I think you need to update your DBSet. go here to have some info :DbSet(TEntity)
In my understanding , you still need to call SaveChanges() from DbContext underlying your DbSet to actually save the data to database.
DbSet.Add() : Adds the given entity to the context underlying the set in the Added state such that it will be inserted into the database when SaveChanges is called. [MSDN]
Our company ships a suite of various applications that manipulate data in a database. Each application has its specific business logic, but all applications share a common subset of business rules. The common stuff is incapsulated in a bunch of legacy COM DLLs, written in C++, which use "classic ADO" (they usually call stored procedures, sometimes they use dynamic SQL). Most of these DLLs have XML-based methods (not to mention the proprietary-format-based methods!) to create, edit, delete and retrieve objects, and also extra action such as methods which copy and transform many entities quickly.
The middleware DLLs are now very old, our application developers want a new object-oriented (not xml-oriented) middleware that can be easily used by C# applications.
Many people in the company say that we should forget old paradigms and move to new cool stuff such Entity Framework. They are intrigued by the simplicity of POCOs and they would like to use LINQ to retrieve data (The Xml-based query methods of the DLLs are not so easy to use and will never be as flexible as LINQ).
So I'm trying to create a mock-up for a simplified scenario (the real scenario is much more complex, and here I'll post just a simplified subset of the simplified scenario!). I'm using Visual Studio 2010, Entity Framework 5 Code First, SQL Server 2008 R2.
Please have mercy if I make stupid mistakes, I'm a newby to Entity Framework.
Since I have many different doubts, I'll post them in separate threads.
This is the first one. Legacy XML methods have a signature like this:
bool Edit(string xmlstring, out string errorMessage)
With a format like this:
<ORDER>
<ID>234</ID>
<NAME>SuperFastCar</NAME>
<QUANTITY>3</QUANTITY>
<LABEL>abc</LABEL>
</ORDER>
The Edit method implemented the following business logic: when a Quantity is changed, an "automatic scaling" must be applied to all Orders which have the same Label.
E.g. there are three orders: OrderA has quantity = 3, label = X. OrderB has quantity = 4, label = X. OrderC has quantity = 5, label = Y.
I call the Edit method supplying a new quantity = 6 for OrderA, i.e. I'm doubling OrderA's quantity. Then, according to the business logic, OrderB's quantity must be automatically doubled, and must become 8, because OrderB and OrderA have the same label. OrderC must not be changed because it has a different label.
How can I replicate this with POCO classes and Entity Framework? It's a problem because the old Edit method can change only one order at a time, while
Entity Framework can change a lot of Orders when SaveChanges is called. Furthermore, a single call to SaveChanges can also create new Orders.
Temporary assumptions, just for this test: 1) if many Order Quantities are changed at the same time, and the scaling factor is not the same for all of them, NO scaling occurs; 2) newly added Orders are not automatically scaled even if they have the same label of a scaled order.
I tried to implement it by overriding SaveChanges.
POCO class:
using System;
namespace MockOrders
{
public class Order
{
public Int64 Id { get; set; }
public string Name { get; set; }
public string Label { get; set; }
public decimal Quantity { get; set; }
}
}
Migration file (to create indexes):
namespace MockOrders.Migrations
{
using System;
using System.Data.Entity.Migrations;
public partial class UniqueIndexes : DbMigration
{
public override void Up()
{
CreateIndex("dbo.Orders", "Name", true /* unique */, "myIndex1_Order_Name_Unique");
CreateIndex("dbo.Orders", "Label", false /* NOT unique */, "myIndex2_Order_Label");
}
public override void Down()
{
DropIndex("dbo.Orders", "myIndex2_Order_Label");
DropIndex("dbo.Orders", "myIndex1_Order_Name_Unique");
}
}
}
DbContext:
using System;
using System.Data.Entity;
using System.Data.Entity.ModelConfiguration;
using System.Linq;
namespace MockOrders
{
public class MyContext : DbContext
{
public MyContext() : base(GenerateConnection())
{
}
private static string GenerateConnection()
{
var sqlBuilder = new System.Data.SqlClient.SqlConnectionStringBuilder();
sqlBuilder.DataSource = #"localhost\aaaaaa";
sqlBuilder.InitialCatalog = "aaaaaa";
sqlBuilder.UserID = "aaaaa";
sqlBuilder.Password = "aaaaaaaaa!";
return sqlBuilder.ToString();
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new OrderConfig());
}
public override int SaveChanges()
{
ChangeTracker.DetectChanges();
var groupByLabel = from changedEntity in ChangeTracker.Entries<Order>()
where changedEntity.State == System.Data.EntityState.Modified
&& changedEntity.Property(o => o.Quantity).IsModified
&& changedEntity.Property(o => o.Quantity).OriginalValue != 0
&& !String.IsNullOrEmpty(changedEntity.Property(o => o.Label).CurrentValue)
group changedEntity by changedEntity.Property(o => o.Label).CurrentValue into x
select new { Label = x.Key, List = x};
foreach (var labeledGroup in groupByLabel)
{
var withScalingFactor = from changedEntity in labeledGroup.List
select new
{
ChangedEntity = changedEntity,
ScalingFactor = changedEntity.Property(o => o.Quantity).CurrentValue / changedEntity.Property(o => o.Quantity).OriginalValue
};
var groupByScalingFactor = from t in withScalingFactor
group t by t.ScalingFactor into g select g;
// if there are too many scaling factors for this label, skip automatic scaling
if (groupByScalingFactor.Count() == 1)
{
decimal scalingFactor = groupByScalingFactor.First().Key;
if (scalingFactor != 1)
{
var query = from oo in this.AllTheOrders where oo.Label == labeledGroup.Label select oo;
foreach (Order ord in query)
{
if (this.Entry(ord).State != System.Data.EntityState.Modified
&& this.Entry(ord).State != System.Data.EntityState.Added)
{
ord.Quantity = ord.Quantity * scalingFactor;
}
}
}
}
}
return base.SaveChanges();
}
public DbSet<Order> AllTheOrders { get; set; }
}
class OrderConfig : EntityTypeConfiguration<Order>
{
public OrderConfig()
{
Property(o => o.Name).HasMaxLength(200).IsRequired();
Property(o => o.Label).HasMaxLength(400);
}
}
}
It seems to work (barring bugs of course), but this was an example with just 1 class: a real production application may have hundreds of classes!
I'm afraid that in a real scenario, with a lot of constraints and business logic, the override of SaveChanges could quickly become long, cluttered and error-prone.
Some colleagues are also concerned about performance. In our legacy DLLs, a lot of business logic (such as "automatic" actions) lives in stored procedures, some colleagues are worried that the SaveChanges-based approach may introduce too many round-trips and hinder performance.
In the override of SaveChanges we could also invoke stored procedures, but what about transactional integrity? What if I make changes to the database
before I call "base.SaveChanges()", and "base.SaveChanges()" fails?
Is there a different approach? Am I missing something?
Thank you very much!
Demetrio
p.s. By the way, is there a difference between overriding SaveChanges and registering to "SavingChanges" event? I read this document but it does not explain whether there's a difference:
http://msdn.microsoft.com/en-us/library/cc716714(v=vs.100).aspx
This post:
Entity Framework SaveChanges - Customize Behavior?
says that "when overriding SaveChanges you can put custom logic before and AFTER calling base.SaveChanges". But are there other caveats/advantages/drawbacks?
I'd say this logic belongs either in your MockOrders.Order class, in a class from a higher layer which uses your Order class (e.g. BusinessLogic.Order) or in a Label class. Sounds like your label acts as a joining attribute so, without knowing the particulars, I'd say pull it out and make it an entity of its own, this will give you navigation properties so you can more naturally access all Orders with the same label.
If modifying the DB to normalise out Labels is not a goer, build a view and bring that into your entity model for this purpose.
I've had to do something similar, but I've created an IPrepForSave interface, and implemented that interface for any entities that need to do some business logic before they're saved.
The interface (pardon the VB.NET):
Public Interface IPrepForSave
Sub PrepForSave()
End Interface
The dbContext.SaveChanges override:
Public Overloads Overrides Function SaveChanges() As Integer
ChangeTracker.DetectChanges()
'** Any entities that implement IPrepForSave should have their PrepForSave method called before saving.
Dim changedEntitiesToPrep = From br In ChangeTracker.Entries(Of IPrepForSave)()
Where br.State = EntityState.Added OrElse br.State = EntityState.Modified
Select br.Entity
For Each br In changedEntitiesToPrep
br.PrepForSave()
Next
Return MyBase.SaveChanges()
End Function
And then I can keep the business logic in the Entity itself, in the implemented PrepForSave() method:
Partial Public Class MyEntity
Implements IPrepForSave
Public Sub PrepForSave() Implements IPrepForSave.PrepForSave
'Do Stuff Here...
End Sub
End Class
Note that I place some restrictions on what can be done in the PrepForSave() method:
Any changes to the entity cannot make the entity validation logic fail, because this will be called after the validation logic is called.
Database access should be kept to a minimum, and should be read-only.
Any entities that don't need to do business logic before saving should not implement this interface.
The exception I am receiving is An entity object cannot be referenced by multiple instances of IEntityChangeTracker. My code is structured like so...
My context class looks like this:
public class MyContext : DbContext, IDataContext
{
public MyContext (string connectionString) :
base(connectionString)
{
}
public DbSet<AssigneeModel> Assignees { get; set; }
public DbSet<AssetAssignmentModel> AssetAssignments { get; set; }
}
public class AssigneeController : Controller
{
protected MyContext db = new MyContext(ConnectionString);
[HttpPost]
public ActionResult Import(SomeObjectType file)
{
AssigneeModel assignee = new AssigneeModel();
assignee.FirstName = "Joe";
assignee.LastName = "Smith";
// Assignees have assets, and the relationship is established via an AssetAssignmentModel entity
AssetAssignmentModel assetAssignmentModel = new AssetAssignmentModel
{
Asset = someExistingAsset,
// Assignee = assignee, // Don't establish relationship here, this object will be added to the assignee collection
}
assignee.AssetAssignments.Add(assetAssignmentModel); // Manually add object to establish relationship
db.Assignees.Add(assignee); // Add the assignee object
// Exception occurs when adding the object above
};
}
EF Version 4.1
The problem is from your Asset object, when you're getting it from the other method, you'll need to explicitly detach it from that context, before adding it to this new context. As Julie mentioned, the entity instance will carry the context with it, but the porblem wasn't with the AssigneeModel you created, but with the someExistingAsset you retrieved.
You've tagged this as EF4.1 (where I expected code first & dbcontext) but it looks like a side effect of EntityObject (edmx, objectcontext, default code gen in VS2008 & VS2010).
In that case, if you have an entity (that derives from EntityObject) and you dispose its' context without first detaching the entity, the entity instance still has an artifact of that context. So when you try to attach it to another context, it gives this message. THat was a problem with EF 3.5 and EF4 if you aren't using POCOs. I haven't had to wrestle with it in a long time but I remember the sting. :)
I'm trying to create an update method in a generic repository as a LINQ to SQL data access layer.
I have an entity like this:
[Table]
public class Product
{
[Column(IsPrimaryKey = true, IsDbGenerated = true,
DbType = "Int NOT NULL IDENTITY")]
public int Id { get; private set; }
[Column(UpdateCheck = UpdateCheck.Never)]
public string Name { get; set; }
....
}
I set Update Check = true for all the fields exept for the id as #jeff Atwood suggests in this post and I set the asModified propery in the attach method to true which i found in this post as following:
public void Update(T entity)
{
_db.GetTable<T>().Attach(entity, true);
_db.SubmitChanges();
}
but I keep getting the same exception:
An entity can only be attached as modified without original state if
it declares a version member or does not have an update check policy.
So what's the problem ???
Do you recommend any other approaches to create an update method in a generic repository except creating a timestamp column as a version number.
We used the following code in our DAO to solve the same issue:
(e.g. only, don't have the real code on me at the moment)
public void UpdateUser(tblUser user)
{
WriteDataContect.Attach
(
user,
ReadOnlyDataContext.tblUsers
.Select(o => o.UserId == user.UserId)
);
WriteDataContext.SubmitChanges();
}
ReadOnlyDataContext has TrackChanges = false;
We couldn't find another solution based on our needs, without having to write alot of plumbing code.
Modifying the database to accommodate LinqToSql's need for a timestamp column wasn't an option for us either.
The additional DB call didn't create any issues in our testing.
You can read the entity from db and copy the fields. I don't like the approach but when dealing with the similar problems we had to do it.
You never know if the object hasn't been loaded into the context before and if it was you will get an exception when attaching it anyway. At least it is the case for EF but it would be logical for linq for sql too.