Duplicating C# ASP.net object recursively with child & parent nodes - c#

I am trying to create an endpoint to take the ID of the "manualMaster" and duplicate it & all of its children which reside inside of a tree like structure, each node has a parent which are all tied to the main parent being the ManualMaster.
Below are the models used
public class ManualStep
{
public int Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string Directions { get; set; }
public int MasterId { get; set; }
public ManualMaster Master { get; set; }
public int Index { get; set; } // This property governs where inside of the manual master this step belongs
public virtual ICollection<Image> Images { get; set; } // Virtual because I think I'd always like to have the attached image objects.
public DateTime Created { get; set; }
public DateTime LastModified { get; set; }
//Begin tree storage scheme
public int? ParentId { get; set; }
public ManualStep Parent { get; set; }
public ICollection<ManualStep> Nodes { get; set; }
}
public class ManualMaster
{
public int Id { get; set; }
public string Name { get; set; }
public int BrandId { get; set; }
public Brand Brand { get; set; }
public string Comment { get; set; }
public ICollection<ManualStep> Steps { get; set; }
public DateTime Created { get; set; }
public DateTime LastModified { get; set; }
}
And then the endpoint which takes the ID and attempts to duplicate.
// GET/DUPLICATE: api/Manual/5
[Route("{id}")]
[HttpGet]
[ResponseType(typeof(ManualMaster))]
public async Task<IHttpActionResult> DuplicateManualMaster(int id)
{
ManualMaster manualMaster = await db.ManualMasters
.Include(i => i.Steps)
.FirstOrDefaultAsync(i => i.Id == id);
//
if (manualMaster == null)
{
return NotFound();
}
var dup = new ManualMaster
{
Brand = db.Brands.FirstOrDefault(i => i.Id == manualMaster.BrandId),
BrandId = manualMaster.BrandId,
//manualMaster.Id,
Name = manualMaster.Name,
Comment = manualMaster.Comment
};
foreach (var step in manualMaster.Steps)
{
var newStep = step;
newStep.Parent.Id = dup.Id;
db.ManualSteps.Add(newStep);
await db.SaveChangesAsync();
}
db.ManualMasters.Add(dup);
await db.SaveChangesAsync();
return Ok(dup);
}
My problem is that currently when duplicating, I am setting the id of every node to the main parent ID and not to their respective ID's which create the tree structure.
newStep.Parent.Id = dup.Id;
So instead of copying the entire tree all of my nodes have the same ID... Instead I need to copy the tree structure how it currently is...

Related

How to get all the relation with filter

I have This object
public class ParentGroup
{
public int Id { get; set; }
public ParentGroup Parent { get; set; }
public int? ParentId { get; set; }
public ICollection<ParentGroup> Children { get; set; }
public ParentGroupType ParentGroupType { get; set; }
public int? ParentGroupTypeId { get; set; }
public bool Active { get; set; }
public string Code { get; set; }
public string Name { get; set; }
}
I'm trying to get all the parent with all the children but there is a something that happened with me and I can't understand if I put the where filter in the query I can't get all the data related to the children I got only the first level
If I removed it I get all the date with all the levels and I don't want to make the query to the DB, not my memory
This my code
public async Task<List<ParentGroup>> GetOrganizationStructureTree()
{
// var query = _context.ParentGroups.Where(x => x.ParentId == null);
List<ParentGroup> ParentGroups = await _context.ParentGroups.Include(par => par.Children).Include(par => par.Parent).Include(pr => pr.ParentGroupType).ToListAsync();
return ParentGroups;
}

MVC - Unable to set field/property on entity

I have 2 tables in my entity framework:
INATIVOS (Employees)
EMPRESAS (Companies)
When registering an employee I select a company in a #Html.DropDownListFor (List).
The registration is ok, the company is saved correctly. However, when trying to edit a registered employee shows the error "Unable to set field/property on entity" in the Companies list.
INATIVO.cs
public partial class INATIVOS
{
public decimal ID { get; set; }
public string COD_EMPRESA { get; set; }
public string CHAPA { get; set; }
public string NOME { get; set; }
public System.DateTime DATA_NASC { get; set; }
public string PLANO { get; set; }
public short LEI { get; set; }
public short APOSENTADO { get; set; }
public short ESTADO_VIDA { get; set; }
public short ISENTO { get; set; }
public Nullable<System.DateTime> INICIO_VIGENCIA { get; set; }
public Nullable<System.DateTime> FIM_VIGENCIA { get; set; }
public string CPF { get; set; }
public string EMAIL { get; set; }
public string ENDERECO { get; set; }
public string NUMERO { get; set; }
public string COMPLEMENTO { get; set; }
public string BAIRRO { get; set; }
public string CIDADE { get; set; }
public string ESTADO { get; set; }
public string CEP { get; set; }
public string TELEFONE { get; set; }
public string CELULAR { get; set; }
public string OBSERVACAO { get; set; }
public List<DEPENDENTES> DEPENDENTES { get; set; }
public List<EMPRESAS> EMPRESAS { get; set; }
public List<PLANOS_MEDICO> PLANOS_MEDICO { get; set; }
}
InativoController.cs
public ActionResult Index(int? id)
{
INATIVOS inaModel = new INATIVOS();
using (Entidades db = new Entidades())
{
if (id != null)
{
inaModel = db.INATIVOS.Where(x => x.ID == id).FirstOrDefault();
}
inaModel.EMPRESAS = db.EMPRESAS.ToList<EMPRESAS>();
inaModel.PLANOS_MEDICO = db.PLANOS_MEDICO.ToList<PLANOS_MEDICO>();
}
return View(inaModel);
}
If these are navigation properties:
public List<DEPENDENTES> DEPENDENTES { get; set; }
public List<EMPRESAS> EMPRESAS { get; set; }
public List<PLANOS_MEDICO> PLANOS_MEDICO { get; set; }
Then (1) they need to be virtual and (2) they need to be something like IList or ICollection:
public virtual ICollection<DEPENDENTES> DEPENDENTES { get; set; }
public virtual ICollection<EMPRESAS> EMPRESAS { get; set; }
public virtual ICollection<PLANOS_MEDICO> PLANOS_MEDICO { get; set; }
Though, as an aside, what you're doing here is very strange:
inaModel.EMPRESAS = db.EMPRESAS.ToList<EMPRESAS>();
inaModel.PLANOS_MEDICO = db.PLANOS_MEDICO.ToList<PLANOS_MEDICO>();
Essentially what you have in the database is, for a given Employee (INATIVOS) there are relationships to specific Companies (EMPRESAS) and specific Medical Plans (PLANOS_MEDICO). But you're ignoring whatever is in that data and replacing it with all companies and all medical plans in the entire database.
So every time you use this controller action to fetch an existing employee record, it's going to look like that employee has every company and every medical plan. Even though that's not what's in the database. I strongly suspect that's not what you want.
UPDATE: Based on comments on this answer, it sounds like those aren't navigation properties. They're not even properties of the model at all. They're just lists of data needed for the view to populate (presumably) <select> elements.
If they're not part of the data model then remove them from the model. Instead, consider using a view model. For example:
public class InativosViewModel
{
public INATIVOS Inativos { get; set; }
public List<EMPRESAS> EMPRESAS { get; set; }
public List<PLANOS_MEDICO> PLANOS_MEDICO { get; set; }
}
Then in the controller return an instance of the view model, which is a composite object of the model and the data needed for the view:
public ActionResult Index(int? id)
{
InativosViewModel result = new InativosViewModel();
using (Entidades db = new Entidades())
{
if (id != null)
{
result.Inativos = db.INATIVOS.Where(x => x.ID == id).FirstOrDefault();
}
result.EMPRESAS = db.EMPRESAS.ToList<EMPRESAS>();
result.PLANOS_MEDICO = db.PLANOS_MEDICO.ToList<PLANOS_MEDICO>();
}
return View(result);
}
And of course change the model binding in the view itself to now expect and use an instance of InativosViewModel. The resulting POST action can still accept an instance of INATIVOS if it needs to, or it can accept an instance of InativosViewModel just as well. That all depends on what the form structure is and what's being posted to that action.
Alternatively, if you want to keep using the INATIVOS model then still remove those lists from it but use something like ViewBag to send them to the view. Something like this:
public ActionResult Index(int? id)
{
INATIVOS inaModel = new INATIVOS();
using (Entidades db = new Entidades())
{
if (id != null)
{
inaModel = db.INATIVOS.Where(x => x.ID == id).FirstOrDefault();
}
ViewBag.Empresas = db.EMPRESAS.ToList<EMPRESAS>();
ViewBag.PlanosMedico = db.PLANOS_MEDICO.ToList<PLANOS_MEDICO>();
}
return View(inaModel);
}
Then in your view you would populate the <select> elements from there:
#Html.DropDownListFor(
model => Model.COD_EMPRESA,
new SelectList(ViewBag.Empresas, "CODIGO", "DESCRICAO"),
htmlAttributes: new { #class = "form-control"
})

Using linq or plain c# to populate a given object structure

I am trying to populate the AllTerms object that will contain LegalFundClassCommercialViewModel and LegalFundClassSideLetterViewModel objects.
Basically the LEGAL_FUND_CLASS table has parent and child records and are related by column LegalParentClassId. One parent has one child. So I need to loop through legalfundClasses object and populate
IEnumerable<LegalFundClassWrapper> AllTerms . The LegalFundClassDetailsViewModel represents the records in the LEGAL_FUND_CLASS table. So legalfundClasses variable contains records from this legal_fund_class table. There are several records. Some records wont have child record. I need to populate in a way where the parent record gets added to LegalFundClassCommercialViewModel and its child record gets added to
LegalFundClassSideLetterViewModel. The wrapper would contain a collection of Parent and child records where some child records wont exist and hence the LegalFundClassSideLetterViewModel property would be null.
Can somebody give me an idea on how to go about it ?
C#
public class LegalFundClassViewModel
{
public IEnumerable<LegalFundClassWrapper> AllTerms;
public class LegalFundClassWrapper
{
public LegalFundClassDetailsViewModel LegalFundClassCommercialViewModel { get; set; }
public LegalFundClassDetailsViewModel LegalFundClassSideLetterViewModel { get; set; }
}
}
public class LegalFundClassDetailsViewModel
{
public string Description { get; set; }
public string AuditSummary { get; set; }
public string FeesReviewSummary { get; set; }
public string TermsReviewSummary { get; set; }
public int Id { get; set; }
public int FundId { get; set; }
public int FundClassType { get; set; }
public int? CurrencyId { get; set; }
public string PrimaryCurrencyName { get; set; }
public string OtherCurrencyName { get; set; }
public int? ManagerStrategyId { get; set; }
public string ManagerStrategyName { get; set; }
public int? SubVotingId { get; set; }
public string SubVotingName { get; set; }
public int? SubHotIssueId { get; set; }
public string SubHotIssueName { get; set; }
public int? RedsFrqncyId { get; set; }
public string RedsFrqncyName { get; set; }
public int? RedsNoticeDays { get; set; }
public int? NoticeTypeOfDaysId { get; set; }
public string NoticeTypeOfDaysName { get; set; }
public int? LegalParentClassId { get; set; }
}
var managerStrategyFundIds = GetService<MANAGERSTRATEGY>().WhereWithIncludes<MANAGERSTRATEGY>(x => x.ID == managerStratedyId, x => x.FUNDs).SelectMany(x => x.FUNDs).Select(x => x.ID).ToList();
var legalfundClasses = GetService<LEGAL_FUND_CLASS>().Where(x => managerStrategyFundIds.Contains(x.FUND_ID));
What I was trying creates a list of all the records in one. How do I loop through and populate the AllTerms
var allFunds = legalfundClasses.Select(fc => new LegalFundClassDetailsViewModel
{
Description = fc.DESCRIPTION,
Id = fc.ID,
FundId = fc.FUND_ID,
FundClassType = fc.CLASS_TYPE,
AuditSummary = getAuditSummary(managerStratedyId, fc.ID),
FeesReviewSummary = getFeesReviewSummary(fc),
TermsReviewSummary = getTermsReviewSummary(fc),
CurrencyId = fc.CURRENCY_ID,
});
public class LegalFundClassViewModel
{
public IEnumerable<LegalFundClassWrapper> AllTerms;
public class LegalFundClassWrapper
{
public LegalFundClassDetailsViewModel
LegalFundClassCommercialViewModel { get; set; }
public LegalFundClassDetailsViewModel
LegalFundClassSideLetterViewModel { get; set; }
}
As you can see in the image below, there are two records. The record that has value in legal_parent_class id field is the child to the record on top of it. If you notice the id of the top record matches the bottom record's legal_parent_class_id.
What is the best way to identify the child and store records in respective properties accordingly
You need left outer join. In your case
var allFunds = new[]
{
new LegalFundClassDetailsViewModel
{
Id = 101,
Description = "Parent with child"
},
new LegalFundClassDetailsViewModel
{
Id = 102,
Description = "Parent without child"
},
new LegalFundClassDetailsViewModel
{
Id = 103,
Description = "I'm child",
LegalParentClassId = 101
}
};
var allTerms = (from fund in allFunds
where fund.LegalParentClassId == null //only parents
join child in allFunds on fund.Id equals child.LegalParentClassId into gj
from child2 in gj.DefaultIfEmpty()
select new LegalFundClassViewModel.LegalFundClassWrapper { LegalFundClassCommercialViewModel = fund, LegalFundClassSideLetterViewModel = child2 })
.ToArray();

Dotnet Core Web API returning empty list | Lazy loading do not work

I have an endpoint in my API which returns all the data of a selected item. The item is a root object called Survey and it has a list of pages.
public partial class Surveys
{
public Surveys()
{
Pages = new HashSet<Pages>();
}
public string Description { get; set; }
public string Name { get; set; }
public long Syear { get; set; }
public long Quarter { get; set; }
public ICollection<Pages> Pages { get; set; }
}
Model class for the Pages look like this.
public partial class Pages
{
public Pages()
{
Elements = new HashSet<Elements>();
}
public string Id { get; set; }
public long Number { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public int? PageFlowId { get; set; }
public bool NamedPage { get; set; }
public bool? IsFirst { get; set; }
public bool? IsLast { get; set; }
public long? SurveyQuarter { get; set; }
public long? SurveySyear { get; set; }
public PagesFlows PageFlow { get; set; }
public Surveys Survey { get; set; }
public ICollection<Elements> Elements { get; set; }
}
But when I send the GET request is returning an empty list for Pages
[
{
"description": "Customer Satisfaction Survey",
"name": "Customer Survey",
"syear": 2019,
"quarter": 1,
"pages": []
}
]
The database contains data. primary key of the table is a composite key(Syear, Quarter). My API looks like this.
public async Task<IActionResult> GetSurveys([FromRoute]long syear, long quarter)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var surveys = await _context.Surveys.Include(s => s.Pages).SingleOrDefaultAsync(s => s.Syear == syear && s.Quarter == quarter);
if (surveys == null)
{
return NotFound();
}
return Ok(surveys);
}
I have been trying to figure this out for a week now. Any help will be highly appreciated, Thank you in advance.
I managed to get around the issue by doing the following.
Removed .Include(s => s.Pages) from here,
var surveys = await _context.Surveys.Include(s => s.Pages).SingleOrDefaultAsync(s => s.Syear == syear && s.Quarter == quarter);
I have added virtual keyword to all the referenced classes.
public partial class Pages
{
public Pages()
{
Elements = new HashSet<Elements>();
}
public string Id { get; set; }
public long Number { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public int? PageFlowId { get; set; }
public bool NamedPage { get; set; }
public bool? IsFirst { get; set; }
public bool? IsLast { get; set; }
public long? SurveyQuarter { get; set; }
public long? SurveySyear { get; set; }
public virtual PagesFlows PageFlow { get; set; }
public virtual Surveys Survey { get; set; }
public virtual ICollection<Elements> Elements { get; set; }
}
Then I have installed Microsoft.EntityFrameworkCore.Proxies package fron NuGet as described here
And enabled Lazy Loading Proxies in Sturtup.cs in ConfigureServices method.
services.AddDbContext<MyDBContext>
(options => options.UseLazyLoadingProxies().UseSqlServer(connection));

How to avoid to load collection property in Entity Framework

When I select artile, it select user, but user has a collection of article, so article select user again. May be recursive cause out of memory ,
The calling processing is :
article=>user=>article=>user...
ef entities is :
public partial class article
{
public int id { get; set; }
public string title { get; set; }
public string cont { get; set; }
public Nullable<int> uid { get; set; }
public System.DateTime addtime { get; set; }
public Nullable<int> colid { get; set; }
public virtual user user { get; set; }
public virtual column column { get; set; }
}
public partial class user
{
public user()
{
this.roleusers = new HashSet<roleuser>();
this.articles = new HashSet<article>();
}
public int id { get; set; }
public string email { get; set; }
public string uname { get; set; }
public string upass { get; set; }
public virtual ICollection<roleuser> roleusers { get; set; }
public virtual ICollection<article> articles { get; set; }
}
mysql EF operation class is :
public class ArtDao
{
readonly crmEntities _ent = new crmEntities();
public List<article> PageArts(int start, int limit, out int total)
{
var ll =
_ent.articles.OrderByDescending(o => o.id)
.Skip(start)
.Take(limit)
.ToList();
total = _ent.articles.Count();
return ll;
}
}
How to avoid to eager load the collection property roleusers and articles ?
You need to set LazyLoad on you edmx properties, and manually load only first level childs with Include() method when selecting:
public List<article> PageArts(int start, int limit, out int total)
{
var ll =
_ent.articles.OrderByDescending(o => o.id)
.Skip(start)
.Take(limit)
.Include(o => o.user)
.ToList();
total = _ent.articles.Count();
return ll;
}
You need to implement it in another class, wich can be a partial class.

Categories