使用RIA服务插入/更新组合属性

时间:2010-07-29 17:50:06

标签: silverlight entity-framework silverlight-4.0 mvvm-light wcf-ria-services

我正在使用Entity Framework,RIA Services和MVVM-Light工具包创建Silverlight 4应用程序。该应用程序处理复杂的对象图,其中包含以下结构:

  • 工作1-> *资源
  • 工作1-> *工作计划
  • 工作计划1-> * WorkplanItems
  • 资源1-> *作业
  • WorkplanItems 1-> *作业

我希望能够加载作业(使用Include属性/指令),这样可以正常工作。从特定作业的根目录加载完整图表。但是,当我提交更改时,我收到错误

Entity for operation '0' has multiple parents

我的理解是this post我的元数据上的Composition属性应该允许我将其作为完整的图表提交,然后通过一次往返来正确处理服务器上所有对象的更新来自我的silverlight应用程序。我的目标是不对每次更改提交更改,但允许用户对作业及其相关部分进行一系列更改,然后提交或取消更改。

如果您发现我在此忽略的任何问题,请告诉我们?这是我设置的元数据:

// 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; }
    }
}

1 个答案:

答案 0 :(得分:2)

我已经找到解决问题的方法。一旦你理解了一些内部工作和警告,这实际上非常强大:

  1. 仅将[Composition]和[Include]属性应用于对象图的根目录。
  2. 您的作品的根对象是提交的内容,也是您的更新过程开始的地方。
  3. 您的根对象的EntityState实际上可能并不代表更新(就像我的情况一样)。
  4. 首先重新附加图表,所有对象都被附加并且其状态设置为相同的值(插入,更新,删除等)
  5. 重新附加图表后,您需要根据提交的ChangeSet更新每个实体。
  6. 可能还有更多我仍然不完全理解;但是,这在我的环境中有效。我想找出一种更好的方法,但是当我尝试应用一种常见的附加方法时,我似乎遇到了不同的问题。

    以下是我为更新方法提出的建议:

    public void UpdateJob(Job currentJob)
        {
            //this.ObjectContext.Jobs.AttachAsModified(currentJob, this.ChangeSet.GetOriginal(currentJob));
    
            // compositional update process
            foreach (Assignment a in this.ChangeSet.GetAssociatedChanges(currentJob, j => j.Assignments))
            {
                ChangeOperation op = this.ChangeSet.GetChangeOperation(a);
                switch (op)
                {
                    case ChangeOperation.Insert:
                        InsertAssignment(a);
                        break;
                    case ChangeOperation.Update:
                        UpdateAssignment(a);
                        break;
                    case ChangeOperation.Delete:
                        DeleteAssignment(a);
                        break;
                    case ChangeOperation.None:
                        if (a.EntityState == EntityState.Detached)
                            this.ObjectContext.Assignments.Attach(a);
                        System.Data.Objects.ObjectStateEntry ose;
                        if (this.ObjectContext.ObjectStateManager.TryGetObjectStateEntry(a.EntityKey, out ose))
                            this.ObjectContext.ObjectStateManager.ChangeObjectState(a, EntityState.Unchanged);
                        break;
                }
            }
    
            foreach (WorkplanItem wpi in this.ChangeSet.GetAssociatedChanges(currentJob, j => j.WorkplanItems))
            {
                ChangeOperation op = this.ChangeSet.GetChangeOperation(wpi);
                switch (op)
                {
                    case ChangeOperation.Insert:
                        InsertWorkplanItem(wpi);
                        break;
                    case ChangeOperation.Update:
                        UpdateWorkplanItem(wpi);
                        break;
                    case ChangeOperation.Delete:
                        DeleteWorkplanItem(wpi);
                        break;
                    case ChangeOperation.None:
                        if (wpi.EntityState == EntityState.Detached)
                            this.ObjectContext.WorkplanItems.Attach(wpi);
                        System.Data.Objects.ObjectStateEntry ose;
                        if (this.ObjectContext.ObjectStateManager.TryGetObjectStateEntry(wpi.EntityKey, out ose))
                            this.ObjectContext.ObjectStateManager.ChangeObjectState(wpi, EntityState.Unchanged);
                        break;
                }
            }
    
            foreach (Workplan wp in this.ChangeSet.GetAssociatedChanges(currentJob, j => j.Workplans))
            {
                ChangeOperation op = this.ChangeSet.GetChangeOperation(wp);
                switch (op)
                {
                    case ChangeOperation.Insert:
                        InsertWorkplan(wp);
                        break;
                    case ChangeOperation.Update:
                        UpdateWorkplan(wp);
                        break;
                    case ChangeOperation.Delete:
                        DeleteWorkplan(wp);
                        break;
                    case ChangeOperation.None:
                        if (wp.EntityState == EntityState.Detached)
                            this.ObjectContext.Workplans.Attach(wp);
                        System.Data.Objects.ObjectStateEntry ose;
                        if (this.ObjectContext.ObjectStateManager.TryGetObjectStateEntry(wp.EntityKey, out ose))
                            this.ObjectContext.ObjectStateManager.ChangeObjectState(wp, EntityState.Unchanged);
                        break;
                }
            }
    
            foreach (Resource res in this.ChangeSet.GetAssociatedChanges(currentJob, j => j.Resources))
            {
                ChangeOperation op = this.ChangeSet.GetChangeOperation(res);
                switch (op)
                {
                    case ChangeOperation.Insert:
                        InsertResource(res);
                        break;
                    case ChangeOperation.Update:
                        UpdateResource(res);
                        break;
                    case ChangeOperation.Delete:
                        DeleteResource(res);
                        break;
                    case ChangeOperation.None:
                        if (res.EntityState == EntityState.Detached)
                            this.ObjectContext.Resources.Attach(res);
                        System.Data.Objects.ObjectStateEntry ose;
                        if (this.ObjectContext.ObjectStateManager.TryGetObjectStateEntry(res.EntityKey, out ose))
                            this.ObjectContext.ObjectStateManager.ChangeObjectState(res, EntityState.Unchanged);
                        break;
                }
            }
    
            ChangeOperation detailop = this.ChangeSet.GetChangeOperation(currentJob.JobDetail);
            switch (detailop)
            {
                case ChangeOperation.Insert:
                    InsertJobDetail(currentJob.JobDetail);
                    break;
                case ChangeOperation.Update:
                    UpdateJobDetail(currentJob.JobDetail);
                    break;
                case ChangeOperation.Delete:
                    DeleteJobDetail(currentJob.JobDetail);
                    break;
                case ChangeOperation.None:
                    System.Data.Objects.ObjectStateEntry ose;
                    if (this.ObjectContext.ObjectStateManager.TryGetObjectStateEntry(currentJob.JobDetail.EntityKey, out ose))
                            this.ObjectContext.ObjectStateManager.ChangeObjectState(currentJob.JobDetail, EntityState.Unchanged);
                    break;
            }
    
            if (currentJob.EntityState == EntityState.Detached)
                this.ObjectContext.Jobs.Attach(currentJob);
    
            ChangeOperation jobop = this.ChangeSet.GetChangeOperation(currentJob);
            switch (jobop)
            {
                case ChangeOperation.Insert:
                    InsertJob(currentJob);
                    break;
                case ChangeOperation.Update:
                    // Since this is the compositional root, we need to make sure there really is a change
                    var origJob = this.ChangeSet.GetOriginal(currentJob);
                    if (origJob != null)
                    {
                        this.ObjectContext.Jobs.AttachAsModified(currentJob,
                            origJob);
                    }
                    else
                    {
                        this.ObjectContext.ObjectStateManager.ChangeObjectState(
                            currentJob, EntityState.Unchanged);
                    }
                    break;
                case ChangeOperation.Delete:
                    DeleteJob(currentJob);
                    break;
                case ChangeOperation.None:
                    this.ObjectContext.ObjectStateManager.ChangeObjectState(currentJob, EntityState.Unchanged);
                    break;
            }
    
        }
    

    此外,以下是我必须对我的实体的元数据类进行的更改。

    // 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]
            [Composition]
            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()
            {
            }
    
            public EntityCollection<Assignment> Assignments { get; set; }
    
            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()
            {
            }
    
            public EntityCollection<Assignment> Assignments { get; set; }
    
            public string Description { get; set; }
    
            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; }
    
            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()
            {
            }
    
            public EntityCollection<Assignment> Assignments { get; set; }
    
            public string Description { get; set; }
    
            public int ItemID { get; set; }
    
            public Job Job { get; set; }
    
            public int JobID { get; set; }
    
            public string Notes { get; set; }
    
            public short Ordinal { get; set; }
    
            public Workplan Workplan { get; set; }
    
            public int WorkplanID { get; set; }
        }
    }
    

    如果有人有其他提示/想法,请添加它们。随着我的了解,我会继续发帖。