i have started learning entity framework CTP5 by writing a windows application.
i have two models (Unit and Good) as following:
public class Unit : BaseEntity
{
public Unit()
{
Goods = new List<Good>();
}
public string name { get; set; }
public virtual ICollection<Good> Goods { get; set; }
}
public class Good : BaseEntity
{
public Int64 code { get; set; }
public string name { get; set; }
public virtual Unit Unit { get; set; }
}
i'm using a repository inteface named IRepository as below :
public interface IRepository
{
BaseEntity GetFirst();
BaseEntity GetNext(Int32 id);
BaseEntity GetPrevoius(Int32 id);
BaseEntity GetLast();
BaseEntity GetById(Int32 id);
void Update(int id, BaseEntity newEntity);
void Delete(int id);
void Insert(BaseEntity entity);
int GetMaxId();
IList GetAll();
}
every model has its own repository but maybe it is better to use a generic repository of BaseEntity type. A reference of GoodRepository is made in GoodForm and appropriate object of Good type is made by Activator object in common form methods like Insert/Update/Delete... as below :
private void InsertButton_Click(object sender, EventArgs e)
{
Unit unit = goodRepo.GetUnitById(Convert.ToInt32(UnitIdTextBox.Text));
if (unit == null)
{
unit = new Unit { Id = goodRepo.GetUnitMaxId(), Name = "Gram" };
}
var good = Activator.CreateInstance<Good>();
good.Id = string.IsNullOrEmpty(IdTextBox.Text) ? goodRepo.GetMaxId() : Convert.ToInt32(IdTextBox.Text);
IdTextBox.Text = good.Id.ToString();
good.Name = NameTextBox.Text;
good.Description = DescriptionTextBox.Text;
good.Unit = unit;
goodRepo.Insert(good);
}
and GoodRepository.Insert method is :
public void Insert(Model.BaseEntity entity)
{
using (PlanningContext context = new PlanningContext())
{
context.Goods.Add(entity as Good);
int recordsAffected = context.SaveChanges();
MessageBox.Show("Inserted " + recordsAffected + " entities to the database");
}
}
My problem is SaveChanges() generate an error "Violation of PRIMARY KEY constraint" and says it can not inset duplicate key in object 'dbo.Units.
but if i move my context to the form which i build Good object and insert it there everything is fine.
Can anybody guid me how to solve this issue?
thank in advance
The source of your problem is here:
using (PlanningContext context = new PlanningContext())
{
context.Goods.Add(entity as Good);
//...
}
You are adding the Good entity to a newly created and therefore initially empty context. Now, if you add an entity to the context EF will add the whole object graph of related entities to the context as well, unless the related entities are already attached to the context. That means that good.Unit will be put into the context in Added state as well. Since you don't seem to have an autogenerated identity key for the Unit class, EF tries to insert the good.Unit into the DB with the same key which is already in the database. This causes the exception.
Now, you could ad-hoc fix this problem by attaching the Unit to the context before you add a new Good:
using (PlanningContext context = new PlanningContext())
{
context.Units.Attach((entity as Good).Unit);
context.Goods.Add(entity as Good);
//...
}
But I would better rethink the design of your repository. It's not a good idea to create a new context in every repository method. The context plays the role of a unit of work and a unit of work is usually more a container for many database operations which belong closely together and should be committed in a single database transaction.
So, operations like your InsertButton_Click method should rather have a structure like this:
using (var context = CreateSomehowTheContext())
{
var goodRepo = CreateSomehowTheRepo(context); // Inject this context
var perhapsAnotherRepo = CreateTheOtherRepo(context); // Inject same context
Unit unit = goodRepo.GetUnitById(Convert.ToInt32(UnitIdTextBox.Text));
// unit is now attached to context
// ...
good.Unit = unit;
goodRepo.Insert(good); // should use the injected context and only do
// context.Goods.Add(good);
// It doesn't add unit to the context since
// it's already attached
// ...
context.SaveChanges();
}
Here you are working only with one single context and the repositories will get this context injected (in the constructor for instance). They never create their own context internally.
I suspect it's because GetUnitMaxId is returning the same value more than once. Is Id an auto-incrementing identity column? If so, you shouldn't try to make any assumptions about what that value might be in code.
Even if it's not an auto-incrementing identity column, you can only be sure of it's value when all others have been committed to the DB.
As a general design pattern, try to avoid the need to refer to Ids in code before they've been stored. EF can help with this by exploiting navigation properties (inter-entity object references).
Related
I'd like to create a generic C# class with a method that will add a row to a database using Entity Framework.
I have one table called Address. I've written the following code to add an address to the database:
public class AddressExchange
{
public int Insert(Address address)
{
using (var db = new DemoWebEntities())
{
//db.AddObject("Address", address);
db.Addresses.AddObject(address);
db.SaveChanges();
return address.Id;
}
}
}
I would like to write a generic class that will perform this operation for any entity in my EDMX. I think that it should look something like this:
public class EntityExchange<T, KeyType>
{
public KeyType Insert(T t)
{
using (var db = new DemoWebEntities())
{
// The entity set name might be wrong.
db.AddObject(typeof(T).Name, t);
// EF doesn't know what the primary key is.
return t.Id;
}
}
}
I think it may be possible to use the AddObject method to add the object to the database, but the entityset name is not necessarily the same as the type name, especially if it has been pluralized!
I also want to return the primary key to the caller, but I don't know how to tell which field contains the primary key.
I have a generic InsertOrUpdate method in a generic repository that also ensures proxies are created. (Proxies are required to support lazy loading and if you create an entity using "new", then proxies are not created). See the question here
public class RepositoryBase<T> : IRepository<T> where T : ModelBase
{
public virtual T InsertOrUpdate(T e)
{
DbSet<T> dbSet = context.Set<T>();
//Generate a proxy type to support lazy loading
T instance = dbSet.Create();
DbEntityEntry<T> entry;
if (e.GetType().Equals(instance.GetType()))
{
//The entity being added is already a proxy type that
//supports lazy loading just get the context entry
entry = context.Entry(e);
}
else
{
//The entity being added has been created using the "new" operator.
//Attach the proxy
//Need to set the ID before attaching or we get
//The property 'ID' is part of the object's key
//information and cannot be modified when we call SetValues
instance.ID = e.ID;
entry = context.Entry(instance);
dbSet.Attach(instance);
//and set it's values to those of the entity
entry.CurrentValues.SetValues(e);
e = instance;
}
entry.State = e.ID == default(int) ?
EntityState.Added :
EntityState.Modified;
return e;
}
}
public abstract class ModelBase
{
public int ID { get; set; }
}
Note that all the models inherit ModelBase so that handles the ID issue and I return the entity rather than just the ID. That is probably not strictly necessary since a reference to the entity is passed in and EF performs fixup on the ID anyway so you can always access it from the refernce passed in.
This might be reliant on a particular version on Entity framework however this is how I do it
public void Create(T entity)
{
using (var db = new DemoWebEntities())
{
db.Set<T>().Add(entity);
}
}
For the primary key issue, can you use partial classes to make your entities implement an interface, something like this:
public interface IEntity
{
Guid PrimaryKey { get; }
}
Your entity classes would then return the appropriate value:
public partial class EntityType : IEntity
{
public Guid PrimaryKey
{
get
{
return this.WhateverId; // Return the primary key
}
}
}
Then, constrain your method to only accept IEntity:
public class EntityExchange<T, KeyType> where T : IEntity
And finally return the primary key after the insert:
return t.PrimaryKey;
May be it can help you.
public T Add(T model)
{
using (BigConceptEntities entity = new BigConceptEntities())
{
entity.Set<T>().Add(model);
entity.SaveChanges();
return model;
}
}
Here's the situation in its most simplified form using the EF5 Code-First approach:
public abstract class EntityBase<PK>
{
public PK ID { get; set; }
}
public class Country : EntityBase<string>
{
public string Name { get; set; }
}
public class Address : EntityBase<int>
{
[Required]
public string CountryID { get; set; }
public Country Country { get; set; }
// ... other address properties ...
}
The one-to-many relationship between Address and Country is set up with no cascade-delete like so:
modelBuilder.Entity<Address>()
.HasRequired(a => a.Country)
.WithMany()
.HasForeignKey(a => a.CountryID)
.WillCascadeOnDelete(false);
Finally, I have a generic base repository class with CRUD methods that call SaveChanges on the underlying DbContext to commit data changes atomically. E.g.:
public class EFRepository<T, PK> : IRepository<T, PK> where T : EntityBase<PK>
{
//
// ... other methods ...
//
public virtual void Delete(T instance)
{
// ... trigger validations, write to log, etc...
_dbContext.Set<T>().Remove(instance);
try
{
_dbContext.SaveChanges();
}
catch(Exception ex)
{
// ... handle the error ...
}
}
}
Part 1:
Scenario:
var countryRepo = new EFRepository<Country>();
var country = countryRepo.Save(new Country() { ID="??", Name="Test Country" });
var addressRepo = new EFRepository<Address>();
var address = addressRepo.Save(new Address() { Country=country });
countryRepo.Delete(country);
This should fail due to the existence of a dependent Address. However, afterwards the address ends up with a null in CountryID, which is invalid because Address.CountryID is required, so subsequent SaveChanges calls throw a validation exception unless the address is detached.
I expected that when an object is deleted, EF5 will be smart enough to first check for any cascade-delete constraints like the one above and, failing to find any, then proceed to delete the data. But exactly the opposite seems to be the case.
Is this a normal behaviour or am I doing something wrong?
Part 2:
Following a failed SaveChanges call, some Addresses are now in an invalid state in my DbContext and need to be restored to their original values. Of course, I can always do so explicitly for each entity type (Country, State, Order, etc.) by creating specialized repository classes and overriding Delete, but it smells big time. I'd much rather write some general purpose code to gracefully recover related entities after a failed SaveChanges call.
It would require interrogating DbContext to get all relationships in which an entity (e.g. Country) is the principal, regardless of whether or not its class defines navigational properties to dependent entities.
E.g. Country has no Addresses property, so I need to somehow find in DbContext the definition of the one-to-many relationship between Country and Address and use it to restore all related Addresses to their original values.
Is this possible?
Answering my own question in Part 2:
Here is my approach to checking for related dependents when deleting an entity on the principal end of a many-to-one relationship and where dependents are NOT exposed as a navigation collection in the principal (e.g. class Address has a Country property, but class Country doesn't have an Addresses collection).
DbContext
Add the following method to the context class:
/// <summary>
/// Returns an array of entities tracked by the
/// context that satisfy the filter criteria.
/// </summary>
public DbEntityEntry[] GetTrackedEntities<T>(
Expression<Func<DbEntityEntry<T>, bool>> filterCriteria)
where T : class
{
var result = new List<DbEntityEntry>();
var doesItMatch = filterCriteria.Compile();
foreach (var entry in this.ChangeTracker.Entries<T>())
{
if (doesItMatch(entry))
result.Add(entry);
}
return result.ToArray();
}
Repositories
Create a repository for each class that has some dependencies, override the Delete method and use the new GetTrackedEntities<T> method to get all related dependents and either:
explicitly delete them if they are cascade-deletable in code
detach them from the context if they are cascade-deletable in the DB itself
throw an exception if they are NOT cascade-deletable.
Example of the latter case:
public class EFCountryRepository :
EFReadWriteRepository<Country, string>,
ICountryRepository
{
public override void Delete(Country instance)
{
// Allow the Country to be deleted only if there are no dependent entities
// currently in the context that are NOT cascade-deletable.
if (
// are there any Regions in the context that belong to this Country?
_dbContext.GetTrackedEntities<Region>(e =>
e.Entity.CountryID == instance.ID ||
e.Entity.Country == instance).Length > 0
||
// are there any Addresses in the context that belong to this Country?
_dbContext.GetTrackedEntities<Address>(e =>
e.Entity.CountryID == instance.ID ||
e.Entity.Country == instance).Length > 0
)
throw new Exception(String.Format(
"Country '{0}' is in use and cannot be deleted.", instance.ID));
base.Delete(instance);
}
// ... other methods ...
}
Example of a case where cascade-deleting will be done by the DB itself, so all we need to do is detach the dependents from the context:
public class EFOrderRepository :
EFReadWriteRepository<Order, string>,
IOrderRepository
{
public override void Delete(Order instance)
{
foreach (var orderItem in _dbContext.GetTrackedEntities<OrderItem>(e =>
e.Entity.OrderID == instance.ID ||
e.Entity.Order == instance))
{
_dbContext.Entry(orderItem).State = System.Data.EntityState.Detached;
}
base.Delete(instance);
}
// ... other methods ...
}
Hope someone will find this solution helpful.
I am using EF 5.0 and the model first approach. I have build a GenericRepository that has the basic get, insert, delete etc statements. Like:
public virtual void Insert(TEntity entity)
{
dbSet.Add(entity);
}
My EF entities all have the attributes Modified and ModifiedBy. Now I want to change this values everytime I save an entity.
Is it possible to modify this two attributes (set the value) without writing an specific implementation all the time?
Thank you
I see two options for you to do this, but they both entail either introducing a base type or an interface for all of your entities to cover them in a generic function. I would prefer an interface, although each entity would have to implement it again and again.
Let's say you create
interface IAuditable
{
DateTime Modified { get; set; }
string ModifiedBy {get; set; } // User id?
}
Now you can do:
public virtual void Insert(TEntity entity)
where TEntity : IAuditable
{
entity.Modified = DateTime.Now;
entity.ModifiedBy = ???? // Whatever you get the name from
...
}
(Same for edit)
You can also subscribe to the context's SavingChanges event:
// In the constructor:
context.SavingChanges += this.context_SavingChanges;
private void context_SavingChanges(object sender, EventArgs e)
{
foreach (var auditable in context.ObjectStateManager
.GetObjectStateEntries(EntityState.Added | EntityState.Modified)
.Select(entry => entry.Entity)
.OfType<IAuditable>)
{
auditable.Modified = DateTime.Now;
auditable.ModifiedBy = ????;
}
}
If you work with DbContext you can get to the event by
((IObjectContextAdapter)this).ObjectContext.SavingChanges
I'd like to add that more reliable time tracking can (and maybe should) be achieved by database triggers. Now you depend on a client's clock.
You can do this using the following code in your all methods of repository where you want to.
public virtual void Edit(TEntity entity)
{
entity.Modified=DateTime.Now;
entity.ModifiedBy=User.Identity.Name;
//Other saving to repository code
}
I'm dynamically creating my DbContext by iterating over any entities that inherit from EntityBase and adding them to my Context:
private void AddEntities(DbModelBuilder modelBuilder)
{
var entityMethod = typeof(DbModelBuilder).GetMethod("Entity");
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
var entityTypes = assembly.GetTypes()
.Where(x => x.IsSubclassOf(typeof(EntityBase)) && !x.IsAbstract);
foreach (var type in entityTypes)
{
dynamic entityConfiguration = entityMethod.MakeGenericMethod(type).Invoke(modelBuilder, new object[] { });
EntityBase entity = (EntityBase)Activator.CreateInstance(type);
//Add any specific mappings that this class has defined
entity.OnModelCreating(entityConfiguration);
}
}
}
That way, I can have many namespaces but just one generic repository in my base namespace that's used everywhere. Also, in apps that make use of multiple namespaces, the base repository will already be setup to use all the entities in all the loaded namespaces. My problem is, I don't want to make EntityFramework.dll a dependency of every namespace in the company. So I'm calling OnModelCreating and passing the EntityTypeConfiguration to the class so it can add any mappings. This works fine and here's how I can add a mapping to tell the model that my "Description" property comes from a column called "Descriptor":
class Widget... {
public override void OnModelCreating(dynamic entity)
{
System.Linq.Expressions.Expression<Func<Widget, string>> tmp =
x => x.Description;
entity.Property(tmp).HasColumnName("Descriptor");
}
The good thing is, my entity class has no reference to EF, this method is only called once, when the context is created and if we scrap EF and go to something else in the future, my classes won't have all sorts of attributes specific to EF in them.
The problem is, it's super ugly. How can I let the model know about column mappings and keys in a simpler way than creating these Expressions to get properties to map without hard coding references to EF all over my poco classes?
You could define your own Attributes and use these to control the configuration within OnModelCreating(). You should be able to gain (using reflection) all the details you need for column mapping in one linq query a second query for the creation of the key.
public class DatabaseNameAttribute : Attribute
{
private readonly string _name;
public DatabaseNameAttribute(string name)
{
_name = name;
}
public string Name
{
get
{
return _name;
}
}
}
public class KeySequenceAttribute : Attribute
{
private readonly int _sequence;
public KeySequenceAttribute(int sequence)
{
_sequence = sequence;
}
public int Sequence
{
get
{
return _sequence;
}
}
}
[DatabaseName("BlogEntry")]
public class Post
{
[DatabaseName("BlogId")]
[KeySequence(1)]
public int id { get; set; }
[DatabaseName("Description")]
public string text { get; set; }
}
I have a repository like that:
public class Repository<T> : IRepository<T> where T : class
{
private readonly IRepositoryContext _repositoryContext;
public Repository(IRepositoryContext repositoryContext)
{
_repositoryContext = repositoryContext;
_objectSet = repositoryContext.GetObjectSet<T>();
}
public virtual void Update(T entity)
{
ObjectSet.AddObject(entity);
_repositoryContext.ObjectContext.ObjectStateManager.ChangeObjectState(entity, EntityState.Modified);
_repositoryContext.SaveChanges();
}
}
Now that actually works for all scalar properties of the entity, but all the other entities that associated with properties of entity typeOf(T), don't care that entity state is modified, and EF simply adds new data.
So, if you do for example Repository<Student>.Update(), and you only changed the name, it will find the right Student and change his name, but it also will change the Campus, although you already have a Campus associated with that student, it will be created again with a different CampusId.
Show me please the correct way to do updates in this situation.
What I did when I wanted to follow generic approach was translated to your code something like:
public class Repository<T> : IRepository<T> where T : class
{
...
public virtual void Update(T entity)
{
if (context.ObjectStateManager.GetObjectStateEntry(entity).State == EntityState.Detached)
{
throw new InvalidOperationException(...);
}
_repositoryContext.SaveChanges();
}
}
All my code then worked like:
var attachedEntity = repository.Find(someId);
// Merge all changes into attached entity here
repository.Update(attachedEntity);
=> Doing this in generic way moves a lot of logic into your upper layer. There is no better way how to save big detached object graphs (especially when many-to-many relations are involved and deleting of relations is involved).