how DbContext.AttachRange() works in this scenario - c#

I saw a book with some code like this:
public class Order
{
public int OrderID { get; set; }
public ICollection<CartLine> Lines { get; set; }
...
}
public class CartLine
{
public int CartLineID { get; set; }
public Product Product { get; set; }
public int Quantity { get; set; }
}
//Product class is just a normal class that has properties such as ProductID, Name etc
and in the order repository, there is a SaveOrder method:
public void SaveOrder(Order order)
{
context.AttachRange(order.Lines.Select(l => l.Product));
if (order.OrderID == 0)
{
context.Orders.Add(order);
}
context.SaveChanges();
}
and the book says:
when store an Order object in the database. When the user’s cart data is deserialized from the session store, the JSON package creates new objects that are not known to
Entity Framework Core, which then tries to write all the objects into the database. For the Product objects, this means that Entity Framework Core tries to write objects that have already been stored, which causes an error. To avoid this problem, I notify Entity Framework Core that the objects exist and shouldn’t be stored in the database unless they are modified
I'm confused, and have two questions:
Q1-why writing objects that have already been stored will cause an error, in the point of view of underlying database, it's just an update SQL statement that modify all columns to their current values?I know it does unnecessary works by changing nothing and rewrite everything, but it shouldn't throw any error in database level?
Q2-why we don't do the same thing to CartLine as:
context.AttachRange(order.Lines.Select(l => l.Product));
context.AttachRange(order.Lines);
to prevent CartLine objects stored in the database just as the way we do it to Product object?

Okay, so this is gonna be a long one:
1st Question:
In Entity Framework (core or "old" 6), there's this concept of "Change tracking". The DbContext class is capable of tracking all the changes you made to your data, and then applying it in the DB via SQL statements (INSERT, UPDATE, DELETE). To understand why it throws an error in your case, you first need to understand how the DbContext / change tracking actually works. Let's take your example:
public void SaveOrder(Order order)
{
context.AttachRange(order.Lines.Select(l => l.Product));
if (order.OrderID == 0)
{
context.Orders.Add(order);
}
context.SaveChanges();
}
In this method, you receive an Order instance which contains Lines and Products. Let's assume that this method was called from some web application, meaning you didn't load the Order entity from the DB. This is what's know as the Disconected Scenario
It's "disconnected" in the sense that your DbContext is not aware of their existence. When you do context.AttachRange you are literally telling EF: I'm in control here, and I'm 100% sure these entities already exist in the DB. Please be aware of them for now on!,
Let's use your code again: Imagine that it's a new Order (so it will enter your if there) and you remove the context.AttachRange part of the code. As soon as the code reaches the Add and SaveChanges these things will happen internally in the DbContext:
The DetectChanges method will be called
It will try to find all the entities Order, Lines and Products in its current graph
If it doesn't find them, they will be added to the "pending changes" as a new records to be inserted
Then you continue and call SaveChanges and it will fail as the book tells you. Why? Imagine that the Products selected were:
Id: 1, "Macbook Pro"
Id: 2, "Office Chair"
When the DbContext looked at the entities and didn't know about them, it added them to the pending changes with a state of Added. When you call SaveChanges, it issues the INSERT statements for these products based on their current state in the model. Since Id's 1 and 2 already exists in the database, the operation failed, with a Primary Key violation.
That's why you have to call Attach (or AttachRange) in this case. This effectively tells EF that the entities exist in the DB, and it should not try to insert them again. They will be added to the context with a state of Unchanged. Attach is often used in these cases where you didn't load the entities from the dbContext before.
2nd question:
This is hard for me to access because I don't know the context/model at that level, but here's my guess:
You don't need to do that with the Cartline because with every order, you probably want to insert new Order line. Think like buying stuff at Amazon. You put the products in the cart and it will generate an Order, then Order Lines, things that compose that order.
If you were then to update an existing order and add more items to it, then you would run into the same issue. You would have to load the existing CartLines prior to saving them in the db, or call Attach as you did here.
Hope it's a little bit clearer. I have answered a similar question where I gave more details, so maybe reading that also helps more:
How does EF Core Modified Entity State behave?

Related

Entity Framework Core - Modified key on owned type

While saving changes in my database, an exception with the following message is returned:
The property 'OrderId' on entity type 'Order.CustomerDeliveryDetails#CustomerDetails' is part of a key and so cannot be modified or marked as modified. To change the principal of an existing entity with an identifying foreign key first delete the dependent and invoke 'SaveChanges' then associate the dependent with the new principal.
The database is implemented with entity framework core with a 'code first' approach. Order.CustomerDeliveryDetails is an owned type (of the type CustomerDetails) of the entity Order. CustomerDetails has no property called OrderId. As I understand OrderId is a implicit key, generated by entity framework core as a shadow property.
The classes are structured as follows:
public class Order
{
public int Id { get; set; }
public CustomerDetails CustomerDeliveryDetails { get; set; }
}
[Owned]
public class CustomerDetails
{
public string Street { get; set; }
}
The object is updated as follows:
var order = await orderContext.Orders
.Where(o => o.Id== updateOrder.Id)
.FirstOrDefaultAsync();
order.CustomerDeliveryDetails.Street = updateOrder.CustomerDeliveryDetails.Street;
await orderContext.SaveChangesAsync();
What I fail to understand is how OrderId can be modified, when it can't be accessed directly in the code.
The only thing I can think of which might cause this error, is the fact that this update is being run on a timed webjob in Azure. This is hunch is supported by the fact that the update passes the related unit tests. Could this have to do with a race condition?
Update:
I'm fairly certain the error comes from some sort of race condition. The timed webjob loads a list of orders that need to be updated every 2 minutes. The update works fine as long as the list contains less then +-100 orders, but starts to fail once this list gets longer.
The webjob is probably inable to finish updating all the orders within 2 minutes if the list gets to long.
The context is added through dependency injection as follows:
serviceProvider.AddDbContext<OrdersContext>(options => options.UseSqlServer(ctx.Configuration["ConnectionString"], sqlOptions => sqlOptions.EnableRetryOnFailure()));
My best geuss is that the context is being shared between multiple calls of the webjob, which is causing the errors.
This boils down to your database relationships.Are you using database first or code first approach? How are the models defined? Whats the relationship between Order, CustomerDetails and the CustomerDeliveryDetails tables?
Please provide the code and I will be able to help you with the solution.

How to pass DbContext (EntityFramework SqlConnection) to another Method?

Issue in passing DbContext to another method i.e. for e.g:
public bool MarkCustomerForDelete(Customer customerObj)
{
using(var dbContext = new MyContext())
{
using(var dbTransaction = dbContext.Database.BeginTransaction())
{
//Clear all orders for the Given Customers
var orderList = dbContext.Orders.Where(x=>x.id == customerObj.OrderId).ToList();
CommonLogicMethod(dbContext, orderList);
//Logic
customerObj.Status = "Deleted";
// The Modification will fail over due to the Customer Object for that object is already attached to the DbContext with Previous Values
dbContext.Entry(customerObj).State = EntityState.Modified;
dbContext.SaveChanges();
dbTransaction.Commit()
return true;
}
}
}
public void DeleteOrderRelatedData(MyContext dbContext, List<Orders> orderList)
{
foreach(var entity2 in entity2List)
{
var OrderAddresses = dbContext.OrderAddresses.Where(x=>x.Id == entity2.Id).ToList();
//Now if here the dbContext has 100 Entities (Tables)
//It internally Enumerates all the entities in the Local cache i.e. dbContext.Coupons.Local has all the Records from the DB in the Local present.
}
}
Question: Why does when DbContext is passed to another method internally calls for all the data i.e. in dbContext.Customers.Local has all the Data in the Database in First-Level Cache ?
Question: How to Pass DbContext from one Method to Another (without creating above given issue) ?
This is Creating problem related to modification of the Data i.e. DeleteCustomer will fail over.
Now, if the code in the DeleteOrderRelatedData, is merged into the DeleteCustomer function, it works fine.
I added a Logs for the dbContext , and dbContext while passing it to the Function internally is calling all the Select queries related to the different Queries..
For more details, please check this Video out : Link
Tools being used :
Entity Framework 6.0
System.Data.Sqlite
PostSharp for MethodBoundary Aspect.
Sounds like your problem is something to do with cascading deletions but the wording is difficult to understand ...
The statement in your question ...
DbContext is passed to another method internally calls for all the
data
... DbContexts don't just "go and get all data" automatically, you must be triggering something that's causing it.
It sounds to me like when you are deleting your customer object EF you are manually implementing the code for a cascading delete when what you should perhaps do is just add that to the model and then remove the customer object negating the need for all this extra logic.
In other words you have said / are trying to say "when a customer is deleted, also find and remove the customers related orders".
In the code sample above you do ...
//Clear all orders for the Given Customers
var orderList = dbContext.Orders.Where(x=>x.id == customerObj.OrderId).ToList();
This is purely getting the orders by executing a "select * from orders where customerid = customer.Id"
then in the method you define below that ...
public void DeleteOrderRelatedData(MyContext dbContext, List<Orders> orderList)
... it looks like you then want to further delete all the addresses for the order. Although you don't appear to be calling that method in the sample above.
Instead you can do something like this to have EF worry about the children and grandchildren deletions for you all in the db ...
Entity Framework (EF) Code First Cascade Delete for One-to-Zero-or-One relationship
Cascading deletes with Entity Framework - Related entities deleted by EF
The Microsoft documentation for this is here ...
https://msdn.microsoft.com/en-gb/data/jj591620.aspx
EDIT:
My answer was based on what I knew EF would do out of the box, it seems that the actual problem was caused by a component not mentioned in the question, the problem was not about performing a heirarchy of actions as I had interpreted it was in fact about solving the issue of how another third party component was adversely affecting the EF behaviour.
In repsonse to the question:
How to Pass DbContext from one Method to Another (without creating above given issue) ?
... Just do it as passing a context between 2 methods will not in its own right cause the problem you were having.
...
It seems that answering this question correctly was impossible :(
Issue was due to :
I was using PostSharp, to Log the Traces using OnMethodBoundaryAspect.
Now this was using Arguments internally.
Since while Logging, it was serializing the Arguments, And this was creating the Problem.

Saving Database Entities

I have a C# program that loads a list of products from a database into a list of Product objects. The user can add new products, edit products, and delete products through my program's interface. Pretty standard stuff. My question relates to tracking those changes and saving them back to the database. Before I get to the details, I know that using something like Entity Framework or NHiberate would solve my problem about tracking adds and edits, but I don't think it would solve my problem about tracking deletes. In addition to wanting an alternative to converting a large codebase to using Entity Framework or NHiberate, I also want to know that answer to this question for my own curiosity.
In order to track edits, I'm doing something like this on the Product class where I set the IsDirty flag any time a property is changed:
class Product
{
public bool IsDirty { get; set; }
public bool IsNew { get; set; }
// If the description is changed, set the IsDirty property
public string Description
{
get
{
return _description;
}
set
{
if (value != _description)
{
this.IsDirty = true;
_description = value;
}
}
}
private string _description;
// ... and so on
}
When I create a new Product object, I set its IsNew flag, so the program knows to write it to the database the next time the user saves. Once I write a product to the database successfully, I clear its IsNew and IsDirty flags.
In order to track deletes, I made a List class that tracks deleted items:
class EntityList<T> : List<T>
{
public List<T> DeletedItems { get; private set; }
EntityList()
{
this.DeletedItems = new List<T>();
}
// When an item is removed, track it in the DeletedItems list
public new bool Remove(T item)
{
this.DeletedItems.Add(item);
base.Remove(item);
}
}
// ...
// When I work with a list of products, I use an EntityList instead of a List
EntityList<Product> products = myRepository.SelectProducts();
Each time I save a list of products to the database, I iterate through all of the products in the EntityList.DeletedItems property and delete those products from the database. Once the list is saved successfully, I clear the DeletedItems list.
All of this works, but it seems like I may be doing too much work, especially to track deleted items and to remember to set the IsNew flag every time I create a new Product object. I can't set the IsNew flag in Product's constructor because I don't want that flag set if I'm loading a Product object from the database. I'm also not thrilled with the fact that I have to use my EntityList class everywhere instead of using List.
It seems like this scenario is extremely common, but I haven't been able to find an elegant way of doing it through my research. So I have two questions:
1) Assuming that I'm not using something like Entity Framework, is there a better way to track adds, edits, and deletes and then persist those changes to the database?
2) Am I correct in saying that even when using Entity Framework or NHiberate, that I'd still have to write some additional code to track my deleted items?
In EF the DbContext object contains all of the logic to track changes to objects that it knows about. When you can SaveChanges it figures out which changes have happened and performs the appropriate actions to commit those changes to the database. You don't need to do anything specific with your object state other than inform the DbContext when you want to add or remove records.
Updates:
When you query a DbSet the objects you get are tracked internally by EF. During SaveChanges the current state of those objects are compared against their original state and those that are changed are put into a queue to be updated in the data.
Inserts:
When you add a new object to the relevant DbSet it is flagged for insertion during the SaveChanges call. The object is enrolled in the change tracking, it's DB-generated fields (auto-increment IDs for instance) are updated, etc.
Deletes:
To delete a record from the database you call Remove on the relevant DbSet and EF will perform that action during the next SaveChanges call.
So you don't need to worry about tracking those changes for the sake of the database, it's all handled for you. You might need to know for your own benefits - it's sometimes nice to be able to color changed records for instance.
The above is also true for Linq2SQL and probably other ORMs and database interface layers, since their main purpose is to allow you to access data without having to write reams of code for doing things that can be abstracted out.
is there a better way to track adds, edits, and deletes and then persist those changes to the database?
Both Entity Framework and NHibernate chose not to make entities themselves responsible for notifying nor tracking their changes*. So this can't be a bad choice. It certainly is a good choice from a design pattern's point of view (single responsibility).
They store snapshots of the data as they are loaded from the database in the context or session, respectively. Also, these snapshots have states telling whether they are new, updated, deleted or unchanged. And there are processes to compare actual values and the snapshots and update the entity states. When it's time to save changes, the states are evaluated and appropriate CRUD statements are generated.
This is all pretty complex to implement all by yourself. And I didn't even mention integrity of entity states and their mutual associations. But of course it's doable, once you decide to follow the same pattern. The advantage of the data layer notifying/tracking changes (and not the entities themselves) is that the DAL know which changes are relevant for the data store. Not all properties are mapped to database tables, but the entities don't know that.
I'd still have to write some additional code to track my deleted items?
No. Both OR mappers have a concept of persistence ignorance. You basically just work with objects in memory, which may encompass removing them from a list (either nested in an owner entity or a list representing a database table) and the ORM knows how to sync the in-memory state of the entities with the database.
*Entity Framework used to have self-tracking entities, but they were deprecated.

Unexpected behavior in entity framework

I ran into what I think is a really odd situation with entity framework. Basically, if I update an row directly with a sql command, when I retrive that row through linq it doesn't have the updated information. Please see the below example for more information.
First I created a simple DB table
CREATE TABLE dbo.Foo (
Id int NOT NULL PRIMARY KEY IDENTITY(1,1),
Name varchar(50) NULL
)
Then I created a console application to add an object to the DB, update it with a sql command and then retrieve the object that was just created. Here it is:
public class FooContext : DbContext
{
public FooContext() : base("FooConnectionString")
{
}
public IDbSet<Foo> Foo { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Foo>().ToTable("Foo");
base.OnModelCreating(modelBuilder);
}
}
public class Foo
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
}
public class Program
{
static void Main(string[] args)
{
//setup the context
var context = new FooContext();
//add the row
var foo = new Foo()
{
Name = "Before"
};
context.Foo.Add(foo);
context.SaveChanges();
//update the name
context.Database.ExecuteSqlCommand("UPDATE Foo Set Name = 'After' WHERE Id = " + foo.Id);
//get the new foo
var newFoo = context.Foo.FirstOrDefault(x => x.Id == foo.Id);
//I would expect the name to be 'After' but it is 'Before'
Console.WriteLine(string.Format("The new name is: {0}", newFoo.Name));
Console.ReadLine();
}
}
The write line at the bottom prints out "Before" however I would expect that it prints out "After". The odd thing about it is that if I run profiler I see the sql query run and if I run the query in management studio myself, it returns "After" as the name. I am running sql server 2014.
Can someone please help me understand what is going on here?
UPDATE:
It is going to the database on the FirstOrDefault line. Please see the attached screen shot from sql profiler.
So my question really is this:
1) If it is caching, shouldn't it not be going to the DB? Is this a bug in EF?
2) If it is going to the db and spending the resources, shouldn't EF update the object.
FooContext includes change tracking and caching, so the in-memory object that is returned from your query is the same instance that you added earlier. Calling SaveChanges() does clear the context and FooContext is not aware of the changes that happened underneath it in the database.
This is usually a good thing -- not making expensive database calls for every operation.
In your sample, try making the same query from a new FooContext, and you should see "After".
update
Responding to your updated question, yes, you are right. I missed before that you were using FirstOrDefault(). If you were using context.Find(foo.Id), as I wrongly assumed, then there would be no query.
As for why the in-memory object is not updated to reflect the change in the database, I'd need to do some research to do anything more than speculate. That said, here is my speculation:
An instance of the database context cannot return more than one instance of the same entity. Within a unit of work, we must be able to rely on the context to return the same instance of the entity. Otherwise, we might query by different criteria and get 3 objects representing the same conceptual entity. At that point, how can the context deal with changes to any of them? What if the name is changed to a different value on two of them and then SaveChanges() is called -- what should happen?
Given then that the context tracks at most a single instance of each entity, why can't EF just update that entity at the point at which a query is executed? EF could even discard that change if there is a pending in-memory change, since it knows about those changes.
I think one part of the answer is that diffing all the columns on large entities and in large result sets is performance prohibitive.
I think a bigger part of the answer is that it executing a simple SELECT statement should not have the potential to cause side effects throughout the system. Entities may be grouped or looped over by the value of some property and to change the value of that property at an indeterminate time and as a result of a SELECT query is highly unsound.

ASP.NET MVC with EF 4.1 Navigation properties

After days studying EF to understand (kinda..) how it works, I finally realized that I might have a big problem.
Imagine that I have two entities: Pais and UF. The relationship between them is Pais (0..1) ... (*) UF. A screenshot: http://i.imgur.com/rSOFU.jpg.
Said that, consider that I have a controller called UFController and it has actions for Edit and Create, which are just fine. My views are using the EditorFor helper (or similar ones) for inputs, so when I submit the form the controller will receive a UF object filled with all the data (automatically) with a reference to an almost-empty Pais. My view code (part of it):
#* UF attributes *#
#Html.EditorFor(m => m.Sigla)
#Html.EditorFor(m => m.Descricao)
#Html.EditorFor(m => m.CodigoIBGE)
#Html.EditorFor(m => m.CodigoGIA)
#* Pais primary key ("ID") *#
#Html.EditorFor(m => m.Pais.Codigo) // Pais id
The controller Edit action code:
[HttpPost]
public ActionResult Edit(UF uf)
{
try
{
if (ModelState.IsValid)
{
db.UFs.Attach(uf);
db.ObjectStateManager.ChangeObjectState(uf, EntityState.Modified);
db.SaveChanges();
return this.ClosePage(); // An extension. Just ignore it.
}
}
catch (Exception e)
{
this.ModelState.AddModelError("Model", e.Message.ToString());
}
return View(uf);
}
When I submit the form, this is what the action receives as uf:
{TOTALWeb.UF}
base {System.Data.Objects.DataClasses.EntityObject}: {TOTALWeb.UF}
(...)
CodigoGIA: 0
CodigoIBGE: 0
Descricao: "Foobar 2001"
ID: 936
Pais: {TOTALWeb.Pais}
PaisReference: {System.Data.Objects.DataClasses.EntityReference<TOTALWeb.Pais>}
And uf.Pais:
{TOTALWeb.Pais}
base {System.Data.Objects.DataClasses.EntityObject}: {TOTALWeb.Pais}
Codigo: 0
CodigoBACEN: null
CodigoGIA: null
CodigoIBGE: null
Descricao: null
UF: {System.Data.Objects.DataClasses.EntityCollection<TOTALWeb.UF>}
The original information (the one on the database) is uf.Pais.Codigo == 716. So, right now I'm receiving the updated information. The problem on that the controller is not upading the FK in the database.
I don't want to set the EntityState from uf.Pais to Modified because the entity itself wasn't changed (I didn't changed the information from that entry), but the relationship was.
In other words, what I'm trying to do is change the value of the FK, pointing the uf.Pais to another instance of Pais. Afaik, it's impossible to change the relationship state to Modified (throw an exception), so I'm looking for alternative solutions.
I've read a bunch of topics I've found on Google about this kind of problem, but I still didn't find a simple and elegant solution. The last ones I read here on stackoverflow:
How to work with navigation properties (/foreign keys) in ASP.NET MVC 3 and EF 4.1 code first
Strongly-Typed ASP.NET MVC with ADO.NET Entity Framework
Getting Error 3007 when I add my Entity Model
I asked a question a few days ago about a similar problem ( Entity Framework 4.1 - default EntityState for a FK? ). I didn't understand how EF works that time, so now a bunch of things look clear to me (that's why I'm opening a new question).
For the Create action I've been testing this solution (proposed by Ladislav on my other question), but it generates an additional select (which can be eventually slow for us):
// Here UF.Pais is null
db.UFs.AddObject(uf);
// Create dummy Pais
var pais = new Pais { Id = "Codigo" };
// Make context aware of Pais
db.Pais.Attach(pais); // <- Executing a SELECT on the database, which -can- be slow.
// Now make the relation
uf.Pais = pais;
db.SaveChanges();
I can replicate this for the Edit (I guess), but I don't want that additional SELECT.
So, in resume: I'm trying to use navigation properties to send data to the controller and save them directly in the database using a fast and easy way (without messing too much with the entity - these ones are simple, but we have huge and very complex ones with a lot of FKs!). My question is: there's a solution that doesn't involve executing another query in the database (a simple one)?
Thanks,
Ricardo
PS: sorry for any english mistakes and any confusions.
Update 1: using BennyM's solution (kind of..)
I tested the following code, but it doesn't work. It throws an exception: "An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key." Probably because Pais is already in the context, I guess?
I'm using a Entities (created by EF) class as context. Also, I don't know what is the method Entry, and I don't know where is it. Just for "fun", I tested this:
// Attach the Pais referenced on editedUF, since editedUF has the new Pais ID, not the old one.
Pais existingPais = new Pais { Codigo = editedUF.Pais.Codigo };
db.Paises.Attach(existingPais);
// Attach the edited UF.
db.UFs.Attach(editedUF);
// Set the correct Pais reference (ignoring the current almost-null one).
editedUF.Pais = existingPais;
// Change the object state to modified.
db.ObjectStateManager.ChangeObjectState(editedUF, EntityState.Modified);
// Save changes.
db.SaveChanges();
return this.ClosePage();
The exception is throwed when I try to attach the editedUF to the current context. I'm working with this idea right now, trying to find other solutions. Also, you're right BennyM, attaching the Pais to the context is not generating an additional SELECT. I don't know what happened that time, it really doesn't do anything with the database.
Still, this is a manual solution: I have to do that for each FK. That's what I'm trying to avoid. You see, some programmers, even if you explain 100 times, won't remember to do that with each FK. Eventually that'll come back to me, so I'm trying to avoid anything that can lead into errors (database or code ones) to make sure everyone can work without any stress. :)
I'm answering my own question because I've found a simple solution (at least in my case). My scenario uses a lot of Views for data input (which means that I have a lot of entities). I needed a simple and easy to use solution, so I deleted my entire Entities EDMX file (Ctrl+A, Delete!).
Then I decided to add again Pais and UF entities, but checking the checkbox for exposing the FK attribute. On first I though they can't work together, but they can, but you need to be a little careful on how to use it. They're now linked with navigation properties and the exposed FK.
The reason I couldn't add the FK attribute is because I was doing it manually. Using the "Update model from database" again checking the correct option it worked flawless.
In my edit view, I'm setting the ID of Pais into the FK attribute, not the Pais.Codigo. The reason why I do that is because the FK attribute is a scalar property and then I can detect changes.
This is the current view code for the Pais input (it's not exactly it, but it's similar to this):
#Html.EditorFor(m => m.PaisCodigo)
Btw, PaisCodigo is the FK. Yes, it can get a little confusing with Pais.Codigo, but we didn't decided any naming rules (yet). Any suggestions on this idea would be appreciated.
The final Edit action code is like this (I removed error processing to make it look simple!):
[HttpPost]
public ActionResult Edit(UF editedUF)
{
if (ModelState.IsValid)
{
// Attach the edited UF into the context and change the state to Modified.
db.UFs.Attach(editedUF);
db.ObjectStateManager.ChangeObjectState(editedUF, EntityState.Modified);
// Save changes.
db.SaveChanges();
// Call an extension (it's a redirect action to another page, just ignore it).
return this.ClosePage();
}
}
This is what is received when I submit the form for editedUF:
{TOTALWeb.UF}
base {System.Data.Objects.DataClasses.EntityObject}: {TOTALWeb.UF}
(...)
CodigoGIA: 0
CodigoIBGE: 0
CodigoPais: 0 <-- new Pais ID!
Descricao: "Foobar 2000"
ID: 902
Pais: {TOTALWeb.Pais}
PaisReference: {System.Data.Objects.DataClasses.EntityReference<TOTALWeb.Pais>}
Sigla: "RI"
Usuarios: {System.Data.Objects.DataClasses.EntityCollection<TOTALWeb.Usuario>}
As you can see, CodigoPais is pointing to the new Pais ID.
About the editedUF.Pais navigation property, there's a small detail. Before attaching it into the context, it's null. But, hey, after adding, this is what happens:
{TOTALWeb.Pais}
base {System.Data.Objects.DataClasses.EntityObject}: {TOTALWeb.Pais}
(...)
Codigo: 0
CodigoBACEN: 1058
CodigoGIA: 0
CodigoIBGE: null
Descricao: "Brasil"
UFs: {System.Data.Objects.DataClasses.EntityCollection<TOTALWeb.UF>}
So, it has been filled. The cost for that should be one query, but I couldn't capture it on the monitor.
In other words, just expose the FK, change it using the View and use the navigation property to make the code a little more clear. That's it! :)
Thanks everyone,
Ricardo
PS: I'm using dotConnect for Oracle as a base for the EF 4.1. We don't use SQL Server (at least for now). The "monitor" I said before was devArt's dbMonitor, so I can see all queries sent to the Oracle database. And, again, sorry for any english mistakes!
If you include the foreign keys in your model. So add a PaisId property to the UF entity, you can directly set it and it will update the association.
using (var db = new Context())
{
db.UFs.Attach(editedUF);
editedUF.PaisId = theForeignKey;
db.Entry(editedUF).State = EntityState.Modified;
db.SaveChanges();
}
Also I've tested the approach you already mentioned in your question, and I don't get that extra select when attaching an entity. So this works as well:
using (var db = new Context())
{
ExistingPais pais = new Pais{Id =theId};
db.Pais.Attach(pais);
db.UF.Attach(editedUF);
editedUF.Pais = pais;
db.Entry(editedUF).State = EntityState.Modified;
db.SaveChanges();
}
Assuming your code looks like this:
public class Pais
{
public int Id { get; set; }
public virtual ICollection<UF> UFs { get; set; }
}
public class UF
{
public int Id { get; set; }
public virtual Pais Pais { get; set; }
public int PaisId { get; set; }
}

Categories

Resources