Entity Framework: How do I update nullable properties - entity-framework

Say I have this class:
public class BuzzKill
{
public string Name {get;set;}
public DateTime? StrikeDate {get;set;}
}
If I write an instance of this class to a database with a null StrikeDate, then attempt to update it and save it using the method below (taken from Julia Lehrman's book, DbContext), I receive the following exception:
System.InvalidOperationException : The original value for the property 'StrikeDate' cannot be set to null because the 'StrikeDate' member on the entity type 'BuzzKill' is not nullable.
Obviously, the member is nullable. Does anyone have a workaround or solution? I'm using EF 5.0.
Here is the code I'm using to update entities, along with a comment noting where the exception gets thrown:
public virtual void ApplyChanges<TEntity>(TEntity root)
where TEntity : class, IObjectWithState
{
using (var context = new AmazingChartsContext())
{
context.Set<TEntity>().Add(root);
CheckForEntitiesWithoutStateInterface(context);
foreach (var entry in context.ChangeTracker.Entries<IObjectWithState>())
{
IObjectWithState stateInfo = entry.Entity;
entry.State = ConvertState(stateInfo.State);
if (stateInfo.State == State.Unchanged)
{
ApplyPropertyChanges(entry.OriginalValues, stateInfo.OriginalValues);
}
}
try
{
context.SaveChanges();
}
catch (DbEntityValidationException ex)
{
//stuff...
}
}
}
protected void ApplyPropertyChanges(DbPropertyValues values, Dictionary<string, object> originalValues)
{
foreach (var originalValue in originalValues)
{
if (originalValue.Value is Dictionary<string, object>)
{
ApplyPropertyChanges((DbPropertyValues)values[originalValue.Key],
(Dictionary<string, object>)originalValue.Value);
}
else
{
values[originalValue.Key] = originalValue.Value; //here's where the exception gets thrown
}
}
}
(I read about this issue here, but no one seemed to have an answer other than this was a bug in 4.3.)

Related

How can I return the ID of the Inserted Record in Entity Framework Generic Insert Method?

Here is the generic insert method. I need your suggestion to return the ID of the inserted record.
public static void Create<T>(T entity) where T : class
{
using (var context = new InformasoftEntities())
{
DbSet dbSet = context.Set<T>();
dbSet.Add(entity);
context.SaveChanges();
}
}
Arturo Martinex is correct in his comment.
Entity framework fixes up the ID's during SaveChanges so it's already updated in the entity you passed in to the method.
To do specifically what you ask you could change your generic constraint from class to a new abstract class that all your entities inherit, which defines the key in that class.
public static int Create<T>(T entity) where T : BaseEntity
{
using (var context = new InformasoftEntities())
{
DbSet dbSet = context.Set<T>();
dbSet.Add(entity);
context.SaveChanges();
return entity.Id;
}
}
public abstract class BaseEntity
{
int Id { get; set;}
}
This technique is more useful in an InsertOrUpdate method
Another way to work with keys inside generic methods is to interrogate the MetaData as described here:
The key to AddOrUpdate
You need a little modification:
You need to create an IHasAutoID that implemented by Entity
public interface IHasAutoID {
int getAutoId();
}
In Entity Class
public class EntityA : IHasAutoID {
public int getAutoId() {
return pk; // Return -1 If the entity has NO Auto ID
}
}
In your Create function
public static int Create<T>(T entity) where T : class
{
using (var context = new InformasoftEntities())
{
DbSet dbSet = context.Set<T>();
dbSet.Add(entity);
context.SaveChanges();
if (entity is IHasAutoID) {
return ((IHasAutoID)entity).getAutoId();
}
return -1; // entity is NOT IHasAutoID)
}
}
NOTES:
If you are sure all tables have Auto ID with named "Id". You don't need to create Interface IHasAutoID. In Create function, after SaveChanges, You use REFLECTION to get value of Id property, but this way is not recommended!
public async Task<int> Add(TEntity entity)
{
await _context.Set<TEntity>().AddAsync(entity);
await Save();
return Task.FromResult(entity).Id;
}

entity-framework get entityreference value

I have an entity object which is connected to another entities.
I want to loop through all entity properties , if the property is String then do something with the value.
If the property is EntityReference, I want to get it's value (it has only one), and do something with the value as well.
I was able to determine if the property is string or EntityReference.
I get the String value by -
value = typeof(entity).GetProperty(property.Name).GetValue(request, null);
but how do I get the value of an entityreference ?
Just trace the property tree.
You have the first step. repeat for lower properties.
var TopLevelProp = poco.GetType().GetProperty(property.Name).GetValue(poco, null);
var LowerProp = TopLevelProp.GetType().GetProperty("aLowerPropName").GetValue(TopLevelProp, null);
although you tagged this EF. What did you mean by entity reference ?
edit: in the hope i have covered the entity and its key question
Here is a sample Repository covering EF Context and entity access. See the Entity field and Entity KEY field methods...
public class Repository<T> : IRepositoryEF<T> where T : BaseObject {
public RepositoryEF(DbContext context) { Context = context; }
public DbEntityEntry<T> Entry(T entity) { return Context.Entry(entity); }
public DbSet<T> EntityDbSet() { return Context.Set<T>(); }
public ObjectContext ObjectContext { get { return ((IObjectContextAdapter) this.Context).ObjectContext; } }
public DbContext Context { get; protected set; }
public EntityState GetEntityState(object entity) { return Context.Entry(entity).State; }
public ObjectSet<T> GetObjectSet() { return ObjectContext.CreateObjectSet<T>(); }
public IList<string> GetEntityFields() {
var entityFields = GetObjectSet().EntitySet.ElementType.Properties;
return entityFields.Select(e => e.Name).ToList();
}
public string[] GetEntityKeyFields() { return GetObjectSet().EntitySet.ElementType.KeyMembers.Select(k => k.Name).ToArray(); }
public EntityKey GetEntityKey(T entity) {
if (entity == null) {
return null;
}
return ObjectContext.CreateEntityKey(GetObjectSet().EntitySet.Name, entity);
}
public string GetEntityKeyAsString(T entity) {
if (entity == null) {
return string.Empty;
}
var eK = GetEntityKey(entity);
var keyAsString = eK.EntityKeyValues.Aggregate("", (current, keyField) => current + keyField.Key + "=" + keyField.Value + ",");
return keyAsString;
}
}
If you want to get all the metadata in the Context:
ObjectContext objContext = ((IObjectContextAdapter)context).ObjectContext;
MetadataWorkspace workspace = objContext.MetadataWorkspace;
IEnumerable<EntityType> managedTypes = workspace.GetItems<EntityType>(DataSpace.OSpace);
You can go to town on the meta data. see all enums values in DataSpace to get at various parts of the model

Exception when adding more than one entity at once

Whenever I do something like the following:
public class MyDto
{
[Key]
public int ID { get; set; }
public int ParentID { get; set; }
public String Name { get; set; }
}
MyDataContext dataContext = new MyDataContext();
MyParentDto myParentDto; // <-- Existing parent DTO querried from the server. Has a relation to MyDto on MyDto.ParentID == MyParentDto.ID.
List<MyDto> myDtos = new List<MyDto>();
myDtos.Add(new MyDto
{
Name = "First MyDto!"
});
myDtos.Add(new MyDto
{
Name = "Second MyDto!"
});
// Some time later.
foreach (var myDto in myDtos)
{
myDto.ParentID = myParentDto.ID;
dataContext.MyDtos.Add(myDto);
}
dataContext.SubmitChanges(OnMyCallback)
I get the following vague exception, but my data submits just fine:
System.ServiceModel.DomainServices.Client.DomainOperationException: Submit operation failed. An entity with the same identity already exists in this EntitySet.
The stack trace ends with:
System.ServiceModel.DomainServices.Client.EntitySet.AddToCache(Entity entity)
System.ServiceModel.DomainServices.Client.Entity.AcceptChanges()
Both MyDto instances are set to Detached before they are added to dataContext and New afterwards. If I reduce the number of added MyDto instances to one, I get no error. If I call SubmitChanges in between the two adds. Again, both of the MyDto instances are added to the database just fine, but the client crashes with the Exception. What is going on? Thanks.
Edits:
// On the server
[Insert]
public void InsertMyDto(MyDto a_myDto) // <- Yes I prefix. :p
{
try
{
MyEntity myEntity = new MyDto
{
ParentID = a_myDto.ParentID,
Name = a_myDto.Name
}
ObjectContext.MyEntities.AddObject(myEntity);
ObjectContext.SaveChanges();
}
catch (Exception)
{
throw; // <- Never hits this spot.
}
}
// Call back
public void OnMyCallback(SubmitOperation a_submitOperation)
{
if (a_submitOperation.HasError)
throw a_submitOperation.Error; // <- It doesn't matter if I have this or not, the client crashes anyway.
// Other stuff that is not hit because it throws the exception above.
}
I found that the solution to my problem is to save the ID back to the dto when the entity is saved. Like this:
[Insert]
public void InsertMyDto(MyDto a_myDto) // <- Yes I prefix. :p
{
try
{
MyEntity myEntity = new MyDto
{
ParentID = a_myDto.ParentID,
Name = a_myDto.Name
}
ObjectContext.MyEntities.AddObject(myEntity);
ObjectContext.SaveChanges();
a_myDto.ID = myEntity.ID; // <- Solution
}
catch (Exception)
{
throw; // <- Never hits this spot.
}
}
Have you tried setting the parent instead of it's ID?
foreach (var myDto in myDtos)
{
myDto.Parent = myParentDto;
} //Assuming myParentDto is already in the context, if not add it first
Edit: I'm taking a wild guess here but could you check the HashCode of the objects right before the Exception occurs? You could also try overriding the GetHashCode() method to return something random every time just to test those are the exact entities involved in the exception.

Entity Framework 4.1 bug in ObjectContext.SavingChanges handling (?)

I have a problem with something that seems to be a bug in Entity Framework 4.1: I have added a handler on ObjectContext.SavingChanges which updates a property "LastModified" whenever an object is added to or modified in the database. Then I do the following:
Add two objects to the database, and submit (call SaveChanges())
Modify the first object that was added
Extract the two objects ordered by LastModified
The resulting objects are returned in the wrong order. Looking at the objects, I can see that the LastModified property has been updated. In other words, the SavingChanges event was fired properly. But looking in the database, the LastModified column has not been changed. That is, there is now a difference between EF's cached objects and the rows in the database.
I tried performing the same update to LastModified in an overridden "SaveChanges" method:
public override int SaveChanges()
{
SaveChangesHandler();//updating LastModified property on all objects
return base.SaveChanges();
}
Doing this caused the database to be updated properly and the queries returned the objects in proper order.
Here is an entire test program showing the error:
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Reflection;
using System.Threading;
namespace TestApplication
{
class Program
{
private PersistenceContext context;
private static void Main(string[] args)
{
var program = new Program();
program.Test();
}
public void Test()
{
SetUpDatabase();
var order1 = new Order {Name = "Order1"};
context.Orders.Add(order1);
var order2 = new Order {Name = "Order2"};
context.Orders.Add(order2);
context.SaveChanges();
Thread.Sleep(1000);
order1 = GetOrder(order1.Id); // Modified 1.
order1.Name = "modified order1";
context.SaveChanges();
List<Order> orders = GetOldestOrders(1);
AssertEquals(orders.First().Id, order2.Id);//works fine - this was the oldest object from the beginning
Thread.Sleep(1000);
order2 = GetOrder(order2.Id); // Modified 2.
order2.Name = "modified order2";
context.SaveChanges();
orders = GetOldestOrders(1);
AssertEquals(orders.First().Id, order1.Id);//FAILS - proves that the database is not updated with timestamps
}
private void AssertEquals(long id1, long id2)
{
if (id1 != id2) throw new Exception(id1 + " != " + id2);
}
private Order GetOrder(long id)
{
return context.Orders.Find(id);
}
public List<Order> GetOldestOrders(int max)
{
return context.Orders.OrderBy(order => order.LastModified).Take(max).ToList();
}
public void SetUpDatabase()
{
//Strategy for always recreating the DB every time the app is run.
var dropCreateDatabaseAlways = new DropCreateDatabaseAlways<PersistenceContext>();
context = new PersistenceContext();
dropCreateDatabaseAlways.InitializeDatabase(context);
}
}
////////////////////////////////////////////////
public class Order
{
public virtual long Id { get; set; }
public virtual DateTimeOffset LastModified { get; set; }
public virtual string Name { get; set; }
}
////////////////////////////////////////////////
public class PersistenceContext : DbContext
{
public DbSet<Order> Orders { get; set; }
public PersistenceContext()
{
Init();
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
}
public void Init()
{
((IObjectContextAdapter) this).ObjectContext.SavingChanges += SavingChangesHandler;
Configuration.LazyLoadingEnabled = true;
}
private void SavingChangesHandler(object sender, EventArgs e)
{
DateTimeOffset now = DateTimeOffset.Now;
foreach (DbEntityEntry entry in ChangeTracker.Entries()
.Where(entity => entity.State == EntityState.Added || entity.State == EntityState.Modified))
{
SetModifiedDate(now, entry);
}
}
private static void SetModifiedDate(DateTimeOffset now, DbEntityEntry modifiedEntity)
{
if (modifiedEntity.Entity == null)
{
return;
}
PropertyInfo propertyInfo = modifiedEntity.Entity.GetType().GetProperty("LastModified");
if (propertyInfo != null)
{
propertyInfo.SetValue(modifiedEntity.Entity, now, null);
}
}
}
}
I should add that the SavingChanges handler worked fine before we upgraded to EF4.1 and using Code-First (that is, it worked in EF4.0 with model-first)
The question is: Have I found a bug here, or have I done something wrong?
I'm not sure if this can be considered a Bug. What seems to happen is that the way you manipulate the LastModified property does not trigger INotifyPropertyChanged and thus the changes do not get populated to your Database.
To prove it use:
order2.Name = "modified order2";
((IObjectContextAdapter)context).ObjectContext.ObjectStateManager.GetObjectStateEntry(order2).SetModifiedProperty("LastModified");
To utilize this knowledge in your SavingChangesHandler:
private void SavingChangesHandler(object sender, EventArgs e)
{
DateTimeOffset now = DateTimeOffset.Now;
foreach (DbEntityEntry entry in ChangeTracker.Entries()
.Where(entity => entity.State == EntityState.Added || entity.State == EntityState.Modified))
{
SetModifiedDate(now, entry);
if (entry.State == EntityState.Modified)
{
((IObjectContextAdapter) this).ObjectContext.ObjectStateManager.GetObjectStateEntry(entry.Entity).SetModifiedProperty("LastModified");
}
}
}
Edit:
I looked into this a little more and you are correct. For some reason MS decided to not fire PropertyChanged events when using PropertyInfo.SetValue anymore. Only one way to find out if this is a bug or a design decision: File a bug report / Post to msdn Forums.
Though changing the property directly via CurrentValue seems to work fine:
private static void SetModifiedDate(DateTimeOffset now, DbEntityEntry modifiedEntity)
{
if (modifiedEntity.Entity == null)
{
return;
}
modifiedEntity.Property("LastModified").CurrentValue = now;
}

Automapper Type Converter from String to IEnumerable<String> is not being called

Here is my custom Type Converter.
public class StringListTypeConverter : TypeConverter<String, IEnumerable<String>>
{
protected override IEnumerable<string> ConvertCore(String source)
{
if (source == null)
yield break;
foreach (var item in source.Split(','))
yield return item.Trim();
}
}
public class Source
{
public String Some {get;set;}
}
public class Dest
{
public IEnumerable<String> Some {get;set;}
}
// ... configuration
Mapper.CreateMap<String, IEnumerable<String>>().ConvertUsing<StringListTypeConverter>();
Mapper.CreateMap<Source, Dest>();
The problem: StringListTypeConverter is not being called at all. Dest.Some == null.
Update: Automapper version 1.0.0.155
I don't know if this helps or not but I just wrote a similar converter, see below. I don't mind admitting that the yield statements in your converter have me a little confused. :)
public class CsvToStringArrayConverter: ITypeConverter<string, string[]>
{
#region Implementation of ITypeConverter<string,string[]>
public string[] Convert(ResolutionContext context)
{
if (context.SourceValue != null && !(context.SourceValue is string))
{
throw new AutoMapperMappingException(context, string.Format("Value supplied is of type {0} but expected {1}.\nChange the type converter source type, or redirect the source value supplied to the value resolver using FromMember.",
typeof(string), context.SourceValue.GetType()));
}
var list = new List<string>();
var value = (string) context.SourceValue;
if(!string.IsNullOrEmpty(value))
list.AddRange(value.Split(','));
return list.ToArray();
}
#endregion
}
I hope it helps, apologies if I've completely misunderstood your problem!
It is verified that everything works fine for Automapper 1.1 RTW