I'm trying to send an object using WCF. The object is retrieved from the DB using EF.
This is the exception I get:
This only happens in an update scenario. Insert works perfectly.
Tracking the bug, I found that the problem is with a collection (called Travelers
) I added recently.
Here is what happens when I try to watch its value at runtime, after updating, before sending the updated entity by WCF:
Here's the offending class' property declaration (I tried uncommenting the DataMember
attribute but it didn't work):
[DataContract]
public class Travel : InsuredObject, ISaleEntity, ICloneable
{
//[DataMember]
public virtual ICollection<Traveler> Travelers { get; set; }
...
I've read that this.Configuration.ProxyCreationEnabled = false;
and/or this.Configuration.LazyLoadingEnabled = false;
might fix it, but I can't change those for reasons beyond me, and even when I tried playing with them - I got some other exceptions...
Additional Code:
The update method:
public virtual TEntity CreateAndUpdate(int saleId, TEntity entity) {
var context = ((IObjectContextAdapter)this.Context).ObjectContext;
var objBaseSet = context.CreateObjectSet<TBase>();
var entityBaseKey = context.CreateEntityKey(objBaseSet.EntitySet.Name, entity);
Object foundBaseEntity;
var baseExists = context.TryGetObjectByKey(entityBaseKey, out foundBaseEntity);
entity.Id = saleId;
if (!baseExists) {
this.GetDbSet<TEntity>().Add(entity);
}
this.objectContext.SaveChanges();
return entity;
}
Retrieving the containing object before updating:
public virtual IQueryable<TEntity> GetAll(Expression<Func<TEntity, bool>> where, bool brutalRefresh = false) {
IQueryable<TEntity> retObj = this.GetDbSet<TEntity>();
if (where != null) {
retObj = retObj.Where(where);
}
if (brutalRefresh) {
var context = ((IObjectContextAdapter)this.Context).ObjectContext;
context.Refresh(RefreshMode.StoreWins, retObj);
}
return retObj;
}
...All that code is common code with other projects, that send and receive the same entity as I do, it's just the Travel
entity I added, that causes me problems, so the solution I'm looking for should consist of 0 changes in common code..
Traveler
Class(fully):
[DataContract]
public class Traveler : ISaleEntity, ICloneable
{
[DataMember]
public int Id { get; set; }
[DataMember]
public string FirstName { get; set; }
[DataMember]
public string LastName { get; set; }
[DataMember]
public string IDNumber { get; set; }
[DataMember]
public DateTime? BirthDate { get; set; }
[DataMember]
public virtual ICollection<SelectedCoverage> SelectedCoverages { get; set; }
[NotMapped]
public List<MedicalQuestionnaireAnswer> MedicalQuestionnaireAnswers
{
get
{
if (string.IsNullOrWhiteSpace(DBMedicalQuestionnaireAnswers))
return new List<MedicalQuestionnaireAnswer>();
return DBMedicalQuestionnaireAnswers.Split(',')
.Select(c => (MedicalQuestionnaireAnswer)int.Parse(c)).ToList();
}
set { DBMedicalQuestionnaireAnswers = string.Join(",", value.Select(m => (int)m)); }
}
[NotMapped]
public Genders Gender
{
get { return (Genders)DBGender; }
set { DBGender = (int)value; }
}
/// <summary>
/// NOTE! Do not use this property directly! use MedicalQuestionnaireAnswers instead
/// </summary>
[DataMember]
public string DBMedicalQuestionnaireAnswers { get; set; }
/// <summary>
/// NOTE! Do not use this property directly! use Gender instead
/// </summary>
[DataMember]
public int DBGender { get; set; }
public object Clone()
{
Traveler traveler = new Traveler();
traveler.FirstName = this.FirstName;
traveler.LastName = this.LastName;
traveler.IDNumber = this.IDNumber;
traveler.BirthDate = this.BirthDate;
traveler.DBMedicalQuestionnaireAnswers = this.DBMedicalQuestionnaireAnswers;
traveler.Gender = this.Gender;
if (this.SelectedCoverages != null)
{
traveler.SelectedCoverages = this.SelectedCoverages.Select(sc => (SelectedCoverage)sc.Clone()).ToList();
}
return traveler;
}
}
public static class TravelerExtension
{
/// <summary>
/// copy all the property except from the id and src defualt values
/// </summary>
/// <param name="dbTraveler"></param>
/// <param name="src"></param>
public static void CopyTravelerProperties(this Traveler target, Traveler src)
{
target.FirstName = src.FirstName;
target.LastName = src.LastName;
target.IDNumber = src.IDNumber;
target.BirthDate = src.BirthDate;
target.DBMedicalQuestionnaireAnswers = src.DBMedicalQuestionnaireAnswers;
target.DBGender = src.DBGender;
target.SelectedCoverages.CopySelectedCoveragesProperties(src.SelectedCoverages);
}
}
public static class TravelersExtension
{
/// <summary>
/// copy all the property except from the id and src defualt values
/// </summary>
/// <param name="dbTravelers"></param>
/// <param name="src"></param>
public static void CopyTravelersProperties(this ICollection<Traveler> target, ICollection<Traveler> src)
{
List<int> allTravelersIdsSrc = src.Select(t => t.Id).ToList();
// remove ids exist target and not in src
target.ToList().RemoveAll(t => allTravelersIdsSrc.Contains(t.Id));
target = target ?? new List<Traveler>();
foreach (Traveler srcTraveler in src)
{
var targetTraveler = target.FirstOrDefault(targetTrv => srcTraveler.Id != 0 && targetTrv.Id == srcTraveler.Id);
// if not exist traveler with target traveler id in db
if (targetTraveler == null)
{
// add srcTraveler to target
target.Add(srcTraveler);
}
else
{
targetTraveler.CopyTravelerProperties(srcTraveler);
}
}
}
}
Further info:
The immediate window exception does not occur if calling ToList()
prior to trying to get the value in the immediate window. The problem itself persists though.
Trying to comment the [DataMember]
attribute on:
public virtual ICollection<SelectedCoverage> SelectedCoverages { get; set; }
in Traveler
class had no impact.
The exception:
Further info 2:
There is just 1 entity that causes the exception:
public class Quote : ISaleEntity, ICloneable {
...
[DataMember]
public virtual Travel Travel { get; set; }
...
When i change the above [DataMember]
to [IgnoreDataMember]
- no exception.
I set all the properties of this class to [IgnoreDataMember]
[DataContract]
public class Travel : InsuredObject, ISaleEntity, ICloneable
{
[IgnoreDataMember]
//[DataMember]
public bool? IsFromIsrael { get; set; }
[IgnoreDataMember]
//[DataMember]
public virtual ICollection<Traveler> Travelers { get; set; }
[IgnoreDataMember]
public virtual Quote Quote { get; set; }
[IgnoreDataMember]
//[DataMember]
[NotMapped]
public List<int> DestinationsCodes
{
get
{
if (string.IsNullOrWhiteSpace(DBDestinationsCodes))
return new List<int>();
return DBDestinationsCodes.Split(',').Select(c => int.Parse(c)).ToList();
}
set { DBDestinationsCodes = string.Join(",", value); }
}
/// <summary>
/// NOTE! Do not use this property directly! use DestinationsCodes instead
/// </summary>
[IgnoreDataMember]
//[DataMember]
public string DBDestinationsCodes { get; set; }
...
But the exception still occurs. Probably because of the class this class inherits from:
[DataContract]
[KnownType(typeof(Vehicle))]
[KnownType(typeof(Apartment))]
[KnownType(typeof(Travel))]
public class InsuredObject : ISaleEntity, ICloneable {
[Key]
[DataMember]
public int Id { get; set; }
[DataMember]
public int? OwnerTypeId { get; set; }
//navigation property
[DataMember]
public bool? HasShiabud { get; set; }
[DataMember]
[IgnoreDataMember]
public virtual Shiabud Shiabud { get; set; }
//[NotMapped]
//public virtual Proposal Proposal { get; set; }
//[DataMember]
[IgnoreDataMember]
public virtual ICollection<Coverage> Coverages { get; set; }
...
So how can I send this entity via WCF?
By making your properties Virtual, what you are doing is lazy loading.
You can make properties into ICollection instead, and do eager loading instead.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With