I am creating a Silverlight 4 application with Entity Framework, RIA Services and MVVM-Light toolkit. The application deals with a complex object graph, that contains the following structure:
- Job 1->* Resources
- Job 1->* Workplans
- Workplan 1->* WorkplanItems
- Resource 1->* Assignment
- WorkplanItems 1->* Assignment
I would like to be able to load the job (using Include attributes/directive) and this works fine. to load the complete graph from the root of a specific job. However, when I submit changes back, I get an error
Entity for operation '0' has multiple parents
It has been my understanding from this post that the Composition attribute on my metadata is what should allow me to submit this as a complete graph and then handle the updating of all objects on the server properly with a single round trip from my silverlight application. My goal is to not submit changes with each change, but to allow the user to make a bunch of changes to the Job, and it's associated parts, then submit them, or cancel the changes.
Please let me know if you are aware of any issues that I have overlooked in this? Here is the metadata as I have it setup:
// The MetadataTypeAttribute identifies AssignmentMetadata as the class
// that carries additional metadata for the Assignment class.
[MetadataTypeAttribute(typeof(Assignment.AssignmentMetadata))]
public partial class Assignment
{
// This class allows you to attach custom attributes to properties
// of the Assignment class.
//
// For example, the following marks the Xyz property as a
// required property and specifies the format for valid values:
// [Required]
// [RegularExpression("[A-Z][A-Za-z0-9]*")]
// [StringLength(32)]
// public string Xyz { get; set; }
internal sealed class AssignmentMetadata
{
// Metadata classes are not meant to be instantiated.
private AssignmentMetadata()
{
}
public decimal CostBudgeted { get; set; }
public decimal CostRemaining { get; set; }
public decimal HoursBudgeted { get; set; }
public decimal HoursRemaining { get; set; }
public bool IsComplete { get; set; }
public int ItemID { get; set; }
public Job Job { get; set; }
public int JobID { get; set; }
public Resource Resource { get; set; }
public int ResourceID { get; set; }
public Workplan Workplan { get; set; }
public int WorkplanID { get; set; }
public WorkplanItem WorkplanItem { get; set; }
}
}
// The MetadataTypeAttribute identifies JobMetadata as the class
// that carries additional metadata for the Job class.
[MetadataTypeAttribute(typeof(Job.JobMetadata))]
public partial class Job
{
// This class allows you to attach custom attributes to properties
// of the Job class.
//
// For example, the following marks the Xyz property as a
// required property and specifies the format for valid values:
// [Required]
// [RegularExpression("[A-Z][A-Za-z0-9]*")]
// [StringLength(32)]
// public string Xyz { get; set; }
internal sealed class JobMetadata
{
// Metadata classes are not meant to be instantiated.
private JobMetadata()
{
}
[Display(AutoGenerateField = false)]
[Include]
[Composition]
public EntityCollection<Assignment> Assignments { get; set; }
[Display(Name="Client Job", Order=2, Description="Is this a client job?")]
[DefaultValue(true)]
public bool IsRealJob { get; set; }
[Display(AutoGenerateField = false)]
[Include]
public JobDetail JobDetail { get; set; }
[Display(AutoGenerateField = false)]
public int JoblID { get; set; }
[Display(Name="Job Title", Order=1, Description="What should this job be called?")]
public string Title { get; set; }
[Display(AutoGenerateField = false)]
[Include]
[Composition]
public EntityCollection<WorkplanItem> WorkplanItems { get; set; }
[Display(AutoGenerateField = false)]
[Include]
[Composition]
public EntityCollection<Workplan> Workplans { get; set; }
[Display(AutoGenerateField = false)]
[Include]
[Composition]
public EntityCollection<Resource> Resources { get; set; }
}
}
// The MetadataTypeAttribute identifies JobDetailMetadata as the class
// that carries additional metadata for the JobDetail class.
[MetadataTypeAttribute(typeof(JobDetail.JobDetailMetadata))]
public partial class JobDetail
{
// This class allows you to attach custom attributes to properties
// of the JobDetail class.
//
// For example, the following marks the Xyz property as a
// required property and specifies the format for valid values:
// [Required]
// [RegularExpression("[A-Z][A-Za-z0-9]*")]
// [StringLength(32)]
// public string Xyz { get; set; }
internal sealed class JobDetailMetadata
{
// Metadata classes are not meant to be instantiated.
private JobDetailMetadata()
{
}
[Display(Name="Client", Order=1,Description="Name of the Client")]
public string Client { get; set; }
[Display(Name = "Client Fee", Order = 5, Description = "Client Fee from Engagement Letter")]
[DisplayFormat(DataFormatString="C",NullDisplayText="<Not Set>",ApplyFormatInEditMode=true)]
public Nullable<decimal> ClientFee { get; set; }
[Display(AutoGenerateField=false)]
public int ClientIndex { get; set; }
[Display(AutoGenerateField = true)]
public string EFOLDERID { get; set; }
[Display(Name = "Engagement Name", Order = 4, Description = "Friendly name of the Engagement")]
public string Engagement { get; set; }
[Display(Name = "Eng Type", Order = 3, Description = "Type of Work being done")]
public string EngagementType { get; set; }
[Display(AutoGenerateField = false)]
public Job Job { get; set; }
[Display(AutoGenerateField = false)]
public int JobID { get; set; }
[Display(AutoGenerateField = false)]
public int PEJobID { get; set; }
[Display(Name = "Service", Order = 2, Description = "Service Type")]
public string Service { get; set; }
[Display(Name = "Timing of the Work", Order = 6, Description = "When will this work occur?")]
public string Timing { get; set; }
}
}
// The MetadataTypeAttribute identifies PendingTimesheetMetadata as the class
// that carries additional metadata for the PendingTimesheet class.
[MetadataTypeAttribute(typeof(PendingTimesheet.PendingTimesheetMetadata))]
public partial class PendingTimesheet
{
// This class allows you to attach custom attributes to properties
// of the PendingTimesheet class.
//
// For example, the following marks the Xyz property as a
// required property and specifies the format for valid values:
// [Required]
// [RegularExpression("[A-Z][A-Za-z0-9]*")]
// [StringLength(32)]
// public string Xyz { get; set; }
internal sealed class PendingTimesheetMetadata
{
// Metadata classes are not meant to be instantiated.
private PendingTimesheetMetadata()
{
}
public decimal PendingHours { get; set; }
public string UserName { get; set; }
public DateTime WorkDate { get; set; }
[Include]
public Workplan Workplan { get; set; }
public int WorkplanID { get; set; }
}
}
// The MetadataTypeAttribute identifies ResourceMetadata as the class
// that carries additional metadata for the Resource class.
[MetadataTypeAttribute(typeof(Resource.ResourceMetadata))]
public partial class Resource
{
// This class allows you to attach custom attributes to properties
// of the Resource class.
//
// For example, the following marks the Xyz property as a
// required property and specifies the format for valid values:
// [Required]
// [RegularExpression("[A-Z][A-Za-z0-9]*")]
// [StringLength(32)]
// public string Xyz { get; set; }
internal sealed class ResourceMetadata
{
// Metadata classes are not meant to be instantiated.
private ResourceMetadata()
{
}
[Include]
[Composition]
public EntityCollection<Assignment> Assignments { get; set; }
[Include]
public Job Job { get; set; }
public int JobID { get; set; }
public decimal Rate { get; set; }
public int ResourceID { get; set; }
public string Title { get; set; }
public string UserName { get; set; }
}
}
// The MetadataTypeAttribute identifies WorkplanMetadata as the class
// that carries additional metadata for the Workplan class.
[MetadataTypeAttribute(typeof(Workplan.WorkplanMetadata))]
public partial class Workplan
{
// This class allows you to attach custom attributes to properties
// of the Workplan class.
//
// For example, the following marks the Xyz property as a
// required property and specifies the format for valid values:
// [Required]
// [RegularExpression("[A-Z][A-Za-z0-9]*")]
// [StringLength(32)]
// public string Xyz { get; set; }
internal sealed class WorkplanMetadata
{
// Metadata classes are not meant to be instantiated.
private WorkplanMetadata()
{
}
[Include]
[Composition]
public EntityCollection<Assignment> Assignments { get; set; }
public string Description { get; set; }
[Include]
public Job Job { get; set; }
public int JobID { get; set; }
public EntityCollection<PendingTimesheet> PendingTimesheets { get; set; }
public Nullable<int> PETaskID { get; set; }
public decimal TtlCost { get; set; }
public decimal TtlHours { get; set; }
public DateTime WorkEnd { get; set; }
public int WorkplanID { get; set; }
[Include]
[Composition]
public EntityCollection<WorkplanItem> WorkplanItems { get; set; }
public DateTime WorkStart { get; set; }
}
}
// The MetadataTypeAttribute identifies WorkplanItemMetadata as the class
// that carries additional metadata for the WorkplanItem class.
[MetadataTypeAttribute(typeof(WorkplanItem.WorkplanItemMetadata))]
public partial class WorkplanItem
{
// This class allows you to attach custom attributes to properties
// of the WorkplanItem class.
//
// For example, the following marks the Xyz property as a
// required property and specifies the format for valid values:
// [Required]
// [RegularExpression("[A-Z][A-Za-z0-9]*")]
// [StringLength(32)]
// public string Xyz { get; set; }
internal sealed class WorkplanItemMetadata
{
// Metadata classes are not meant to be instantiated.
private WorkplanItemMetadata()
{
}
[Include]
[Composition]
public EntityCollection<Assignment> Assignments { get; set; }
public string Description { get; set; }
public int ItemID { get; set; }
[Include]
public Job Job { get; set; }
public int JobID { get; set; }
public string Notes { get; set; }
public short Ordinal { get; set; }
[Include]
public Workplan Workplan { get; set; }
public int WorkplanID { get; set; }
}
}