I've faced with situation when I need to have EF readonly property in case of 'optimistic update'(you do not load current state of your domain object from database to check what properties are really changed. You just set your object as Modified and update it to database. You avoid redundant select and merge operations in this case).
You can't write something like this : DataContext.Entry(entity).Property(propertyName).IsModified = false;, because setting of 'false' value is not supported and you will get an exception. (in EF 4.1)
I've created a simple structure for registering readonly properties in repository.
So, you can easy Modify just nonreadonly properties.
What do you think about this?
public abstract class RepositoryBase<T> where T : class
{
private const string MethodReferenceErrorFormat = "Expression '{0}' refers to a method, not a property.";
private const string FieldReferenceErrorFormat = "Expression '{0}' refers to a field, not a property.";
protected IList<PropertyInfo> _readOnlyProperties;
/// <summary>
/// This method is used to register readonly property for Entity.
/// </summary>
/// <param name="propertyLambda">Entity property as LambdaExpression</param>
protected void RegisterReadOnlyProperty<TProperty>(Expression<Func<T, TProperty>> propertyLambda)
{
Guard.ArgumentNotNull(propertyLambda, "propertyLambda");
var propertyMember = propertyLambda.Body as MemberExpression;
if (propertyMember == null)
{
var exceptionMessage = string.Format(MethodReferenceErrorFormat, propertyLambda);
throw new ArgumentException(exceptionMessage);
}
var propertyInfo = propertyMember.Member as PropertyInfo;
if (propertyInfo == null)
{
var exceptionMessage = string.Format(FieldReferenceErrorFormat, propertyLambda);
throw new ArgumentException(exceptionMessage);
}
_readOnlyProperties.Add(propertyInfo);
}
/// <summary>
/// This method is used to attach domain object to DbContext and mark it as modified to save changes.
/// </summary>
/// <param name="entity">Detached entity</param>
public void SetModified(T entity)
{
Guard.ArgumentNotNull(entity, "entity");
//Mark whole entity as Modified, when collection of readonly properties is empty.
if(_readOnlyProperties.Count == 0)
{
DataContext.Entry(entity).State = EntityState.Modified;
return;
}
//Attach entity to DbContext.
_dbSet.Attach(entity);
//Mark all properties except readonly as Modified.
var allProperties = entity.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
var propertiesForUpdate = allProperties.Except(_readOnlyProperties);
foreach (var propertyInfo in propertiesForUpdate)
{
DataContext.Entry(entity).Property(propertyInfo.Name).IsModified = true;
}
}
This would work but I don't like the need to register modified properties directly in repository. You can forget about registered properties and code will accidentaly not save some changes - that will be bug which will be hard to find when reusing repository in complex scenarios. I like explicit definition of updated properties each time you call something like Update on your repository. Also I don't like reflection in the code. Unless you modify your code to get reflected data about each entity only once for whole application you are doing it wrong.
I wrote the answer for EFv4 but it can be easily modified to EFv4.1:
public void Update(T entity, params Expression<Func<T, object>>[] properties)
{
_dbSet.Attach(entity);
DbEntityEntry<T> entry = _context.Entry(entity);
foreach (var selector in properties)
{
entry.Property(selector).IsModified = true;
}
}
You will call it like:
repo.Update(entity, e => e.Name, e => e.Description);
Related
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.
I have a DbContext that is empty. Mappings are created dynamically and the DbContext is used generically using Set();
The following is my generic DbContext.
/// <summary>
/// Object context
/// </summary>
public class MethodObjectContext : DbContext, IDbContext
{
private readonly IEventPublisher _eventPublisher;
public MethodObjectContext(string nameOrConnectionString, IEventPublisher eventPublisher)
: base(nameOrConnectionString)
{
_eventPublisher = eventPublisher;
}
public MethodObjectContext(DbConnection existingConnection, bool contextOwnsConnection, IEventPublisher eventPublisher)
: base(existingConnection, contextOwnsConnection)
{
_eventPublisher = eventPublisher;
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
_eventPublisher.Publish(new ModelCreating(modelBuilder));
base.OnModelCreating(modelBuilder);
}
public new IDbSet<TEntity> Set<TEntity>() where TEntity : class
{
return base.Set<TEntity>();
}
}
I am trying write a unit test that will assert that the database is out of sync if I change the mappings (from the ModelCreating event).
The following is my test code.
[TestClass]
public class MigrationTests
{
private string _connectionString = string.Empty;
private string _testDb = string.Empty;
public MigrationTests()
{
_testDb = Path.Combine("C:\\", System.Reflection.Assembly.GetExecutingAssembly().GetName().Name.Replace(".", "") + ".sdf");
if (File.Exists(_testDb))
File.Delete(_testDb);
_connectionString = string.Format("Data Source={0};Persist Security Info=False;", _testDb);
Database.DefaultConnectionFactory = new SqlCeConnectionFactory("System.Data.SqlServerCe.4.0");
}
[TestMethod]
public void ThrowsErrorForOutOfDateDatabase()
{
// The initializer will handle migrating the database.
// If ctor param is false, auto migration is off and an error will be throw saying the database is out of date.
Database.SetInitializer(new MigrationDatabaseInitializer<MethodObjectContext>(false));
// Create the initial database and do a query.
// This will create the database with the conventions of the Product1 type.
TryQueryType<Product1>("Product");
// The next query will create a new model that has conventions for the product2 type.
// It has an additional property which makes the database (created from previous query) out of date.
// An error should be thrown indicating that the database is out of sync.
ExceptionAssert.Throws<InvalidOperationException>(() => TryQueryType<Product2>("Product"));
}
private void TryQueryType<T>(string tableName) where T : class
{
using (var context = new MethodObjectContext(_connectionString, new FakeEventPublisher(x => x.ModelBuilder.Entity<T>().ToTable(tableName))))
{
var respository = new EfRepository<T>(context);
var items = respository.Table.ToList();
}
}
}
My Product1 class is a POCO object, and my Product2 class is the same object with an additional db field.
My problem is that when I new() up the MethodObjectContext the second time and do a query, the ModelCreating method isn't called, causing me to get the following error.
The entity type Product2 is not part of the model for the current context.
Product2 would be a part of the context of the ModelCreating event was being called, but it is not. Any ideas?
NOTE: I am expecting errors since we are using the same connection string (sdf) and the db being created didn't create the additional field that my second call (Product2) requires.
My DbCompiledModel was being cached. The following flushed the cache.
private void ClearDbCompiledModelCache()
{
var type = Type.GetType("System.Data.Entity.Internal.LazyInternalContext, EntityFramework");
var cmField = type.GetField("CachedModels",System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic);
var cachedModels = cmField.GetValue(null);
cachedModels.GetType().InvokeMember("Clear", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.InvokeMethod, null, cachedModels, null);
}
I'm using EF4.1 for DAL for an application, and I use DbContext template generator with POCP entities. The model is created from the database, so all the fields / PK's / FK's / relations are already defined in database.
I need to find out in code which are the fields for the table for an entity.
Some tables might have a single field PK, while other might have compound PK. Whan I need is to have a method that will return me a List for an entity, with all field names composing the primary keys. It can be from DbContext, or from entity, doesn't matter.
I could even customize the template to generate a method in POCO entity, as below:
public List<string> PrimaryKey()
{
List<string> pk = new List<string>();
pk.AddRange(
new string[] {"Field1", "Field2"});
return pk;
}
but I don't know how to find the field names composing the PK.
Any suggestions?
Thank you
I did some research and I modified the template to generate a property that returns this for me.
First I customized the template to generate strong typed names for field names (I hate using strings in code which can cause problems when refactoring). Then that is used to generate a property that returns primary key fields as List
Here are the changes to template (I used ADO.NET DbContext Template Generator, but for any other template it should be very similar):
<#=Accessibility.ForType(entity)#>
<#=code.SpaceAfter(code.AbstractOption(entity))#>partial class <#=code.Escape(entity)#>
<#=code.StringBefore(" : ", code.Escape(entity.BaseType))#>
{
<#
WriteStrongTypedPropertyNames(code, entity); // <-- Insert this
WritePrimaryKeyProperty(code, entity); // <-- and this
// .....
And at the end of template file add:
<#+
void WriteStrongTypedPropertyNames(CodeGenerationTools code, EntityType entity)
{
#> /// <summary>
/// Strong typed property names
/// </summary>
public class PropertyNames
{
<#+
foreach (var property in entity.Properties)
{
#>
public const string <#=code.Escape(property)#> = "<#=property#>";
<#+
}
#>
}
<#+
}
void WritePrimaryKeyProperty(CodeGenerationTools code, EntityType entity)
{
#> /// <summary>
/// Returns primary key as List
/// </summary>
public List<string> PrimaryKey
{
get
{
List<string> pk = new List<string>();
pk.AddRange(
new string[] {
<#+
foreach (var member in entity.KeyMembers)
{
string delim = "";
#>
<#=delim#> PropertyNames.<#=code.Escape(member.Name)#>
<#+
delim=",";
}
#> });
return pk;
}
}
<#+
}
#>
It generates a code as below in the entity:
/// <summary>
/// Strong typed property names
/// </summary>
public class PropertyNames
{
public const string AppID = "AppID";
public const string AppName = "AppName";
public const string AppCode = "AppCode";
}
/// <summary>
/// Returns primary key as List
/// </summary>
public List<string> PrimaryKey
{
get
{
List<string> pk = new List<string>();
pk.AddRange(
new string[] {
PropertyNames.AppID
});
return pk;
}
}
Hope this helps someone
I had a tough time trying to do almost the same thing, getting the primary key name and value at runtime when the type is unknown, from a DbContext. I was just get trying to implement an auditing scheme for deletes, and every solution i find involves superfluous code that I dont really understand. The EntityKey is not available from a DbContext, which is also confusing and annoying. The last 5 lines may save you 5 hours and 1 yr of baldness. I am not attempting this for Inserts, so if you do, you need to inspect those values carefully as they may be 0 or null.
foreach(var entry in ChangeTracker.Entries<IAuditable>()) {
...
case EntityState.Deleted:
...
var oc = ((IObjectContextAdapter.this).ObjectContext; //.this is a DbContext EntityKey
ek = oc.ObjectStateManager.GetObjectStateEntry(entry.Entity).EntityKey;
var tablename= ek.EntitySetName;
var primaryKeyField = ek.EntityKeyValues[0].Key; //assumes only 1 primary key var
primaryKeyValue = ek.EntityKeyValues[0].Value;
stumbled across this post after having done something similar to #bzamfir. I thought I would post what I did in hopes that it might alleviate some of the headaches that #Bill mentions that I have also experienced!
Using the DBContext, I also modified the Template.
1) Add Imports System.ComponentModel.DataAnnotations to the list of Imports already in the file
2) Add code to the Primitive Properties For Each Loop which adds the KeyAttribute to the primary key properties. It should look like:
For Each edmProperty As EdmProperty In primitiveProperties
If ef.IsKey(edmProperty) Then
#><#= code.StringBefore("<KeyAttribute()>",Environment.NewLine & CodeRegion.GetIndent(region.CurrentIndentLevel + 2))#><#
End If
WriteProperty(code, edmProperty)
Next
You can add this Extension Method to your code somewhere which will find the Primary Key based off of the KeyAttribute.
<Extension()>
Public Function FindPrimaryKeyProperty(Of T As Class)(context As DbContext, TEntity As T) As PropertyInfo
Return TEntity.GetType().GetProperties().Single(Function(p) p.GetCustomAttributes(GetType(KeyAttribute), True).Count > 0)
End Function
I do recognize the point that this function will fail if you have more than one property flagged with KeyAttribute, however, for my situation this was not the case.
Alternatively, I just came across this solution that appears to work just fine and doesn't require any Template editing (+1).
http://blog.oneunicorn.com/2012/05/03/the-key-to-addorupdate/
public static IEnumerable<string> KeysFor(this DbContext context, Type entityType)
{
Contract.Requires(context != null);
Contract.Requires(entityType != null);
entityType = ObjectContext.GetObjectType(entityType);
var metadataWorkspace =
((IObjectContextAdapter)context).ObjectContext.MetadataWorkspace;
var objectItemCollection =
(ObjectItemCollection)metadataWorkspace.GetItemCollection(DataSpace.OSpace);
var ospaceType = metadataWorkspace
.GetItems<EntityType>(DataSpace.OSpace)
.SingleOrDefault(t => objectItemCollection.GetClrType(t) == entityType);
if (ospaceType == null)
{
throw new ArgumentException(
string.Format(
"The type '{0}' is not mapped as an entity type.",
entityType.Name),
"entityType");
}
return ospaceType.KeyMembers.Select(k => k.Name);
}
In EntityFramework, is that possible to query the objects that have just been added to the context using AddObject but before calling the SaveChanges method?
Thanks
To persist an entity you usually add it to it's DbSet in the context.
For example
var bar = new Bar();
bar.Name = "foo";
var context = new Context();
context.Bars.Add(bar);
Surprisingly, querying context.Bars, the just added entity cannot be found
var howMany = context.Bars.Count(b => b.Name == "foo");
// howMany == 0
After context.SaveChanges() the same line will result 1
The DbSet seems unaware to changes until they're persisted on db.
Fortunately, each DbSet has a Local property that acts like the DbSet itself, but it reflect all in-memory operations
var howMany = context.Bars.Local.Count(b => b.Name == "foo");
// howMany == 1
You can also use Local to add entities
context.Bars.Local.Add(bar);
and get rid of the weird behavior of Entity Framework.
you can query objects like this,
context.ObjectStateManager.GetObjectStateEntries(EntityState.Added).Select(obj => obj.Entity).OfType<TheEntityType>()
this will query the objects which are in added state. If you want other states too you can pass all other states to GetObjectStateEntries method like this.
GetObjectStateEntries(EntityState.Added | EntityState.Modified | EntityState.Unchanged)
In hibernate transient instances are already attached to context. Just stumbled upon this EF restriction.
I did not managed to intersect/union the ObjectSet with its transient entities ObjectSet.Local but for our usecase the below find method is sufficient.
In our cases we create some entities lazy depending on unique criteria during an iteration
Find method
If you are using an repository pattern you can create a method like:
public interface IRepository<T> where T : class, IEntity
{
/// <summary>
/// Finds the unique Entity with the given predicate.
/// Depending on implementation also checks transient / local (unsaved) Entities.
/// </summary>
/// <param name="predicate"></param>
/// <returns></returns>
IQueryable<T> FindAll(Expression<Func<T, bool>> predicate);
}
public class EfRepository<T> : IRepository<T> where T : class, IEntity
{
protected readonly ObjectContext context;
protected readonly ObjectSet<T> objectSet;
/// <summary>
/// Creates a new repository of the given context.
/// </summary>
/// <param name="context"></param>
public EfRepository(ObjectContext context)
{
if (context == null)
throw new ArgumentException("Context must not be null.");
this.context = context;
this.objectSet = context.CreateObjectSet<T>();
}
/// <summary>
/// Also takes local context into consideration for unsaved changes
/// </summary>
/// <param name="predicate"></param>
/// <returns></returns>
public T Find(Expression<Func<T, bool>> predicate)
{
T result = this.objectSet.Where(predicate).FirstOrDefault();
if (result == null)
result = this.objectSet.Local().Where(predicate).FirstOrDefault();
return result;
}
}
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?