Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Trying to update an entity using EF and send it using WCF - property is causing an exception in an update scenario

I'm trying to send an object using WCF. The object is retrieved from the DB using EF.

This is the exception I get:

enter image description here

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:

enter image description here

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:

enter image description here

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?

like image 898
Oren A Avatar asked Feb 24 '16 15:02

Oren A


1 Answers

By making your properties Virtual, what you are doing is lazy loading.

You can make properties into ICollection instead, and do eager loading instead.

like image 172
Nonik Avatar answered Nov 03 '22 01:11

Nonik