Why do I need to Insert and Update methods to my DomainService? - entity-framework

If I don't add Insert and Update methods to my domain service, I get exceptions when I try to add entities to the associated EntityCollection of my Entity. Now that I've added them (completely blank) I can add entities and modify them on the client but they never show up in my database. What am I missing? Do I need to create my own Insert and Update methods to my domain service and if so, what on earth would I put in them?
Edits:
This is what I have in my DomainContext. This seems a bit superfluous; I would think the Entity
Framework would already do this.
[Update]
public void UpdateProject(Project a_project)
{
ObjectContext.AcceptAllChanges();
}
[Update]
public void UpdateProjectItem(ProjectItem a_projectItem)
{
ObjectContext.AcceptAllChanges();
}
[Insert]
public void InsertProjectItem(ProjectItem a_projectItem)
{
ObjectContext.ProjectItems.AddObject(a_projectItem);
ObjectContext.AcceptAllChanges();
}
And this is how I'm using this on the client.
ProjectItem projectItem = new ProjectItem();
_reservedProject.Status = Project.ProjectStatusSubmitted;
_reservedProject.ProjectItems.Add(projectItem);
projectItem.LibraryItem = a_item;
_projectItems.Add(projectItem);
_domainContext.SubmitChanges();
UpdateProjectItem is never called.

Related

Update object using only DbSet

I'm trying to apply the unit of work pattern as described in this blog, but have bumped into the following problem: If I inject the associated DbSet into the repo only, e.g.
public ArticleRepository(DbSet<Article> articles)
{
this.articles = articles;
}
then how do I update records or set their status to modified?
Before I used
public void Update(Article article)
{
this.context.Entry(article).State = EntityState.Modified;
}
but with the new approach I don't have access to DbContext anymore. Neither DbSet.Add nor DbSet.Attach will work here, so how can I update the object in the context?
System.Data.Entity.Migrations.IDbSetExtensions contains the IDbSet extension AddOrUpdate<TEntity>. This will update the entity.
Some people like the advantage of not knowing whether they are adding a new entity or changing an existing one.
However, if you really want an error if you are updating an item that is not added yet, take a look at the Source Code of IDbSetExtensions.AddOrUpdate
Here you can see that the function first checks if the item exists and depending on the result adds or updates it as follows:
var existing = set.SingleOrDefault
(Expression.Lambda<Func <TEntity, bool>> (matchExpression, new[]
{parameter}));
if (existing != null)
{ // entity exists: update it
foreach (var keyProperty in keyProperties)
{
keyProperty.Single().SetValue
(entity, keyProperty.Single().GetValue (existing, null), null);
}
internalSet.InternalContext.Owner.Entry(existing)
.CurrentValues.SetValues (entity);
}
else
{ // not existing entity: Add it
internalSet.Add(entity);
}
If you don't want the AddOrUpdate, but really only an update, consider Creating your own Extension method for IDbSet. See Extension Methods (C# Programming Guide)

How can I create a generic update method for One to Many structures in Entity Framework 5?

I am writing a web application, such that I get different objects back from the web that need to be either updated or added to the database. On top of this, I need to check that the owner is not modified. Since a hacker could potentially get an account and send an update to modify the foreign key to the user model. I don't want to have to manually code all of these methods, instead I want to make a simple generic call.
Maybe something as simple as this
ctx.OrderLines.AddOrUpdateSet(order.OrderLines, a => a.Order)
Based on old persisted records that have a foreign key to Order, and on the new incoming records.
Delete old records that are not on the new records list.
Add new records that are not on the old records list.
Update new records that exist on both lists.
ctx.Entry(orderLine).State=EntityState.Deleted;
...
ctx.Entry(orderLine).State=EntityState.Added;
...
ctx.Entry(orderLine).State=EntityState.Modified;
This gets a bit complicated when the old record is loaded to verify that ownership did not change. I get an error if I don't do.
oldorder.OrderLines.remove(oldOrderLine); //for deletes
oldorder.OrderLines.add(oldOrderLine); //for adds
ctx.Entry(header).CurrentValues.SetValues(header); //for modifications
With Entity Framework 5 there is a new extension function called AddOrUpdate. And there was a very interesting (please read) blog entry on how to create this method before it was added.
I'm not sure if this is too much to ask as a question in StackOverflow, any clues on how to approach the problem may be sufficient. Here are my thoughts so far:
a) leverage AddOrUpdate for some of the functionality.
b) create a secondary context hoping to avoid loading order into the context and avoid extra calls.
c) Set the state of all the saved objects to initially deleted.
Since you have linked to this question from my own question, I thought I'd throw in some newly-aquired experience with Entity Framework for me.
To achieve a common save method in my generic repository with Entity Framework, I do this. (Please note that the Context is a member of my repository, as I am implementing the Unit of Work pattern as well)
public class EFRepository<TEntity> : IRepository<TEntity> where TEntity : class
{
internal readonly AwesomeContext Context;
internal readonly DbSet<TEntity> DbSet;
public EFRepository(AwesomeContext context)
{
if (context == null) throw new ArgumentNullException("context");
Context = context;
DbSet = context.Set<TEntity>();
}
// Rest of implementation removed for brevity
public void Save(TEntity entity)
{
var entry = Context.Entry(entity);
if (entry.State == EntityState.Detached)
DbSet.Add(entity);
else entry.State = EntityState.Modified;
}
}
Honestly, I can't tell you why this works, because I just kept changing the state conditions - however I do have unit (integration) tests to prove that it works. Hopefully someone more into EF than myself can shed some light on this.
Regarding the "cascading updates", I was curious myself as if it would work using the Unit of Work pattern (my question I linked to was when I did not know it existed, and my repositories would basically create a unit of work whenever I wanted to save/get/delete, which is bad), so I threw in a test case in a simple relational DB. Here is a diagram to give you an idea.
IMPORTANT In order for test case number 2 to work, you need to make your POCO reference properties virtual, in order for EF to provide lazy loading.
The repository implementation is just derived from the generic EFRepository<TEntity> as shown above, so I'll leave out that implementation.
These are my test cases, both pass.
public class EFResourceGroupFacts
{
[Fact]
public void Saving_new_resource_will_cascade_properly()
{
// Recreate a fresh database and add some dummy data.
SetupTestCase();
using (var ctx = new LocalizationContext("Localization.CascadeTest"))
{
var cultureRepo = new EFCultureRepository(ctx);
var resourceRepo = new EFResourceRepository(cultureRepo, ctx);
var existingCulture = cultureRepo.Get(1); // First and only culture.
var groupToAdd = new ResourceGroup("Added Group");
var resourceToAdd = new Resource(existingCulture,"New Resource", "Resource to add to existing group.",groupToAdd);
// Verify we got a single resource group.
Assert.Equal(1,ctx.ResourceGroups.Count());
// Saving the resource should also add the group.
resourceRepo.Save(resourceToAdd);
ctx.SaveChanges();
// Verify the group was added without explicitly saving it.
Assert.Equal(2, ctx.ResourceGroups.Count());
}
// try creating a new Unit of Work to really verify it has been persisted..
using (var ctx = new LocalizationContext("Localization.CascadeTest"))
{
Assert.DoesNotThrow(() => ctx.ResourceGroups.First(rg => rg.Name == "Added Group"));
}
}
[Fact]
public void Changing_existing_resources_group_saves_properly()
{
SetupTestCase();
using (var ctx = new LocalizationContext("Localization.CascadeTest"))
{
ctx.Configuration.LazyLoadingEnabled = true;
var cultureRepo = new EFCultureRepository(ctx);
var resourceRepo = new EFResourceRepository(cultureRepo, ctx);
// This resource already has a group.
var existingResource = resourceRepo.Get(2);
Assert.NotNull(existingResource.ResourceGroup); // IMPORTANT: Property must be virtual!
// Verify there is only one resource group in the datastore.
Assert.Equal(1,ctx.ResourceGroups.Count());
existingResource.ResourceGroup = new ResourceGroup("I am implicitly added to the database. How cool is that?");
// Make sure there are 2 resources in the datastore before saving.
Assert.Equal(2, ctx.Resources.Count());
resourceRepo.Save(existingResource);
ctx.SaveChanges();
// Make sure there are STILL only 2 resources in the datastore AFTER saving.
Assert.Equal(2, ctx.Resources.Count());
// Make sure the new group was added.
Assert.Equal(2,ctx.ResourceGroups.Count());
// Refetch from store, verify relationship.
existingResource = resourceRepo.Get(2);
Assert.Equal(2,existingResource.ResourceGroup.Id);
// let's change the group to an existing group
existingResource.ResourceGroup = ctx.ResourceGroups.First();
resourceRepo.Save(existingResource);
ctx.SaveChanges();
// Assert no change in groups.
Assert.Equal(2, ctx.ResourceGroups.Count());
// Refetch from store, verify relationship.
existingResource = resourceRepo.Get(2);
Assert.Equal(1, existingResource.ResourceGroup.Id);
}
}
private void SetupTestCase()
{
// Delete everything first. Database.SetInitializer does not work very well for me.
using (var ctx = new LocalizationContext("Localization.CascadeTest"))
{
ctx.Database.Delete();
ctx.Database.Create();
var culture = new Culture("en-US", "English");
var resourceGroup = new ResourceGroup("Existing Group");
var resource = new Resource(culture, "Existing Resource 1",
"This resource will already exist when starting the test. Initially it has no group.");
var resourceWithGroup = new Resource(culture, "Exising Resource 2",
"Same for this resource, except it has a group.",resourceGroup);
ctx.Cultures.Add(culture);
ctx.ResourceGroups.Add(resourceGroup);
ctx.Resources.Add(resource);
ctx.Resources.Add(resourceWithGroup);
ctx.SaveChanges();
}
}
}
It was interesting to learn this, as I was not sure if it would work.
After working on this for a while I found an opensource project called GraphDiff here is it's blog entry 'introducing graphdiff for entity framework code first – allowing automated updates of a graph of detached entities'. I only began using it but it looks impressive. And it does solve the problem of issuing update/delete/insert for Many to One relationships. It actually generalizes the problem to graphs and allows arbitrary nesting.
Here is the generic method I concocted. It does use AddOrUpdate from the System.Data.Entity.Migrations namespace. Which may be reloading records from the db, I'll be checking on that later. The usage is
ctx.OrderLines.AddOrUpdateSet(l => l.orderId == neworder.Id,
l => l.Id, order.orderLines);
Here is the code:
public static class UpdateExtensions
{
public static void AddOrUpdateSet<TEntity>(this IDbSet<TEntity> set, Expression<Func<TEntity, bool>> predicate,
Func<TEntity, int> selector, IEnumerable<TEntity> newRecords) where TEntity : class
{
List<TEntity> oldRecords = set.Where(predicate).ToList();
IEnumerable<int> keys = newRecords.Select(selector);
foreach (TEntity newRec in newRecords)
set.AddOrUpdate(newRec);
oldRecords.FindAll(old => !keys.Contains(selector(old))).ForEach(detail => set.Remove(detail));
}
}

How can I prevent EF from inserting an object that already exists in the db when adding one that contains this first one?

It's quite self-explainatory.
I have a class that contains another
Let's call them Subject and Classroom
public class Subject
{
public Classroom Class {get; set;}
}
I'm using stateless facades, wich means my DbContext is disposed right after recovering the objects and is created to store the new ones.
Shouldn't it know that Classroom isn't a new object since it's ID is already in the DB?
Using the debugger I can track to the point right before I call the SaveChanges method and Classroom.id is the one I have on the database.
What's the problem? EF adds a new Classroom with the exact properties as the previous one, but with a new PK.
What am I doing wrong here?
This is the code used for the general CRUD operations (They are in my DbContext) Both update and delete work just fine:
public void Update(DbSet MySet, object Obj)
{
MySet.Attach(Obj);
var Entry = this.Entry(Obj);
Entry.State = EntityState.Modified;
this.SaveChanges();
}
public void Insert(DbSet MySet, object Obj)
{
MySet.Add(Obj);
this.SaveChanges();
}
public void Delete(DbSet MySet, object Obj)
{
MySet.Attach(Obj);
var Entry = this.Entry(Obj);
Entry.State = EntityState.Deleted;
this.SaveChanges();
}
Without seeing you're actual code on how you're either updating or creating your Subject entity, it's hard to tell. However, you're probably not attaching the Classroom so EF is assuming that the entity is new, when it's really not.
using (Model m = new Model())
{
m.Subject.Add(subject);
m.Classrooms.Attach(subject.Class);
m.SaveChanges();
}
Even though the PK is the same, without attaching to the Context, EF has no way of figuring out what you're intention is. Attaching the entity explicitly tells your context what you want.

RIA Services EntitySet does not support 'Edit' operation

Making my first steps in RIA Services (VS2010Beta2) and i encountered this problem:
created an EF Model (no POCOs), generic repository on top of it and a RIA Service(hosted in an ASP.NET MVC application) and tried to get data from within the ASP.NET MVC application: worked well.
Next step: Silverlight client. Got a reference to the RIAService (through its context), queried for all the records of the repository and got them into the SL application as well (using this code sample):
private ObservableCollection<Culture> _cultures = new ObservableCollection<Culture>();
public ObservableCollection<Culture> cultures
{
get { return _cultures; }
set
{
_cultures = value;
RaisePropertyChanged("cultures");
}
}
....
//Get cultures
EntityQuery<Culture> queryCultures = from cu in dsCtxt.GetAllCulturesQuery()
select cu;
loCultures = dsCtxt.Load(queryCultures);
loCultures.Completed += new EventHandler(lo_Completed);
....
void loAnyCulture_Completed(object sender, EventArgs e)
{
ObservableCollection<Culture> temp=
new ObservableCollection<Culture>loAnyCulture.Entities);
AnyCulture = temp[0];
}
The problem is this: whenever i try to edit some data of a record (in this example the first record) i get this error:
This EntitySet of type 'Culture' does not support the 'Edit' operation.
I thought that i did something weird and tried to create an object of type Culture and assign a value to it: it worked well!
What am i missing? Do i have to declare an EntitySet? Do i have to mark it? Do i have to...what?
Thanks in advance
It turns out that in the DomainService class one has to implement (or at least to mark "placeholder methods") as "Edit", "Delete",... eg
[Delete]
public void DeleteCulture(Culture currentCulture)
{
throw new NotImplementedException("UpdateCulture not Implemented yet");
}
[Insert]
public void InsertCulture(Culture newCulture)
{
throw new NotImplementedException("InsertCulture not Implemented yet");
}
This way the OrganizationDomainContextEntityContainer class creates an EntitySet with parameter EntitySetOperations.All (meaning that all the CUD operations are available).
Hope it's useful for someone in the future!

Entity Framework and Entity Tracker Problems

If I run the following code it throws the following error:
An entity object cannot be referenced by multiple instances of IEntityChangeTracker
public void Save(Category category)
{
using(var db = new NorthwindContext())
{
if(category.CategoryID == 0)
{
db.AddToCategorySet(category);
}
else
{
//category.RemoveTracker();
db.Attach(category);
}
db.SaveChanges();
}
}
The reason is of course that the category is sent from interface which we got from GetById method which already attached the EntityChangeTracker to the category object. I also tried to set the entity tracker to null but it did not update the category object.
protected void Btn_Update_Category_Click(object sender, EventArgs e)
{
_categoryRepository = new CategoryRepository();
int categoryId = Int32.Parse(txtCategoryId.Text);
var category = _categoryRepository.GetById(categoryId);
category.CategoryName = txtUpdateCategoryName.Text;
_categoryRepository.Save(category);
}
I'm still learning Entity Framework myself, but maybe I can help a little. When working with the Entity Framework, you need to be aware of how you're handling different contexts. It looks like you're trying to localize your context as much as possible by saying:
public void Save(Category category)
{
using (var db = new NorthwindContext())
{
...
}
}
... within your data access method. Did you do the same thing in your GetById method? If so, did you remember to detach the object you got back so that it could be attached later in a different context?
public Category GetById(int categoryId)
{
using (var db = new NorthwindContext())
{
Category category = (from c in db.Category where Category.ID == categoryId select c).First();
db.Detach(category);
}
}
That way when you call Attach it isn't trying to step on an already-attached context. Does that help?
As you pointed out in your comment, this poses a problem when you're trying to modify an item and then tell your database layer to save it, because once an item is detached from its context, it no longer keeps track of the changes that were made to it. There are a few ways I can think of to get around this problem, none of them perfect.
If your architecture supports it, you could expand the scope of your context enough that your Save method could use the same context that your GetById method uses. This helps to avoid the whole attach/detach problem entirely, but it might push your data layer a little closer to your business logic than you would like.
You can load a new instance of the item out of the new context based on its ID, set all of its properties based on the category that is passed in, and then save it. This costs two database round-trips for what should really only need one, and it isn't very maintainable.
You can dig into the context itself to mark the Category's properties as changed.
For example:
public void Save(Category category)
{
using (var db = new NorthwindContext())
{
db.Attach(category);
var stateEntry = db.ObjectStateManager.GetObjectStateEntry(category);
foreach (var propertyName in stateEntry.CurrentValues.DataRecordInfo.FieldMetadata.Select(fm => fm.FieldType.Name)) {
stateEntry.SetModifiedProperty(propertyName);
}
db.SaveChanges();
}
}
This looks a little uglier, but should be more performant and maintainable overall. Plus, if you want, you could make it generic enough to throw into an extension method somewhere so you don't have to see or repeat the ugly code, but you still get the functionality out of it.