Can I use strongly typed POCOs as related values with EF code first without creating new ones every time? - entity-framework

I have a status field on a class that has an ID and a Name. I'm not using an enum to model it, but rather a class with some static values, like this:
public class MailoutStatus : IEntity
{
public static MailoutStatus Draft = new MailoutStatus() { Id = 1, Name = "Draft" };
public static MailoutStatus Scheduled = new MailoutStatus() { Id = 2, Name = "Scheduled" };
public static MailoutStatus Cancelled = new MailoutStatus() { Id = 3, Name = "Cancelled" };
public static MailoutStatus Sent = new MailoutStatus() { Id = 4, Name = "Sent" };
public int Id { get; set; }
public string Name { get; set; }
...
}
Now I want to set this status value on the object it describes, like so:
var repo = new MailoutRepository();
var mailout = repo.Get(1);
mailout.Status = MailoutStatus.Cancelled;
repo.Update(mailout);
repo.CommitChanges();
However, this code will see MailoutStatus.Cancelled as a new entity and will insert a new row into the MailoutStatus table, ignoring the ID that is already on Cancelled and adding a new IDENTITY generated ID (for instance, 5). I can prevent this by adding an entityvalidation stuff, but that just makes the above blow up due to the validation failure.
I can work around the issue using this code:
var repo = new MailoutRepository();
var mailout = repo.Get(1);
mailout.Status = new MailoutStatusRepository().Get(MailoutStatus.Cancelled.Id);
repo.Update(mailout);
repo.CommitChanges();
This works because now Entity Framework knows about the MailoutStatus that I'm fetching and is tracking its state, etc. But it's really crappy to have to write that much code just to set a status. I also don't want to use an enum for other reasons and I don't want MailoutStatus to know anything about persistence. Any ideas?

Here's how I solved it.
I defined an attribute named NotTrackedAttribute and apply that on entities like Status. Then override the SaveChanges method of the derived context as follows. Reset the tracked changes to those entities
public override int SaveChanges()
{
var changedEntities = ChangeTracker.Entries();
foreach (var changedEntity in changedEntities)
{
var entity = changedEntity.Entity;
//ignore the types that are marked as NotTracked
if (Attribute.IsDefined(entity.GetType(), typeof(NotTrackedAttribute)))
{
changedEntity.State = EntityState.Unchanged;
continue;
}
}
return base.SaveChanges();
}
The attribute
/// <summary>
/// Indicates that a Type having this attribute should not be persisted.
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public class NotTrackedAttribute : Attribute
{
}
Then use it as follows
[NotTracked]
public class MailoutStatus
{
}

You're already duplicating what's in the database. If you change your model to now just have an integer status, then you can change the MailoutStatus to a static int and it will just work.
In other words, what are you gaining by having MailoutStatus as another entity, when in fact it's just a lookup value?

Now EF is supporting enums. http://blogs.msdn.com/b/efdesign/archive/2011/06/29/enumeration-support-in-entity-framework.aspx. In code first you can have a discriminater column to map enum.
Or else this is a good solution Enums with EF code-first - standard method to seeding DB and then using?

Related

Is it possible to convert ObjectSet to DbSet in Entity Framework 6?

I am working on upgrading a WPF application from using .Net4/EF 4.4 to .Net4.5/EF 6.1. After the upgrade I will use DbContext (since there was no POCO-generator for ObjectContext).
The application use a Repository/UnitOfWork-pattern to access Entity Framework, and before the upgrade I could set the ObjectSet.MergeOption to OverwriteChanges (in the repository-class), but the DbSet-class does not have this feature. However, I know that I can get to a ObjectSet from the DbContext by using the IObjectContextAdapter. (See code below). But it seems that setting the MergeOption on the created ObjectSet will not reflect back to the DbSet.
So my question is this: is there any way to convert the ObjectSet back to a DbSet (conserving the MergeOption-setting)?
This is some of the repository class:
public class SqlRepository<T> : IRepository<T> where T : class, IEntity
{
protected DbSet<T> dbSet;
public SqlRepository(DbContext context)
{
var objectContext = ((IObjectContextAdapter)context).ObjectContext;
var set = objectContext.CreateObjectSet<T>();
set.MergeOption = MergeOption.OverwriteChanges;
dbSet = context.Set<T>();
//I would like to do something like this: dbSet = (DbSet)set;
}
}
Although not a direct answer to your question, I have come up with a T4 based solution to the EF oversight around MergeOption not being accessible in DbSet. It seems from your question this is what you are looking for?
In the default Context you have something generated by the T4 generator like:
public virtual DbSet<Person> Persons { get; set; }
public virtual DbSet<Address> Addresses { get; set; }
etc.
My approach is to edit the T4 to add Getters for each Entity that provide direct access the ObjectSet as an IQueryable:
public IQueryable<Person> GetPersons(MergeOption mergeOption = MergeOption.AppendOnly, bool useQueryImplentation = true)
{
return useQueryImplementation ? GetSet<Person>(mergeOption).QueryImplementation() : GetSet<Person>(mergeOption);
}
Then in a base DataContext
public class DataContextBase
{
/// <summary>
/// Gets or sets the forced MergeOption. When this is set all queries
/// generated using GetObjectSet will use this value
/// </summary>
public MergeOption? MergeOption { get; set; }
/// <summary>
/// Gets an ObjectSet of type T optionally providing a MergeOption.
/// <remarks>Warning: if a DataContext.MergeOption is specified it will take precedence over the passed value</remarks>
/// </summary>
/// <typeparam name="TEntity">ObjectSet entity Type</typeparam>
/// <param name="mergeOption">The MergeOption for the query (overriden by DataContext.MergeOption)</param>
protected IQueryable<TEntity> GetObjectSet<TEntity>(MergeOption? mergeOption = null) where TEntity : class
{
var set = Context.CreateObjectSet<TEntity>();
set.MergeOption = MergeOption ?? mergeOption ?? MergeOption.AppendOnly;
return set;
}
By creating a default Extension method for an IQueryable as below you can optionally add your own implenations of QueryImplementation for each table/type so that all users of your table get sorting or includes etc. (this part is not required to answer the question but its useful anyway)
So for example you could add the following to always Include Addresses when calling GetPersons()
public static class CustomQueryImplentations
{
public static IQueryable<Person> QueryImplementation(this IQueryable<Person> source)
{
return source
.Include(r => r.Addresses)
.OrderByDescending(c => c.Name);
}
}
Finally:
//just load a simple list with no tracking (Fast!)
var people = Database.GetPersons(MergeOption.NoTracking);
//user wants to edit Person so now need Attached Tracked Person (Slow)
var peson = Database.GetPersons(MergeOption.OverwriteChanges).FirstOrDefault(p => p.PersonID = 1);
//user makes changes and on another machine sometime later user clicks refresh
var people = Database.GetPersons(MergeOption.OverwriteChanges);
Or you can (as I have) write something like
Database.MergeOption = MergeOption.OverwriteChanges;
refresh loads of entities using existing Get methods but will now ALL overwrite Attached entities
Database.MergeOption = null;
Something to note is that if you load AsNoTracking before you make changes you need to either Re-Attach or probably better reload with OverwriteChanges to ensure you have the latest Entity.

How to create generic EF Insert method?

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;
}
}

Entity Framework 5 - Immediately refresh DbContext after saving changes

I have an MVC application that uses Entity Framework 5. In few places I have a code that creates or updates the entities and then have to perform some kind of operations on the updated data. Some of those operations require accessing navigation properties and I can't get them to refresh.
Here's the example (simplified code that I have)
Models
class User : Model
{
public Guid Id { get; set; }
public string Name { get; set; }
}
class Car : Model
{
public Guid Id { get; set; }
public Guid DriverId { get; set; }
public virtual User Driver { get; set; }
[NotMapped]
public string DriverName
{
get { return this.Driver.Name; }
}
}
Controller
public CarController
{
public Create()
{
return this.View();
}
[HttpPost]
public Create(Car car)
{
if (this.ModelState.IsValid)
{
this.Context.Cars.Create(booking);
this.Context.SaveChanges();
// here I need to access some of the resolved nav properties
var test = booking.DriverName;
}
// error handling (I'm removing it in the example as it's not important)
}
}
The example above is for the Create method but I also have the same problem with Update method which is very similar it just takes the object from the context in GET action and stores it using Update method in POST action.
public virtual void Create(TObject obj)
{
return this.DbSet.Add(obj);
}
public virtual void Update(TObject obj)
{
var currentEntry = this.DbSet.Find(obj.Id);
this.Context.Entry(currentEntry).CurrentValues.SetValues(obj);
currentEntry.LastModifiedDate = DateTime.Now;
}
Now I've tried several different approaches that I googled or found on stack but nothing seems to be working for me.
In my latest attempt I've tried forcing a reload after calling SaveChanges method and requerying the data from the database. Here's what I've done.
I've ovewrite the SaveChanges method to refresh object context immediately after save
public int SaveChanges()
{
var rowsNumber = this.Context.SaveChanges();
var objectContext = ((IObjectContextAdapter)this.Context).ObjectContext;
objectContext.Refresh(RefreshMode.StoreWins, this.Context.Bookings);
return rowsNumber;
}
I've tried getting the updated object data by adding this line of code immediately after SaveChanges call in my HTTP Create and Update actions:
car = this.Context.Cars.Find(car.Id);
Unfortunately the navigation property is still null. How can I properly refresh the DbContext immediately after modifying the data?
EDIT
I forgot to originally mention that I know a workaround but it's ugly and I don't like it. Whenever I use navigation property I can check if it's null and if it is I can manually create new DbContext and update the data. But I'd really like to avoid hacks like this.
class Car : Model
{
[NotMapped]
public string DriverName
{
get
{
if (this.Driver == null)
{
using (var context = new DbContext())
{
this.Driver = this.context.Users.Find(this.DriverId);
}
}
return this.Driver.Name;
}
}
}
The problem is probably due to the fact that the item you are adding to the context is not a proxy with all of the necessary components for lazy loading. Even after calling SaveChanges() the item will not be converted into a proxied instance.
I suggest you try using the DbSet.Create() method and copy across all the values from the entity that you receive over the wire:
public virtual TObject Create(TObject obj)
{
var newEntry = this.DbSet.Create();
this.Context.Entry(newEntry).CurrentValues.SetValues(obj);
return newEntry;
}
UPDATE
If SetValues() is giving an issue then I suggest you try automapper to transfer the data from the passed in entity to the created proxy before Adding the new proxy instance to the DbSet. Something like this:
private bool mapCreated = false;
public virtual TObject Create(TObject obj)
{
var newEntry = this.DbSet.Create();
if (!mapCreated)
{
Mapper.CreateMap(obj.GetType(), newEntry.GetType());
mapCreated = true;
}
newEntry = Mapper.Map(obj, newEntry);
this.DbSet.Add(newEntry;
return newEntry;
}
I use next workaround: detach entity and load again
public T Reload<T>(T entity) where T : class, IEntityId
{
((IObjectContextAdapter)_dbContext).ObjectContext.Detach(entity);
return _dbContext.Set<T>().FirstOrDefault(x => x.Id == entity.Id);
}

When does an Entity show up in the collection of the Model

I use entity framework and have a set of users:
public class DbModel : DbContext
{
public DbSet<User> Users { get; set; }
I add a User like so:
User UserOne = new User();
model.Users.Add( UserOne );
I request the users count:
int userCount = model.Users.Count();
userCount is "0" I would expect "1". Adding DetectChanges doen't help.
After "model.SaveChanges()" the Count = 1, but that is to late I need to combine the in memory stuff with the DB stuff for validation. Is there a way to do this?
SOLUTION
Using the answer of Erik Philips I wrote the following extension method for the DbSet
public static class DBSetExtentions
{
public static IEnumerable<T> AllMembers<T>(
this DbSet<T> target,
Func<T, bool> selection
) where T : class
{
return target.Local.Where(selection).Union(target.Where(selection));
}
}
it allows me to do selections an validations accross all entities like:
private void ValidateEmail(ValidationDto validationDto)
{
int usersWithSameEmail =
validationDto.Model.Users.AllMembers(
x => x.EmailAddress.Equals( EmailAddress ) ).Count();
if (usersWithSameEmail > 1)
{
validationDto.Result.Add(new ValidationResult("Email address is in use"));
}
}
You can query the client side of items (committed and uncomitted) to your data storage by using Local.
var count = model.Users.Local.Count();
One caveat is that this is only the local representation of Users. Meaning it could contain a partial amount of users from the database (changed and/or unchanged), and new users you've created and not saved.
Interesting Article - Using DbContext in EF 4.1 Part 7: Local Data

Entity framework CTP5 Violation of PRIMARY KEY constraint

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).