Generate and auto increment the Id in Entity Framework database first - c#

I have a table CampaignLanguage. The primary key is Id. It should be auto increment.
So I have the code:
public partial class CampaignLanguage
{
public CampaignLanguage()
{
this.CampaignLanguageQuestions = new HashSet<CampaignLanguageQuestion>();
}
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
Then in the controller, I want to save the generated object.
[HttpPost]
public ActionResult Save(int clientId, int campaignId)
{
var campaign = CampaignService.GetCampaignById(campaignId);
var campaignLanguage = campaign.CampaignLanguages.Where(x => x.CampaignId == campaignId).FirstOrDefault();
if (campaignLanguage != null)
{
campaignLanguage.WelcomeMessage = message;
CampaignService.Save(campaignLanguage);
}
else
{
campaignLanguage = new CampaignLanguage();
campaignLanguage.Id = 1;
CampaignService.Save(campaignLanguage);
}
return Redirect("/Campaign/Index/" + clientId);
}
However, I get the error.
{"Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. Refresh ObjectStateManager entries."}
I don't want to change my CampaignService.Save method. So how to fix it?
EDIT
public void Save(CampaignLanguage campaignLanguage)
{
_campaignLanguageRepository.Update(campaignLanguage);
_unitOfWork.Commit();
}
EDIT 1
public virtual void Add(T entity)
{
dbset.Add(entity);
}
public virtual void Update(T entity)
{
dbset.Attach(entity);
dataContext.Entry(entity).State = EntityState.Modified;
}

You should be calling Add instead of Update as this is a new instance you want to insert into the data store. Your Save method should check if the primary (auto incremented key) has a value greater than 0 to see if it is new or not. Alternatively you can also see if it is already attached. There is also no need to call Update, setting the entity to state modified does nothing except ensure that all properties will be written back to the DB but the DbContext implements change tracking on entities so this will already happen (unless you are working in a detached state).
public void Save(CampaignLanguage campaignLanguage)
{
if(campaignLanguage.Id == 0)
_campaignLanguageRepository.Add(campaignLanguage);
else
_campaignLanguageRepository.Update(campaignLanguage);
_unitOfWork.Commit();
}
On a side note: The type DbContext already implements a Unit of Work pattern and DbSet<T> is an implementation of a Repository pattern. There should not be any need to add another customer Unit of work and repository pattern around EF, you are just creating a whole abstraction layer for no reason that will problems with readability as well as issues later when you want to perform more complex operations like joining multiple tables together in a query.

Unfortunately, you need to change your CampaignService.Save. You are trying to update an inexistent campaignLanguage object.
The other problem is you are trying to force a key into an Identity column. You cannot do it with out first set insert_identy to the table.
Maybe, you need to ask for the correct method of CampaignService.

Related

Deleting an EF entity efficiently without retrieving inquiry by using .Local and new constructor

I'm reading some EF tutorials about deleting items by setting context.Entry().State to EntityState.Deleted. Two approaches were mentioned when we want to delete an item by Id:
First call table.Find(Id) to return the entity, then set its EntityState.
The Find() method will try to locate the entity if it already exists locally, saving a database inquiry, but otherwise it will retrieve the item from the database, wasting an inquiry.
Construct a placeholder entity using the Id, then set its EntityState
But the problem with #2 is that it won't work if the actual entity with the same Id does already exist locally.
Now assuming we do not know if the entity is actually already tracked or not, but we just need to delete it anyway, and we want to guarantee that no wasted retrieval from the database occurs. My question is does this method below work efficiently for this purpose by using the table.Local property:
public int Delete(int id)
{
T entityToDelete = table.Local.SingleOrDefault(e => e.Id == id);
if (entityToDelete == null)
{
entityToDelete = new T() { Id = id};
}
Context.Entry(entityToDelete).State = EntityState.Deleted;
return SaveChanges();
}
My thinking is that since .Local will never attempt to retrieve the entity from the database, using it instead of .Find and combine it with the new entity constructor approach could guarantee an entity being deleted without extra inquiry.
However, .Net documentation does say this about Local:
One final thing to note about Local is that because it is an ObservableCollection performance is not great for large numbers of entities. Therefore if you are dealing with thousands of entities in your context it may not be advisable to use Local.
Can I assume that the overheads of using Local should be smaller than a database inquiry in most cases?
Another approach I can think of is wrapping #2 inside a try/catch, and handle the exception (which implies that the entity is indeed already being tracked) by using .Find() to retrieve the entity locally. How would this compare to the .Local approach?
Your approach is very good, the performance will be much better than a classic delete, because you're finding an element from a memory collection instead of retrieving the object from database.
IMPORTANT: If an entry is attached manually and marked as deleted/modified, and the entry does not exist, a DbUpdateConcurrencyException exception will be thrown after invoke the SaveChanges.
I wanted to recommend EntityFramework Plus it provides free functions to improve the EF performance such as Batch Delete, Batch Update, Query Cache and much more (BTW I don't work for EntityFramework Plus). You can read more about EF+ here
Here is a code snip to add the Delete function to your DbContext. To make it generic, we need to have an entity base class, let's call it EntityBase.
public abstract class EntityBase
{
public int Id { get; set; }
}
public int Delete<T>(int id) where T : EntityBase, new()
{
//using local view/manually attaching
var item = Set<T>().Local.SingleOrDefault(f => f.Id == id);
if (item is null)
{
item = new T { Id = id };
Set<T>().Attach(item); //WARNING: a DbUpdateConcurrencyException will be thrown is entry does not exist
}
Entry(item).State = EntityState.Deleted;
return SaveChanges();
}
public int DeleteEfPlus<T>(int id) where T : EntityBase, new()
{
//using entityframework-plus
return Set<T>().Where(x => x.Id == id).Delete();
}

Entity Framework Core Find and Update single value [duplicate]

What is the best approach to update database table data in Entity Framework Core?
Retrieve the table row, do the changes and save
Use keyword Update in DB context and handle exception for item not exist
What are the improved features we can use over EF6?
To update an entity with Entity Framework Core, this is the logical process:
Create instance for DbContext class
Retrieve entity by key
Make changes on entity's properties
Save changes
Update() method in DbContext:
Begins tracking the given entity in the Modified state such that it will be updated in the database when SaveChanges() is called.
Update method doesn't save changes in database; instead, it sets states for entries in DbContext instance.
So, We can invoke Update() method before to save changes in database.
I'll assume some object definitions to answer your question:
Database name is Store
Table name is Product
Product class definition:
public class Product
{
public int? ProductID { get; set; }
public string ProductName { get; set; }
public string Description { get; set; }
public decimal? UnitPrice { get; set; }
}
DbContext class definition:
public class StoreDbContext : DbContext
{
public DbSet<Product> Products { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer("Your Connection String");
base.OnConfiguring(optionsBuilder);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Order>(entity =>
{
// Set key for entity
entity.HasKey(p => p.ProductID);
});
base.OnModelCreating(modelBuilder);
}
}
Logic to update entity:
using (var context = new StoreDbContext())
{
// Retrieve entity by id
// Answer for question #1
var entity = context.Products.FirstOrDefault(item => item.ProductID == id);
// Validate entity is not null
if (entity != null)
{
// Answer for question #2
// Make changes on entity
entity.UnitPrice = 49.99m;
entity.Description = "Collector's edition";
/* If the entry is being tracked, then invoking update API is not needed.
The API only needs to be invoked if the entry was not tracked.
https://www.learnentityframeworkcore.com/dbcontext/modifying-data */
// context.Products.Update(entity);
// Save changes in database
context.SaveChanges();
}
}
According to Microsoft docs:
the read-first approach requires an extra database read, and can result in more complex code for handling concurrency conflict
However, you should know that using Update method on DbContext will mark all the fields as modified and will include all of them in the query. If you want to update a subset of fields you should use the Attach method and then mark the desired field as modified manually.
context.Attach(person);
context.Entry(person).Property(p => p.Name).IsModified = true;
context.SaveChanges();
public async Task<bool> Update(MyObject item)
{
Context.Entry(await Context.MyDbSet.FirstOrDefaultAsync(x => x.Id == item.Id)).CurrentValues.SetValues(item);
return (await Context.SaveChangesAsync()) > 0;
}
It's super simple
using (var dbContext = new DbContextBuilder().BuildDbContext())
{
dbContext.Update(entity);
await dbContext.SaveChangesAsync();
}
Microsoft Docs gives us two approaches.
Recommended HttpPost Edit code: Read and update
This is the same old way we used to do in previous versions of Entity Framework. and this is what Microsoft recommends for us.
Advantages
Prevents overposting
EFs automatic change tracking sets the Modified flag on the fields that are changed by form input.
Alternative HttpPost Edit code: Create and attach
an alternative is to attach an entity created by the model binder to the EF context and mark it as modified.
As mentioned in the other answer the read-first approach requires an extra database read, and can result in more complex code for handling concurrency conflicts.
Assume we have an entity Student and AppDbContext as follows.
class Student
{
public int Id { get; set; }
public string Name { get; set; } = default!;
public int Age { get; set; }
}
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> opts) : base(opts) { }
public DbSet<Student> Students { get; set; }
}
Version A
CurrentValues can only work for a tracked entity (found).
Only the changed properties are marked as Modified.
Automatic property mapping that is useful when using type parameter TEntity instead of a fixed type Student.
async Task Edit_A(int id, Student incoming, AppDbContext db)
{
if (await db.Students.FindAsync(id) is Student found)
{
db.Entry(found).CurrentValues.SetValues(incoming);
await db.SaveChangesAsync();
}
}
Version B
It works only on a tracked entity (found).
It is not necessary to map all properties because only the changed properties are marked as Modified.
Manual property mapping so we cannot not use generic type parameter.
async Task Edit_B(int id, Student incoming, AppDbContext db)
{
if (await db.Students.FindAsync(id) is Student found)
{
found.Name = incoming.Name;
found.Age = incoming.Age;
await db.SaveChangesAsync();
}
}
Version C
Update() works only on an untracked entity (incoming) and makes it tracked. Untracking found before invoking Update(incoming) is mandatory because only one entity can be tracked with the given primary key.
All properties (including unchanged ones) are marked as Modified. It is less efficient.
Automatic property mapping that is useful for generic type parameter.
async Task Edit_C(int id, Student incoming, AppDbContext db)
{
if (await db.Students.FindAsync(id) is Student found)
{
db.Students.Entry(found).State = EntityState.Detached;
db.Students.Update(incoming);
await db.SaveChangesAsync();
}
}
Version D
It is the same as version C. I rewrite again below for the sake of completeness.
It works only on an untracked entity (incoming) and makes it tracked. Untracking found is mandatory because only one entity can be tracked with the given primary key.
All properties (including unchanged ones) are marked as Modified. It is less efficient.
Automatic property mapping that is useful for generic type parameter.
async Task Edit_D(int id, Student incoming, AppDbContext db)
{
if (await db.Students.FindAsync(id) is Student found)
{
db.Students.Entry(found).State = EntityState.Detached;
db.Students.Entry(incoming).State = EntityState.Modified;
await db.SaveChangesAsync();
}
}
Version E
It works only on an untracked entity (incoming) and makes it tracked. Untracking found is mandatory because only one entity can be tracked with the given primary key.
It is not necessary to map all properties because only properties (including unchanged ones) marked with IsModified=true will be updated. It is less efficient if you mark IsModified=true for unchanged properties.
Manual property mapping so we cannot not use generic type parameter.
async Task Edit_E(int id, Student incoming, AppDbContext db)
{
if (await db.Students.FindAsync(id) is Student found)
{
db.Students.Entry(found).State = EntityState.Detached;
db.Students.Entry(incoming).Property(s => s.Name).IsModified = true;
db.Students.Entry(incoming).Property(s => s.Age).IsModified = true;
await db.SaveChangesAsync();
}
}
I set it as a Community Wiki, feel free to edit as many as you want.
After going through all the answers I thought i will add two simple options
If you already accessed the record using FirstOrDefault() with tracking enabled (without using .AsNoTracking() function as it will disable tracking) and updated some fields then you can simply call context.SaveChanges()
In other case either you have entity posted to server using HtppPost or you disabled tracking for some reason then you should call context.Update(entityName) before context.SaveChanges()
1st option will only update the fields you changed but 2nd option will update all the fields in the database even though none of the field values were actually updated :)
A more generic approach
To simplify this approach an "id" interface is used
public interface IGuidKey
{
Guid Id { get; set; }
}
The helper method
public static void Modify<T>(this DbSet<T> set, Guid id, Action<T> func)
where T : class, IGuidKey, new()
{
var target = new T
{
Id = id
};
var entry = set.Attach(target);
func(target);
foreach (var property in entry.Properties)
{
var original = property.OriginalValue;
var current = property.CurrentValue;
if (ReferenceEquals(original, current))
{
continue;
}
if (original == null)
{
property.IsModified = true;
continue;
}
var propertyIsModified = !original.Equals(current);
property.IsModified = propertyIsModified;
}
}
Usage
dbContext.Operations.Modify(id, x => { x.Title = "aaa"; });
Personally I see the operation that you are doing is an upsert operation, where if data already exist we update, else we insert. There is one good library from Flexlab to support Upsert operation with this syntax
var country = new Country
{
Name = "Australia",
ISO = "AU",
Created = DateTime.UtcNow,
};
await DataContext.Upsert(country)
.On(c => c.ISO)
.UpdateColumns(c => new Country
{
Name = "Australia"
Updated = DateTime.UtcNow,
})
.RunAsync();
The code will check for the property Country.ISO in the table. If it does not exist yet it will insert new row altogether into the table. Else if any row with the same Country.ISO already exist it will update columns Country.Name and Country.Updated for that row.
This method is very fast because we only do one call to the database instead of two calls to check if the data already exist before we updating or inserting the data.
Note that this answer does not apply to you if your intend is not to do the Upsert operation

How to get id from Add using UnitOfWork pattern?

I am using the UnitOfWork pattern to abstract database access in my Asp.Net application. Basically I follow the UnitOfWork pattern approach described here:
https://chsakell.com/2015/02/15/asp-net-mvc-solution-architecture-best-practices/
However, I'm struggling to understand, how I will get the Id of a newly added item. Like if I want to add a new customer to my Customer repository, how will I get the customer id? The problem is that Add and Commit are decoupled, and the Id is not known until after Commit.
Is there a way to get the id of an added item, using the UnitOfWork pattern?
My approach is as follows. Simply continue working with the added entity as an object. So instead of returning it's ID, return the added object itself. Then, at some point (typically in the controller) you will call UoW.Commit(); and as a result, the Id property of the added entity will contain the updated value.
At this point, you can start using the Id property and for example store it in a cookie as you said.
Note that I dont want my EF model classes to propagate to my domain layer
I have done a workaround. I think it works pretty well
When you want a repository, for example of DbCars, and you insert a new DomainCar you want to get that Id that was only generated when SaveChanges() is applied.
public DomainCar //domain class used in my business layers
{
public int Id{get;set;}
public string Name{get;set;}
}
public DbCar //Car class to be used in the persistence layer
{
public int Id{get;set;}
public string Name{get;set;}
public DateTime CreatedDate{get;set;}
public string CreatedBy{get;set;}
}
First you create a generic IEntity interface and a class implementing it:
public interface IEntity<T>
{
T Id { get; }
}
public class Entity<T> : IEntity<T>
{
dynamic Item { get; }
string PropertyName { get; }
public Entity(dynamic element,string propertyName)
{
Item = element;
PropertyName = propertyName;
}
public T Id
{
get
{
return (T)Item.GetType().GetProperty(PropertyName).GetValue(Item, null);
}
}
}
Then in your add method of the repository you return a IEntity of the type of your Id:
public IEntity<int> AddCar(DomainCar car)
{
var carDb=Mapper.Map<DbCar>(car);//automapper from DomainCar to Car (EF model class)
var insertedItem=context.CARS.Add(carDb);
return new Entity<int>(insertedItem,nameof(carDb.Id));
}
Then , somewhere you are calling the add method and the consequent Save() in the UnitofWork:
using (var unit = UnitOfWorkFactory.Create())
{
IEntity<int> item =unit.CarsRepository.AddCar(new DomainCar ("Ferrari"));
unit.Save(); //this will call internally to context.SaveChanges()
int newId= item.Id; //you can extract the recently generated Id
}
The problem here is that the id is generated by the database, so we need to call SaveChanges so that the database generates the id and EntityFramework will fix the entity up with the generated id.
So what if we could avoid the database roundtrip?
One way to do this is to use a uuid instead of an integer as an id. This way you could simply generate a new uuid in the constructor of your domain model and you could (pretty) safely assume that it would be unique across the entire database.
Of course choosing between a uuid and an integer for the id is an entire discussion of its own: Advantages and disadvantages of GUID / UUID database keys But at least this is one point in favor of a uuid.
Unit of work should be a transaction for the entire request.
Simply just return the person id from the newly created object.
Depending on what technology you are using for your data access this will differ, but if you are using Entity Framework, you can do the following:
var person = DbContext.Set<Person>().Create();
// Do your property assignment here
DbContext.Set<Person>().Add(person);
return person.Id;
By creating the Person instance this way, you get a tracked instance that allows for lazy loading, and using the Id property, as it will be updated when SaveChanges is called (by ending your unit of work).
Instead of IDENTITY, I use SEQUENCE at database level. When a new entity is being created, first, I get the next value of the sequence and use it as Id.
Database:
CREATE SEQUENCE dbo.TestSequence
START WITH 1
INCREMENT BY 1
CREATE TABLE [dbo].[Test](
[Id] [int] NOT NULL DEFAULT (NEXT VALUE FOR dbo.TestSequence),
[Name] [nvarchar](200) NOT NULL,
CONSTRAINT [PK_Test] PRIMARY KEY CLUSTERED ([Id] ASC)
)
C#:
public enum SequenceName
{
TestSequence
}
public interface IUnitOfWork : IDisposable
{
DbSet<TEntity> Set<TEntity>() where TEntity : class;
void Commit(SqlContextInfo contextInfo);
int NextSequenceValue(SequenceName sequenceName);
}
public class UnitOfWork : MyDbContext, IUnitOfWork
{
...
public void Commit(SqlContextInfo contextInfo)
{
using (var scope = Database.BeginTransaction())
{
SaveChanges();
scope.Commit();
}
}
public int NextSequenceValue(SequenceName sequenceName)
{
var result = new SqlParameter("#result", System.Data.SqlDbType.Int)
{
Direction = System.Data.ParameterDirection.Output
};
Database.ExecuteSqlCommand($"SELECT #result = (NEXT VALUE FOR [{sequenceName.ToString()}]);", result);
return (int)result.Value;
}
...
}
internal class TestRepository
{
protected readonly IUnitOfWork UnitOfWork;
private readonly DbSet<Test> _tests;
public TestRepository(IUnitOfWork unitOfWork)
{
UnitOfWork = unitOfWork;
_tests = UnitOfWork.Set<Test>();
}
public int CreateTestEntity(NewTest test)
{
var newTest = new Test
{
Id = UnitOfWork.NextSequenceValue(SequenceName.TestSequence),
Name = test.Name
};
_tests.Add(newTest);
return newTest.Id;
}
}
I don't think there is a way to do that unless you break the pattern and pass in some extra information about the newly created entity.
Since the Id will only be allocated since commit is successful and if you don't have information about which entities were created/updated/deleted, its almost impossible to know.
I once did it using the code below (I don't recommend it though but I use it for this need specifically)
public string Insert(Person entity)
{
uow.Repository.Add(entity); //public Repository object in unit of work which might be wrong
Response responseObject = uow.Save();
string id = entity.Id; //gives the newly generated Id
return id;
}
My solution is returning Lazy<MyModel> by repository method:
public class MyRepository
{
// ----
public Lazy<MyModel> Insert(MyModel model)
{
MyEntity entity = _mapper.MapModel(model);
_dbContext.Insert(entity);
return Lazy<MyModel>(()=>_mapper.MapEntity(entity));
}
}
And in the domain:
Lazy<MyModel> mayModel = unitOfWork.MyRepository.Insert(model);
unitOfWork.Save();
MyModel myModel = myModel.Value;
To expand on Martin Fletcher his answer:
EF Core generates (depending on the db provider) a temporary value for the generated id, once you add the entity to the DbContext, and start tracking it. So before the unit of work is actually committed via SaveChanges(). After SaveChanges() is called, the DbContext will actually fix up all placeholder ids with the actual generated id.
A nice example (which I quote from this answer):
var x = new MyEntity(); // x.Id = 0
dbContext.Add(x); // x.Id = -2147482624 <-- EF Core generated id
var y = new MyOtherEntity(); // y.Id = 0
dbContext.Add(y); // y.Id = -2147482623 <-- EF Core generated id
y.MyEntityId = x.Id; // y.MyEntityId = -2147482624
dbContext.SaveChangesAsync();
Debug.WriteLine(x.Id); // 1261 <- EF Core replaced temp id with "real" id
Debug.WriteLine(y.MyEntityId); // 1261 <- reference also adjusted by EF Core
More information:
https://learn.microsoft.com/en-us/ef/core/change-tracking/explicit-tracking#generated-key-values
https://learn.microsoft.com/en-us/ef/core/modeling/generated-properties?tabs=data-annotations#primary-keys
Another option is to generate the keys on the client side (this is possible with for example UUID-based primary keys). But this has been discussed in other answers.

Updating an Entity in EF with a 1:1 relationship

I have an application with 4 layers:
-Core (Models)
-Repository (EF DbContext actions)
-Service (Business logic)
-Web (MVC)
I'm trying to update an object with a 1:1 relationship with EF using the following method:
public async Task<bool> UpdateProductTicketing(ProductTicketing ticketing)
{
var product = await GetProductByIdAsync(ticketing.ProductId);
// Validation removed for simplicity
// 'ticketing' passed validation so let's
// just replace it with the existing record.
product.Ticketing = ticketing;
_repo.ProductRepository.Update(product);
return await _repo.SaveAsync();
}
This works for an initial insert, but it doesn't work as I'd expect when I'm updating the record:
A first chance exception of type 'System.Data.Entity.Infrastructure.DbUpdateException' occurred...
The actual error message is:
Violation of PRIMARY KEY constraint 'PK_dbo.ProductTicketing'. Cannot insert duplicate key in object 'dbo.ProductTicketing'. The statement has been terminated.
Obviously the PK and FK "ProductId" doesn't change - so why does EF try to drop and insert my record instead of just updating it, and why does it fail?
But more importantly - how can I prevent this. I know I can manually map the object values and then update it - that works but it's tedious mapping two identical objects together and doesn't feel correct.
My repository for retrieving the Product object is in my Repository layer, while the method above is in my Service layer.
This is how I'm currently resolving this - and it looks as dirty as it feels:
public async Task<bool> UpdateProductTicketing(ProductTicketing ticketing)
{
var product = await GetProductByIdAsync(ticketing.ProductId);
// Validation removed for simplicity
if (product.Ticketing == null)
{
product.Ticketing = ticketing;
}
else
{
product.Ticketing.AllowEventBooking = ticketing.AllowEventBooking;
// Doing the same for all other properties etc
// etc
// etc
}
_repo.ProductRepository.Update(product);
return await _repo.SaveAsync();
}
How can I achieve this without doing all this horrible mapping an object to an identical object?
Edit
Here are the two models referred to above:
[Table(#"Products")]
public class Product
{
[Key]
public int Id { get; set; }
public virtual ProductTicketing Ticketing { get; set; }
// Removed others for clarity
[Timestamp]
public byte[] RowVersion { get; set; }
}
[Table(#"ProductTicketing")]
public class ProductTicketing
{
[Key, ForeignKey("Product")]
public int ProductId { get; set; }
public bool AllowEventBooking { get; set; }
// Removed others for clarity
public virtual Product Product { get; set; }
}
It's also probably worth noting that the "ProductTicketing" object I'm passing into the UpdateProductTicketing method is a new object created from values in my controller - but the ID is the same so I assume it should work.
I think I see the problem now - when you do product.Ticketing = ticketing;, EF treats this as a new insert.
To avoid this, you can do one of these things:
Continue using the workaround (which is not a wokaround actually but just the way EF expects you to tell when to insert vs. when to update).
Now this depends on rest of your code and design, but instead of fetching the product, you can fetch the ticket and update its properties. Of course, this means that if the ticketing is not found, you need to insert it which then kinda looks like what you're already doing with UpdateProductTicketing.
Use the InsertOrUpdate pattern (I made some assumptions about your code but hopefully it gives you the idea - the main thing here is the InsertOrUpdate method):
public class ProductRepository : IRepository
{
private SomeContext context;
public void InsertOrUpdate(ProductTicketing ticketing)
{
context.Entry(ticketing).State = ticketing.ProductId == 0 ?
EntityState.Added :
EntityState.Modified;
context.SaveChanges();
}
}
// And a generic version
public void InsertOrUpdate<T>(T entity) where T : class
{
if (context.Entry(entity).State == EntityState.Detached)
context.Set<T>().Add(entity);
context.SaveChanges();
}
You are getting that error because ef thinks that the ProductTicket is a new entity and is trying to insert the entity into the db. I don't know about the _repo.ProductRepository.Update(product) call but how about you attach the ProductTicket to the context and set the entity state to modified

Efficient way to be updating child records when updating the Master table using Linq

I currently use a general repositry class that can update only a single table like so
public abstract class MyRepository<T> : IRepository<T>
where T : class
{
protected IObjectSet<T> _objectSet;
protected ObjectContext _context;
public MyRepository(ObjectContext Context)
{
_objectSet = Context.CreateObjectSet<T>();
_context = Context;
}
public IQueryable<T> GetAll()
{
return _objectSet.AsQueryable();
}
public IQueryable<T> Find(Expression<Func<T, bool>> filter)
{
return _objectSet.Where(filter);
}
public void Add(T entity)
{
_objectSet.AddObject(entity);
_context.ObjectStateManager.ChangeObjectState(entity, System.Data.EntityState.Added);
_context.SaveChanges();
}
public void Update(T entity)
{
_context.ObjectStateManager.ChangeObjectState(entity, System.Data.EntityState.Modified);
_context.SaveChanges();
}
public void Delete(T entity)
{
_objectSet.Attach(entity);
_context.ObjectStateManager.ChangeObjectState(entity, System.Data.EntityState.Deleted);
_objectSet.DeleteObject(entity);
_context.SaveChanges();
}
}
For every table class generated by my EDMX designer I create another class like this
public class CustomerRepo : MyRepository<Customer>
{
public CustomerRepo (ObjectContext context)
: base(context)
{
}
}
for any updates that I need to make to a particular table I do this:
Customer CustomerObj = new Customer();
CustomerObj.Prop1 = ...
CustomerObj.Prop2 = ...
CustomerObj.Prop3 = ...
CustomerRepo.Update(CustomerObj);
This works perfectly well when I am updating just to the specific table called Customer. Now if I need to also update each row of another table which is a child of Customer called Orders what changes do I need to make to the class MyRepository. Orders table will have multiple records for a Customer record and multiple fields too, say for example Field1, Field2, Field3.
So my question is: 1.) If I only need to update Field1 of the Orders table for some rows based on a condition and Field2 for some other rows based on a different condition then what changes I need to do?
2.) If there is no such condition and all child rows need to be updated with the same value for all rows then what changes do I need to do?
Thanks for taking the time. Look forward to your inputs...
Your primary issue is that you are attempting to update a disconnected entity (i.e. var myCustomer = new Customer()) without attaching it to the data context. See this answer for details on how to attach disconnected entities. In your case, I think this could get complex, because not only would the parent entity need to be attached, but all child entities as well.
My suggestion, in your case, is to perform updates against entities that are retrieved from the context first, and therefore will already be attached to it. In this case, these entities will track changes themselves. You will have no need to attach the entities to your context or call _context.ObjectStateManager.ChangeObjectState. Again, take a look at the same answer I mentioned earlier for an example that performs an update on attached entities. Once the parent object is loaded, you just apply any changes to it and its children, and then call the SaveChanges method on the context once its all said and done.
Note: By default, EF will lazy load any child relationships (i.e child objects, collections, etc). However, in some cases, you may wish to explicitly/eager load the child objects at the same time that the parent is loaded, rather than lazy loading (you can find documentation here).

Categories

Resources