Entity Framework 6 - DataServiceContext Detect Has Changes - c#

I have a WCF server application running Entity Framework 6.
My client application consumes OData from the server via a DataServiceContext, and in my client code I want to be able to call a HasChanges() method on the context to see if any data in it has changed.
I tried using the following extension method:
public static bool HasChanges(this DataServiceContext ctx)
{
// Return true if any Entities or links have changes
return ctx.Entities.Any(ed => ed.State != EntityStates.Unchanged) || ctx.Links.Any(ld => ld.State != EntityStates.Unchanged);
}
But it always returns false, even if an entity it is tracking does have changes.
For instance, given that I have a tracked entity named Customer, the following code always returns before calling SaveChanges().
Customer.Address1 = "Fred"
if not ctx.HasChanges() then return
ctx.UpdateObject(Customer)
ctx.SaveChanges()
If I comment out the if not ctx.HasChanges() then return line of code, the changes are saved successfully so I'm happy that the entity has received the change and is able to save it.
It seems that the change is getting tracked by the context, just that I can't determine that fact from my code.
Can anyone tell me how to determine HasChanges on a DataServiceContext?

Far out. I just read through DataServiceContext.UpdateObjectInternal(entity, failIfNotUnchanged), which is called directly from UpdateObject(entity) with a false argument.
The logic reads like:
If already modified, return; (short-circuit)
If not unchanged, throw if failIfNotUnchanged; (true only from ChangeState())
Else set state to modified. (no data checks happened)
So by the looks of it, UpdateObject doesn't care about/check the internal state of the entity, just the State enum. This makes updates feel a little inaccurate when there are no changes.
However, I think your problem is then in the OP 2nd block of code, you check your extension HasChanges before calling UpdateObject. The entities are only glorified POCOs (as you can read in your Reference.cs (Show Hidden Files, then under the Service Reference)). They have the obvious properties and a few On- operations to notify about changing. What they do not do internally is track state. In fact, there is an EntityDescriptor associated to the entity, which is responsible for state-tracking in EntityTracker.TryGetEntityDescriptor(entity).
Bottom line is operations actually work very simply, and I think you just need to make your code like
Customer.Address1 = "Fred";
ctx.UpdateObject(Customer);
if (!ctx.HasChanges()) return;
ctx.SaveChanges();
Though as we know now, this will always report HasChanges == true, so you may as well skip the check.
But don't despair! The partial classes provided by your service reference may be extended to do exactly what you want. It's totally boilerplate code, so you may want to write a .tt or some other codegen. Regardless, just tweak this to your entities:
namespace ODataClient.ServiceReference1 // Match the namespace of the Reference.cs partial class
{
public partial class Books // Your entity
{
public bool HasChanges { get; set; } = false; // Your new property!
partial void OnIdChanging(int value) // Boilerplate
{
if (Id.Equals(value)) return;
HasChanges = true;
}
partial void OnBookNameChanging(string value) // Boilerplate
{
if (BookName == null || BookName.Equals(value)) return;
HasChanges = true;
}
// etc, ad nauseam
}
// etc, ad nauseam
}
But now this works great and is similarly expressive to the OP:
var book = context.Books.Where(x => x.Id == 2).SingleOrDefault();
book.BookName = "W00t!";
Console.WriteLine(book.HasChanges);
HTH!

Is it possible you're not adding/editing your entities properly? MSDN states that you must use AddObject, UpdateObject, or DeleteObject to get change tracking to fire on the client (https://msdn.microsoft.com/en-us/library/gg602811(v=vs.110).aspx - see Managing Concurrency). Otherwise your extension method looks good.

In order for this to work, automatic change tracking must be enabled. You can find this setting in
ctx.Configuration.AutoDetectChangesEnabled
All the entity objects must also be tracked by the context ctx.
This means that they must be returned by one of ctx's methods or explicitly added to the context.
It also means that they must be tracked by the same instance of DataServiceContext. Are you somehow creating more than one context?
The model must also be configured correctly. Perhaps Customer.Address1 is not mapped to a database column. In that case, EF will not detect changes to the column.

I doubt that the datacontext in client is not the same one.so the changes is always is false.
You must be sure the Datacontext is the same one(instance), for every changes of the Datacontext. Then to detect the changes is meaningful.
Another way ,you must tracked the changes by yourself.simply using the Trackable Entities to help you tracking the changes of entities in the datacontext.
BTW. I Use the Code ' ctx.ChangeTracker.HasChanges()' to detect the changes of DataContext.
public bool IsContextDirty(DataServiceContext ctx)
{
#if DEBUG
var changed = ctx.ChangeTracker.Entries().Where(t => t.State != EntityState.Unchanged).ToList();
changed.ForEach(
(t) => Debug.WriteLine("entity Type:{0}", t.Entity.GetType()));
#endif
return ctx != null && ctx.ChangeTracker.HasChanges();
}

Related

What's the real difference between EntityState.Deleted and Remove() method? When to use each of them?

I'm kinda confused about recognizing a disconnected scenario and a connected scenario, I've searched the internet but I couldn't find any real answer to my questions, I'm kinda confused about entities tracking system, connected and disconnected scenarios, when should I use the Attach method and also in differences between using the Entry(entity).State = EntityState.Deleted and Remove(entity) method, and while I was searching about the last one, most of the time, they were thought identical, but it didn't match with the test that I did and what I expected
I just made a simple console app to test the differences, and how it works is that I make a person completely outside of the context instantiation scope and then pass it to the AddPerson method, because I think this makes a disconnected scenario, right? because the Remove method will complain about why I haven't attached the entity first, so I think that tells us that we're in a disconnected scenario, I'm not sure tho
This is the app:
class Program
{
static void Main(string[] args)
{
Person person = new Person()
{
PersonID = 1,
Name = "John",
Family = "Doe"
};
using (var context = new MyContext())
{
// Why this one requires attaching but the code below doesn't
context.Person.Attach(person);
context.Person.Remove(person);
context.SaveChanges();
// This method of deleting works fine without the entity being attached
context.Entry(person).State = EntityState.Deleted;
context.SaveChanges();
var people = context.Person.ToList();
foreach (var p in people)
{
Console.WriteLine($"PersonID: {p.PersonID} | Name: {p.Name} | Family: {p.Family}");
}
}
Console.ReadKey();
}
}
so for the Remove method, I have to Attach the entity first, otherwise, it will throw an exception, BUT when I use the Entry(person).state = EntityState.Deleted without attaching it, it works fine, and deletes the person, now why is that, isn't this a big difference? why is it not said anywhere, I've read some websites and some other similar questions on Stackoverflow too, but this wasn't said anywhere, and for the most part, these two were presumed to be the same, and do the same thing, yes they both delete the entity, but how can we describe what happened in this test, isn't this a difference between these two?
I have two questions but I think they're related to each other, so I'm just going to ask both of them here:
When does exactly a disconnected scenario happen, and how can I recognize it, does it depend on the scope of the context instantiation, or on retrieving the entity directly from the context and then modifying it (with no need to attach it), or using an entity from outside of the context (like passing it from another scope to our context as a parameter, as I did in my test)?
Why does the Remove method requires attaching but the EntityState.Deleted doesn't, but they're presumed identical? why should I even bother to attach the entity first, while setting the state to deleted works without needing to attach, so When to use each of them?
Basically, The way I assume that how all these work (with my current understanding of Entity Framework which is probably wrong) is that when you're in a disconnected scenario, you have to attach your entity first, but then setting the state to EntityState.Deleted doesn't need attaching, so then why does the Remove method exists at all, we could use the other way of deleting all the time.
EDIT:
Based on the second code block in the accepted answer, I wrote this test, to figure out how it's working, you said that the otherPersonReference is equal to having a Attach(Person) but when I first attach the person and try to use EntityState.Deleted It works then too, and it'll delete it, but you said that it would fail, I'm a little confused :s
class Program
{
static void Main(string[] args)
{
Person person = new Person()
{
PersonID = 3,
Name = "John",
Family = "Doe"
};
using (var context = new MyContext())
{
//var pr = context.Person.Single(p => p.PersonID == 3);
context.Person.Attach(person);
context.Entry(person).State = EntityState.Deleted;
context.SaveChanges();
}
Console.ReadKey();
}
}
if I uncomment the pr variable line and then comment the context.Person.Attach(person) then setting the EntityState to Deleted would fail and it'll throw an exception as expected
Setting context.Entry(person).State tells EF to start tracking the "person" instance if it isn't already tracking it. You would get an error if the DbContext was already tracking an instance for the same record.
For example, you can try the following:
var person = new Person { Id = 100 }; // assume an existing record with ID = 100;
using (var context = new AppDbContext())
{
context.Entry(person).State = EntityState.Deleted;
context.SaveChanges();
}
This works as you expect... However, if you were to have code that did this:
var person = new Person { Id = 100 }; // assume an existing record with ID = 100;
using (var context = new AppDbContext())
{
var otherPersonReference = context.Persons.Single(x => x.Id == 100);
context.Entry(person).State = EntityState.Deleted;
context.SaveChanges();
}
Your attempt to use context.Entry(person).State = EntityState.Deleted; would fail because the context is now already tracking an entity with that ID. It's the same behaviour as if you were to try and call Attach(person).
When dealing with short-lived DbContexts (such as when using using() blocks) and single entity operations, it can be reasonably safe to work with detached entity references, but this will get a lot more "iffy" once you start dealing with multiple possible entity references (I.e. working with lists or objects sharing references etc.) and/or calls across a DbContext which may already be tracking entity references from previous operations / iterations.
Edit: Working with detached references can be problematic and you need to take extra care when doing so. My general recommendation is to avoid it wherever possible. The approach I recommend when dealing with entities is that you should never pass an entity outside of the scope of the DbContext that read it. This means leveraging a ViewModel or DTO to represent entity-sourced details outside the scope of the DbContext. A detached EF Entity
can certainly work, but with a DTO it is explicitly clear that the data cannot be confused with a tracked entity. When it comes to performing operations like a Delete, you only really need to pass the ID.
For example, leveraging Automapper to help translate between DTOs and entities:
PersonDTO AddPerson(PersonDTO details)
{
if(details == null)
throw new ArgumentNullException("details");
using (var context = new AppDbContext())
{
// TODO: Add validations such as verifying unique name/dob etc.
var person = Mapper.Map<Person>(details); // Creates a new Person.
context.Persons.Add(person);
context.SaveChanges();
details.PersonId = person.PersonId; // After SaveChanges we can retrieve the new row's ID.
return details;
}
}
PersonDTO UpdatePerson(PersonDTO details)
{
if(details == null)
throw new ArgumentNullException("details");
using (var context = new AppDbContext())
{
var existingPerson = context.Persons.Single(x => x.PersonId == details.PersonId); // Throws if we pass an invalid PersonId.
Mapper.Map(details, existingPerson); // copies values from our DTO into Person. Mapping is configured to only copy across allowed values.
context.SaveChanges();
return Mapper.Map<PersonDTO>(existingPerson); // Return a fresh, up to date DTO of our data record.
}
}
void DeletePerson(int personId)
{
using (var context = new AppDbContext())
{
var existingPerson = context.Persons.SingleOrDefault(x => x.PersonId == details.PersonId);
if (existingPerson == null)
return; // Nothing to do.
// TODO: Verify whether the current user should be able to delete this person or not. (I.e. based on the state of the person, is it in use, etc.)
context.Persons.Remove(existingPerson);
context.SaveChanges();
}
}
In this example a Person entity does not ever leave the scope of a DbContext. The trouble with detached entities is that whenever passing an entity around to other methods and such, those methods might assume they are working with attached, complete or complete-able (i.e. through lazy loading) entities. Was the entity loaded from a DbContext that is still "alive" so if if the code wants to check person.Address that data is either eager loaded and available, or lazy-loadable? vs. #null which could mean the person does not have an address, or that without a DbContext or lazy loading we cannot determine whether it does or not. As a general rule if a method is written to accept an entity, it should always expect to have a complete, or complete-able version of that entity. Not a detached "maybe complete, maybe not" instance, not a "new"ed up instance of a class that has some arbitrary values populated, (rather than an entity representing a data row) and not a deserialized block of JSON coming from a web client. All of those can be typed as a "Person" entity, but not a Person entity.
Edit 2: "Complete" vs. "Complete-able"
A Complete entity is an entity that has all related entities eager loaded. Any method that accepts a Person should be able to access any property, including navigation properties, and receive the true value. If the Person has an Address, then a #null address should only ever mean that person does not have an address (if that is valid), not "that person does not have an address, or it just wasn't loaded." This also goes for cases where you might have a method that accepts an entity, which you haven't loaded, but want to substitute with a entity class populated with an ID and whatever data you might have on hand. That incomplete "entity" could find itself sent to other methods that expect a more complete entity. Methods should never need to guess at what they receive.
A Complete-able entity is an entity where any related entities within that entity can be lazy loaded if accessed. The consuming method doesn't need to determine whether properties are available or not, it can access Person.Address and it will always get an Address if that person is supposed to have one, whether the caller remembered to eager load it or not.
Where methods are using tightly scoped DbContexts (using()) if you return an entity then there is no way that you can guarantee later down the call-chain that this entity is complete-able. Today you can make the assurance that all properties are eager-loaded, but tomorrow a new relationship could be added leaving a navigation property somewhere within the object graph that might not be remembered to be eager-loaded.
Eager loading is also expensive, given to ensure an entity is "complete", everything needs to be loaded, whether the consumers ever need it or not. Lazy Loading was introduced to facilitate this, however, in many cases this is extremely expensive leading to a LOT of chatter with the database and the introduction of performance costs when the model evolves. Elements like serialization (a common problem in web applications) touch every property by default leading to numerous lazy load calls for every entity sent.
DTOs/ViewModels are highly recommended when data needs to leave the scope of a DbContext as it ensures only the data a consumer needs is loaded, but equally importantly, as a model may evolve, you avoid lazy loading pitfalls. Serializing a DTO rather than an Entity will ensure those new relationships don't come into play until a DTO is updated to actually need that data.

Is there a way to prevent Entity Framework to NEVER read entries where a property is a specific value?

I have a software that has been in the works for a while, today our client decided we NOT delete any data but instead hide them. To do this, I plan to add an "isDeleted" property to all tables and change all methods for deletion to set this property to "true" instead.
Problem is, I have 1000 times more reading than deletion, I can have a User and try to read all Comments of this User by using entity relation, I have to either add a "Where(x => !x.isDeleted)" to every single read like this or if it is possible, opt out ALL data that has isDeleted as true from being read.
Is the latter possible in any way? If not, is there an alternative to writing "Where(x => !x.isDeleted)" a thousand times?
I've looked at this problem before in the past and rolling your own solution is much more difficult than you'd initially think, mostly because it's really hard to change how Include statements load the related entities (EF doesn't really allow you to filter them).
But there is a library that can do it for you.
Filtering the read results
It can be done quite easily using the EntityFramework.DynamicFilters library. (I am not in any way affiliated with the devs, I just really like their library)
The main readme actually has an example that fits your use case:
modelBuilder.Filter("IsDeleted", (ISoftDelete d) => d.IsDeleted, false);
Essentially, it will only return results Where(d => !d.IsDeleted), which is exactly what you'd want. This filter is applied to all direct fetches and include statements, which means that those soft deleted entities are essentially non-existing as far as your domain is concerned.
This does assume that your entities all derive from a shared root which has the delete flag, which is something I'd advise you to do anyway.
Soft-deleting the entities
It's also possible to convert hard deletes into soft deletes in your database context itself, which means that you don't need to rewrite your delete code to instead update the entity (which can be a cumbersome rewrite, and it's always possible that someone forgets it here and there).
You can override the SaveChanges (and SaveChangesAsync) behavior in your context class. This allows you to find all the entities that are going to be deleted, and gives you the option to convert this into an update statement while also raising the IsDeleted flag.
It also ensures that no one can forget to soft delete. Your developers can simply hard delete the entities (when handling the code), and the context will convert it for them.
public class MyContext : DbContext
{
public override int SaveChanges()
{
ConvertHardDeleteToSoftDelete();
return base.SaveChanges();
}
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
ConvertHardDeleteToSoftDelete();
return await base.SaveChangesAsync(cancellationToken);
}
private void ConvertHardDeleteToSoftDelete()
{
var deletedEntries = ChangeTracker
.Entries<ISoftDelete>()
.Where(entry => entry.State == EntityState.Deleted)
.ToList();
foreach (var entry in deletedEntries)
{
entry.State = EntityState.Modified;
entry.IsDeleted = true;
}
}
}
Combined with the dynamic filter suggestion above, this means that such a soft deleted entity will not appear again in your application, but it will still exist in the database.

ASP.NET C# Entity Framework - How to update Foreign Key properly?

I'M quite new to this EF but I think I'M making progress. Anyway, it appears I do NOT know how to Update an object that is RELATED by a Foreign Key.
My DbRelation is:
And I'm trying to UPDATE the one members LANGUAGEID and here is the Context I invoke:
public class ManagerBase
{
private static NoxonEntities _entities = null;
public NoxonEntities Entities
{
get
{
if (_entities == null)
_entities = new NoxonEntities();
return _entities;
}
}
}
There are many things I have tried. Here is one:
1)
MemberManager currentMemberManager = new MemberManager();
var Mem = currentMemberManager.MyEntities.Member.SingleOrDefault(c => c.Id == 2);
var Lang = currentLanguageManager.Entities.Language.SingleOrDefault(c => c.Id == 1);
Mem.Language = Lang;
//Or
Mem.LanguageId = Lang.Id;
currentMemberManager.Save(Mem);
In appreach 1, I get an error like
The changes to the database were committed successfully, but an error occurred while updating the object context. The ObjectContext might be in an inconsistent state. Inner exception message: A referential integrity constraint violation occurred: The property values that define the referential constraints are not consistent between principal and dependent objects in the relationship.
2)
//Managers uses ManagerBase class as a Base class
MemberManager currentMemberManager = new MemberManager();
currentMemberManager.Save(Globals.CurrentMember.Id, Globals.CurrentLanguage.Id);
//These Global objects coming from a Http Session
//You may also say not to keep whole member object in session which I'll not after I figure this out
Here is my SAVE method and where I have the actual problem:
public void Save(Member entity)
{
var Data = base.Entities.Member.First(c => c.Id == entity.Id);
if (Data != null)
{
Data = entity;
base.Entities.SaveChanges();
}
}
}
In appreach 1, I get an error in this code in EF model edmx file
[EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=false)]
[DataMemberAttribute()]
public global::System.Int32 LanguageId
{
get
{
return _LanguageId;
}
set
{
OnLanguageIdChanging(value);
ReportPropertyChanging("LanguageId");
_LanguageId = StructuralObject.SetValidValue(value);
ReportPropertyChanged("LanguageId");
OnLanguageIdChanged();
}
}
And the error is
Object reference not set to an instance of an object.
Clearly I'm in the wrong way with EF.
Could you please help me and show how to properly Update a relation Id (ForeignKeyId) ?
Thank you so much.
Yes, you are using EF in many wrong ways. First, as Arran points out, you should never make your data context static. I know that seems easier, but it's a huge problem because data contexts are designed to be created and destroyed frequently.
If you don't, then the object graph continues to grow until the app pool is eventually exhausted, plus you can run into all kinds of problems with concurrent access. Static objects are shared between all threads, so imagine if two users are using your app at the same time, they will both be using the same data context and would stomp all over each other.
Second, you're using a singleton, which is one of the most reviled patterns there are, for a lot of reasons. They have their uses, but they're far more rare than most people use them.
Third, based on the error messages, it sounds like your data model may not be in sync with your EF model, and thus it's getting confused and throwing exceptions. Have you made changes to your database structure without updating your ef model? Bear in mind that regenerating doesn't always update everything, and sometimes you have to either update it manually, or delete your objects and re-add them.
Fourth, your real problem (trust me, the ones I've laid out are also real problems, that WILL bite you in the rear at some point) lies in this code:
var Data = base.Entities.Member.First(c => c.Id == entity.Id);
if (Data != null)
{
Data = entity;
base.Entities.SaveChanges();
}
The first thing you have to realize is that EF returns tracked objects, these objects have references in EF and track the changes that occur. What you are doing here is getting an object, and then throwing it away and replacing it with a non-tracked object that was passed in.
You have to update the object returned from the First method, and not replace it with a new one.

EF 4: How to properly update object in DbContext using MVC with repository pattern

I'm trying to implement an AuditLog using the DBContext's ChangeTracker object, I ran into an issue where the DbEntityEntry.OriginalValues were getting wiped out and replaced with the DbEntityEntry.CurrentValues. It was brought to my attention that the problem was how I was updating the object that was being tracked in the DbContext (original post: Entity Framework DbContext SaveChanges() OriginalValue Incorrect).
So now I need some help on the proper way to update a persisted object using the repository pattern in MVC 3 with Entity Framework 4. This example code is adapted from the SportsStore application in the Pro Asp.NET MVC 3 Framework book put out by Apress.
Here is my 'Edit' post action in the AdminController:
[HttpPost]
public ActionResult Edit(Product product)
{
if (ModelState.IsValid)
{
// Here is the line of code of interest...
repository.SaveProduct(product, User.Identity.Name);
TempData["message"] = string.Format("{0} has been saved", product.Name);
return RedirectToAction("Index");
}
else
{
// there is something wrong with the data values
return View(product);
}
}
This calls into concrete class EFProductRepository (which is implementing the IProductRepository interface and injected via Ninject). Here is the SaveProduct method in the concrete repository class:
public void SaveProduct(Product product, string userID)
{
if (product.ProductID == 0)
{
context.Products.Add(product);
}
else
{
context.Entry(product).State = EntityState.Modified;
}
context.SaveChanges(userID);
}
The problem (as was brought to my attention in my previous SO post), is that when context.Entry(product).State = EntityState.Modified; is called it somehow messes up the ChangeTracker's ability to report on the changes. So in my overloaded DBContext.SaveChanges(string userID) method, I am not seeing accurate values in the ChangeTracker.Entries().Where(p => p.State == System.Data.EntityState.Modified).OriginalValues object.
If I update my EFProductRepository.SaveProduct method to this it works:
public void SaveProduct(Product product, string userID)
{
if (product.ProductID == 0)
{
context.Products.Add(product);
}
else
{
Product prodToUpdate = context.Products
.Where(p => p.ProductID == product.ProductID).FirstOrDefault();
if (prodToUpdate != null)
{
// Just updating one property to demonstrate....
prodToUpdate.Name = product.Name;
}
}
context.SaveChanges(userID);
}
I would like to know the proper way to update the Product object and persist it in this scenario in such a way that the ChangeTracker accurately tracks my changes to the POCO class in the repository. Am I supposed to do the latter example (except of course copying over all fields that may have been updated), or should I be taking a different approach?
In this example the "Product" class is very simple and only has string properties and decimal properties. In our real application we will have "complex" types and the POCO classes will reference other objects (i.e. Person that has a list of addresses). I know I may also need to do something special to track the changes in this case. Perhaps knowledge of this will change some advice that I receive here.
it somehow messes up the ChangeTracker's ability to report on the changes
No it doesn't messes anything. Change tracker ability is based on the fact that change tracker knows the entity prior to making changes. But in your case the change tracker is informed about the entity with changes already applied and POCO entity doesn't keep any information about its original values. POCO entity has only single set of values which is interpreted as both current and original. If you want anything else you must code it yourselves.
Am I supposed to do the latter example
In your simple case yes and you can simply use:
public void SaveProduct(Product product, string userID)
{
if (product.ProductID == 0)
{
context.Products.Add(product);
}
else
{
Product prodToUpdate = context.Products
.Where(p => p.ProductID == product.ProductID).FirstOrDefault();
if (prodToUpdate != null)
{
context.Entry(prodToUpdate).CurrentValues.SetValues(product);
}
}
context.SaveChanges(userID);
}
The problem is that this works only for simple and complex properties. Another problem is that this overwrites all properties so if for example your entity has some field you don't want to show in UI (or don't want to let user to edit the field) you must still set correct current value to your product instance otherwise that value will be overwritten when applying current values.
The whole situation becomes significantly more complex once you try to apply this to the real scenario. You will fail and you will fail many times before you write a lot of code to support exactly your cases because there is probably no generic solution EF has no supporting methods for this scenarios. The reason is that EF has internal state machine for every entity and some associations and you must configure the state for every single entity or association you want to update, insert or delete and you must do it in compliance with EF internal rules. Setting state of the entity will change the state of that single entity but not its relations.
I do it simply by loading current entity with all relations from database and manually (in code) merging whole entity graph (simply you have detached and attached entity graph and you must transfer all changes from detached to attached one).

EF 4.1 DBContext and Navigation Properties

We've been using EF STEs for a while, but our application has grown quite a bit and we decided to sue the new 4.1 DbContext so we can "evolve" a separate business layer on top of our data layer without having to use different types for it.
In the elementary evaluation for the DbContext way of doing things, I am facing a little problem.
I am used to query and preload required related data like:
return context.Orders.Include("Detail").SingleOrDefault(ord => ord.ID == ID);
And then send the returned object to the UI for modification, and when returned from the UI save the changes to the database.
From what I read so far, doing the "change saving" in DbContext is easily done using code like this:
context.Entry(order).State = EntityState.Modified;
The problem with this code is that it actually marks all properties in the object as modified, a thing that's not allowed for some properties in my model (a business rule).
I resorted to the following solution (which seems to require a lot of code for a relatively small requirement! BTW, changing a modified property state to Unchanged is not supported):
context.Orders.Attach(order);
DbEntityEntry<Order> ordEntity = context.Entry(order);
string[] arr =
{
ordEntity.Property(ord => ord.ID).Name,
ordEntity.Property(ord => ord.ClientID).Name,
};
foreach (string prop in ordEntity.OriginalValues.PropertyNames)
{
if (!arr.Contains(prop))
{
ordEntity.Property(prop).IsModified = true;
}
}
context.SaveChanges();
The problem I am facing with this code is that the "Attach" statement is throwing an exception saying that there is some sort of conflict in the navigation properties in the attached object, even if no changes were made to anything at all! (saving the object exactly as it was retrieved from the database).
The error message is something like:
"Conflicting changes to the role 'Detail' of the relationship 'OrdersDatamodel.FK_Order_Detail' have been detected."
The questions are:
Is there a more "elegant" way for preventing the modification of certain object properties?
Does anybody know what's going on with the exception raised when attaching the object to the context?
Thanks.
From what I read so far, doing the "change saving" in DbContext is easily done using code like this:
context.Entry(order).State = EntityState.Modified;
You rarely need to explicitly set the state. When you modify properties, assuming they are virtual, the state will automatically change to Modified without you having to set it. Otherwise, DetectChanges will pick this up during your call to SaveChanges.

Categories