简体   繁体   中英

Cascade Add/Update tree (Parent/Child) data

I have some problem. This is database structure:

CREATE TABLE [EvaluationProcess].[CriteriaHeader](
    [CriteriaHeader_No] [uniqueidentifier] NOT NULL,
    [CriteriaHeader_Type] [int] NOT NULL,
    [CriteriaHeader_Name] [nvarchar](128) NOT NULL,
    [CriteriaHeader_Description] [nvarchar](256) NULL,
    [CriteriaHeader_MaxScore] [int] NOT NULL,
    [CriteriaHeader_MinScore] [int] NOT NULL,
    [CriteriaHeader_ScoreStep] [int] NOT NULL,
    [CriteriaHeader_IsCountedResult] [bit] NOT NULL,
    [CriteriaHeader_IsBlocked] [bit] NOT NULL,
    [OwnedOrganisationID] [uniqueidentifier] NULL,
    [ModifyUser] [uniqueidentifier] NULL,
    [ModifyDate] [datetime] NULL,
    [TimeStamp] [timestamp] NULL,
 CONSTRAINT [PK_Criteria] PRIMARY KEY CLUSTERED 
(
    [CriteriaHeader_No] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

GO

ALTER TABLE [EvaluationProcess].[CriteriaHeader] ADD  CONSTRAINT [DF_Criteria_Criteria_No]  DEFAULT (newsequentialid()) FOR [CriteriaHeader_No]
GO

ALTER TABLE [EvaluationProcess].[CriteriaHeader] ADD  CONSTRAINT [DF_Criteria_Criteria_Type]  DEFAULT ((0)) FOR [CriteriaHeader_Type]
GO

ALTER TABLE [EvaluationProcess].[CriteriaHeader] ADD  CONSTRAINT [DF_Criteria_Criteria_MaxScore_1]  DEFAULT ((5)) FOR [CriteriaHeader_MaxScore]
GO

ALTER TABLE [EvaluationProcess].[CriteriaHeader] ADD  CONSTRAINT [DF_Criteria_Criteria_MinScore_1]  DEFAULT ((0)) FOR [CriteriaHeader_MinScore]
GO

ALTER TABLE [EvaluationProcess].[CriteriaHeader] ADD  CONSTRAINT [DF_Criteria_Criteria_ScoreStep_1]  DEFAULT ((1)) FOR [CriteriaHeader_ScoreStep]
GO

ALTER TABLE [EvaluationProcess].[CriteriaHeader] ADD  CONSTRAINT [DF_Criteria_Criteria_IsCountedResult]  DEFAULT ((0)) FOR [CriteriaHeader_IsCountedResult]
GO

ALTER TABLE [EvaluationProcess].[CriteriaHeader] ADD  CONSTRAINT [DF_Criteria_Criteria_IsBlocked]  DEFAULT ((0)) FOR [CriteriaHeader_IsBlocked]
GO

ALTER TABLE [EvaluationProcess].[CriteriaHeader] ADD  CONSTRAINT [DF_Criteria_ModifyDate]  DEFAULT (getdate()) FOR [ModifyDate]
GO

CREATE TABLE [EvaluationProcess].[CriteriaLine](
    [CriteriaLine_No] [uniqueidentifier] NOT NULL,
    [CriteriaLine_ParentID] [uniqueidentifier] NULL,
    [CriteriaLine_CriteriaHeaderID] [uniqueidentifier] NULL,
    [CriteriaLine_Text] [nvarchar](128) NOT NULL,
    [CriteriaLine_Description] [nvarchar](512) NULL,
    [OwnedOrganisationID] [uniqueidentifier] NULL,
    [ModifyUser] [uniqueidentifier] NULL,
    [ModifyDate] [datetime] NULL,
    [TimeStamp] [timestamp] NULL,
 CONSTRAINT [PK_CriteriaList] PRIMARY KEY CLUSTERED 
(
    [CriteriaLine_No] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

GO

ALTER TABLE [EvaluationProcess].[CriteriaLine] ADD  CONSTRAINT [DF_CriteriaList_CriteriaList_No]  DEFAULT (newsequentialid()) FOR [CriteriaLine_No]
GO

ALTER TABLE [EvaluationProcess].[CriteriaLine] ADD  CONSTRAINT [DF_CriteriaList_ModifyDate]  DEFAULT (getdate()) FOR [ModifyDate]
GO

ALTER TABLE [EvaluationProcess].[CriteriaLine]  WITH CHECK ADD  CONSTRAINT [FK_CriteriaLine_CriteriaLine] FOREIGN KEY([CriteriaLine_CriteriaHeaderID])
REFERENCES [EvaluationProcess].[CriteriaHeader] ([CriteriaHeader_No])
ON UPDATE CASCADE
ON DELETE CASCADE
GO

ALTER TABLE [EvaluationProcess].[CriteriaLine] CHECK CONSTRAINT [FK_CriteriaLine_CriteriaLine]
GO

ALTER TABLE [EvaluationProcess].[CriteriaLine]  WITH CHECK ADD  CONSTRAINT [FK_CriteriaLine_CriteriaLine1] FOREIGN KEY([CriteriaLine_ParentID])
REFERENCES [EvaluationProcess].[CriteriaLine] ([CriteriaLine_No])
GO

ALTER TABLE [EvaluationProcess].[CriteriaLine] CHECK CONSTRAINT [FK_CriteriaLine_CriteriaLine1]
GO

I using EntityFramework 5. this is a Add/Update function

public override bool InsertOrUpdate(object[] _BaseEntityClasses, string _RoleName,
                                    System.Nullable<Guid> _LoginUserID,
                                      System.Nullable<System.Guid> _OrganisationID, bool? IsModified = null)
{
    using (Evaluation_SystemConection EvaluationDBContext = new Evaluation_SystemConection())
    {
        try
        {
            EvaluationDBContext.Configuration.AutoDetectChangesEnabled = false;

            IEnumerable<CriteriaHeader> _BaseEntityClassesList = _BaseEntityClasses.Cast<CriteriaHeader>();

            foreach (var OneItem in _BaseEntityClassesList)
            {
                if (OneItem.CriteriaLines != null)
                {
                    foreach (var OneLine in OneItem.CriteriaLines)
                    {
                        if (OneLine.CriteriaLine_No == Guid.Empty)
                            OneLine.CriteriaLine_CriteriaHeaderID = OneItem.CriteriaHeader_No;

                        EvaluationDBContext.CriteriaLines.Attach(OneLine);


                        EvaluationDBContext.Entry(OneLine).State = OneLine.CriteriaLine_No == Guid.Empty ? EntityState.Added : EntityState.Modified;

                    }

                }

                EvaluationDBContext.CriteriaHeaders.Add(OneItem);

                EvaluationDBContext.Entry(OneItem).State = OneItem.CriteriaHeader_No == Guid.Empty ? EntityState.Added : EntityState.Modified;

                if (EvaluationDBContext.SaveChanges() <= 0)
                    return false;
            }

            return true;
        }
        catch (Exception Ex)
        {
            ErrorMessage.Add("");

            ErrorMessage.Add((Ex.InnerException != null) ? Ex.InnerException.Message : Ex.Message);

            return false;
        }
        finally
        {
            EvaluationDBContext.Configuration.AutoDetectChangesEnabled = true;
        }
    }

}

And Generated POCO Classes

public partial class CriteriaHeader
{
    public CriteriaHeader()
    {
        this.CriteriaLines = new HashSet<CriteriaLine>();
    }

    public System.Guid CriteriaHeader_No { get; set; }
    public int CriteriaHeader_Type { get; set; }
    public string CriteriaHeader_Name { get; set; }
    public string CriteriaHeader_Description { get; set; }
    public int CriteriaHeader_MaxScore { get; set; }
    public int CriteriaHeader_MinScore { get; set; }
    public int CriteriaHeader_ScoreStep { get; set; }
    public bool CriteriaHeader_IsCountedResult { get; set; }
    public bool CriteriaHeader_IsBlocked { get; set; }
    public Nullable<System.Guid> OwnedOrganisationID { get; set; }
    public Nullable<System.Guid> ModifyUser { get; set; }
    public Nullable<System.DateTime> ModifyDate { get; set; }
    public byte[] TimeStamp { get; set; }

    public virtual ICollection<CriteriaLine> CriteriaLines { get; set; }
}

public partial class CriteriaLine
{
    public CriteriaLine()
    {
        this.CriteriaLine1 = new HashSet<CriteriaLine>();
    }

    public System.Guid CriteriaLine_No { get; set; }
    public Nullable<System.Guid> CriteriaLine_ParentID { get; set; }
    public Nullable<System.Guid> CriteriaLine_CriteriaHeaderID { get; set; }
    public string CriteriaLine_Text { get; set; }
    public string CriteriaLine_Description { get; set; }
    public Nullable<System.Guid> OwnedOrganisationID { get; set; }
    public Nullable<System.Guid> ModifyUser { get; set; }
    public Nullable<System.DateTime> ModifyDate { get; set; }
    public byte[] TimeStamp { get; set; }

    public virtual CriteriaHeader CriteriaHeader { get; set; }
    public virtual ICollection<CriteriaLine> CriteriaLine1 { get; set; }
    public virtual CriteriaLine CriteriaLine2 { get; set; }
}

Data did not insert or update more than 1 level in CriteriaList .

What did I do wrong?

First, you should always try to avoid changing AutoDetectChangesEnabled . You really have to thoroughly understand what it does (and does not) when you use this. So first thing to do is not to touch AutoDetectChangesEnabled . It looks like your method could work without it. If it doesn't, feel free to tell what happens so we can try to deal with it. (I suspect that some issues will arise that you tried to prevent by changing AutoDetectChangesEnabled ).

You could consider to use entity framework's own AddOrUpdate method . So where you have

EvaluationDBContext.CriteriaHeaders.Add(OneItem);
EvaluationDBContext.Entry(OneItem).State = OneItem.CriteriaHeader_No == Guid.Empty ? EntityState.Added : EntityState.Modified;

you could do

EvaluationDBContext.CriteriaHeaders.AddOrUpdate(OneItem);

(likewise with the CriteriaLines ).

But it takes a roundtrip to the database for each record to see whether it should be inserted or updated.

There are more issues in your code.

  • Naming conventions (like _BaseEntityClasses , the convention is baseEntityClasses .
  • You should not use object[] as an input parameter, but use generics: InsertOrUpdate<T>(IEnumerable<T> baseEntityClasses, ... .
  • You seem to have some external ErrorMessage list, which is a stateful way of programming. Ie your method has side effects. It may be hard to tell where ErrorMessage is affected and when it should be cleared or reported.
  • You swallow any exceptions. That's no real exception handling. Just re-throw them and handle them where appropriate.
  • Never just return a boolean. Especially when you return false it is meaningless. You could return the number of affected rows for instance. Together with rethrowing exceptions a consumer will receive far more information from your method than just a boolean.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM