*I have written a recursive query to get unlimited menu layer. The query works fine providing the exact results but it takes too much time to load. It takes probably 10 to 15 seconds. Please help me if I need to do anything to improve the performance. I have provided all the code to find out the problem. for mapping from entity to view model I have used automapper. *
Entity:
public class Menus
{
public int Id { get; set; }
public string Icon { get; set; }
public string Label { get; set; }
public string To { get; set; }
[ForeignKey("Parents")]
public int? ParentsId { get; set; }
public string Key { get; set; }
public bool? Permitted { get; set; }
public Menus Parents { get; set; }
public ICollection<Menus> Subs { get; set; }
public ICollection<MenusRole> MenusRoles { get; set; }
}
Query:
public async Task<IEnumerable<Menus>> GetAllMenusAsync()
{
List<Menus> temp = await ApplicationDbContext
.Menus
.Include(x => x.Subs)
.Where(x => x.Parents == null)
.Select(f => new Menus
{
Id = f.Id,
Key = f.Key,
Label = f.Label,
To = f.To,
Icon = f.Icon,
ParentsId = f.ParentsId,
Subs = f.Subs
}).ToListAsync();
return Get_all_menus(temp);
}
public List<Menus> Get_all_menus(List<Menus> menus)
{
int z = 0;
List<Menus> menuList = new List<Menus>();
if (menus.Count > 0)
{
menuList.AddRange(menus);
}
foreach (Menus item in menus)
{
Menus menu = ApplicationDbContext
.Menus
.Include(y => y.Subs)
.Where(y => y.Id == item.Id)
.Select(y => new Menus
{
Id = y.Id,
Key = y.Key,
Label = y.Label,
To = y.To,
Icon = y.Icon,
ParentsId = y.ParentsId,
Subs = y.Subs,
Permitted = true
}).First();
if (menu.Subs == null)
{
z++;
continue;
}
List<Menus> subMenu = menu.Subs.ToList();
menu.Subs = Get_all_menus(subMenu);
menuList[z] = menu;
z++;
}
return menuList;
}
In Controller
[HttpGet("get-all-menus")]
public async Task<ActionResult> GetAllMenus()
{
var menus = await _menusService.GetAllMenus();
var menusResources = _mapper.Map<IEnumerable<Menus>, IEnumerable<MenusResourceForSidebar>>(menus);
return Ok(menusResources);
}
View Model
public string Id { get; set; }
public string Icon { get; set; }
public string Label { get; set; }
public string To { get; set; }
public bool? Permitted { get; set; }
public ICollection<MenusResourceForSidebar> Subs { get; set; }
Instead of loading the root menus, then loading the children in separate queries, just load the whole collection in one query, and then populate the navigation links afterwards.
public async Task<IEnumerable<Menus>> GetAllMenusAsync()
{
List<Menus> temp = await ApplicationDbContext.Menus.ToList();
List<Menus> topLevel = new List<Menu>();
foreach (var menu in temp)
{
if (menu.ParentsId == null)
{
topLevel.Add(menu);
continue;
}
var parent = temp.Find(x => x.Id == temp.ParentsId.Value);
if (parent.Subs == null)
parent.Subs = new List<Menus>();
parent.Subs.Add(menu);
}
return topLevel;
}
You should just be able to do:
context.Menus.Include(m => m.Subs).ToList();
The relationship fixup in EFCore will link all the menus together in a tree for you. In later EFs you don't even need the Include..
context.Menus.ToList();
Here is a table in SSMS:
Here is the data:
Here it is chopped up in a paint program and rearranged into a tree:
Here's the scaffolded entity:
// <auto-generated> This file has been auto generated by EF Core Power Tools. </auto-generated>
#nullable disable
using System.Collections.Generic;
namespace ConsoleApp7net5.Models
{
public partial class Menu
{
public Menu()
{
InverseParent = new HashSet<Menu>();
}
public int Id { get; set; }
public int? ParentId { get; set; }
public string Label { get; set; }
public virtual Menu Parent { get; set; }
public virtual ICollection<Menu> InverseParent { get; set; }
}
}
Here's what we see after asking EFC (5, in my case) to download it all with just a ToList:
Of course it might make sense to start with a root (or multiple roots but my data only has one)
Don't give classes plural names (Menus), btw, and don't give properties plural names if they aren't collections/enumerables (Parents) - it makes for very confusing code
Related
I have a Razor pages project that is trying to populate a Kendo TreeView (or any other TreeView) from a Database created with a Data Model.
The page I am working on contains apps that are in use, and the tree is reading a self referencing Organisations list so that we know what apps each organisation or department etc has access to.
I'm working on the Edit page in a razor app, so ~Pages\Apps\Edit.cshtml and ~Pages\Apps\Edit.cshtml.cs, with associated model pages as shown below.
These are the models involved, ignore RoleApps for this issue:
namespace FliveRetry.Models
{
public class Org
{
public int ID { get; set; }
public string OrgName { get; set; }
public int? ParentID { get; set; }
public bool? HasChildren { get; set; }
}
}
And
namespace FliveRetry.Models
{
public class App
{
public int ID { get; set; }
public string AppName { get; set; }
public string AppDescription { get; set; }
public int? DisplayOrder { get; set; }
public bool? Archived { get; set; }
public DateTime? Saved { get; set; }
public int? SavedBy { get; set; }
public ICollection<OrgAppJoin> OrgAppJoins { get; set; }
public ICollection<RoleAppJoin> RoleAppJoins { get; set; }
}
}
and the index model:
{
public class AppIndexData
{
public IEnumerable<App> Apps { get; set; }
public IEnumerable<Role> Roles { get; set; }
public IEnumerable<Org> Orgs { get; set; }
public IEnumerable<RoleAppJoin> RoleAppJoins { get; set; }
public IEnumerable<OrgAppJoin> OrgAppJoins { get; set; }
}
public class AssignedAppOrgData
{
public int OrgID { get; set; }
public string Org { get; set; }
public int? ParentID { get; set; }
public bool Assigned { get; set; }
public bool? HasChildren { get; set; }
}
public class SelectedAppOrgs
{
public int OrgID { get; set; }
}
public class SelectedAppOrgNames
{
public string OrgName { get; set; }
}
I have a page model to populate selected items into lists called selectedOrgs or selectedOrgNames that I can use in the view.
public class AppSelectPageModel : PageModel
{
//Load list for Selecting Orgs for Apps
public List<AssignedAppOrgData> AssignedAppOrgDataList;
public List<SelectedAppOrgs> selectedOrgs;
public List<SelectedAppOrgNames> selectedOrgNames;
public void PopulateAssignedAppOrgData(FliveRetryContext context, App app)
{
var allOrgs = context.Org;
var appOrgs = new HashSet<int>(
app.OrgAppJoins.Select(c => c.OrgID));
AssignedAppOrgDataList = new List<AssignedAppOrgData>();
selectedOrgs = new List<SelectedAppOrgs>();
selectedOrgNames = new List<SelectedAppOrgNames>();
foreach (var org in allOrgs)
{
AssignedAppOrgDataList.Add(new AssignedAppOrgData
{
OrgID = org.ID,
Org = org.OrgName,
Assigned = appOrgs.Contains(org.ID)
});
if (appOrgs.Contains(org.ID))
{
selectedOrgs.Add(new SelectedAppOrgs
{
OrgID = org.ID
});
selectedOrgNames.Add(new SelectedAppOrgNames
{
OrgName = org.OrgName
});
}
}
}
public void UpdateAppOrgs(FliveRetryContext context,
string[] selectedOrgs, App appToUpdate)
{
if (selectedOrgs == null)
{
appToUpdate.OrgAppJoins = new List<OrgAppJoin>();
return;
}
var selectedOrgsHS = new HashSet<string>(selectedOrgs);
var appOrgs = new HashSet<int>
(appToUpdate.OrgAppJoins.Select(c => c.Org.ID));
foreach (var org in context.Org)
{
if (selectedOrgsHS.Contains(org.OrgName.ToString()))
{
if (!appOrgs.Contains(org.ID))
{
appToUpdate.OrgAppJoins.Add(
new OrgAppJoin
{
AppID = appToUpdate.ID,
OrgID = org.ID
});
}
}
else
{
if (appOrgs.Contains(org.ID))
{
OrgAppJoin orgToRemove
= appToUpdate
.OrgAppJoins
.SingleOrDefault(i => i.OrgID == org.ID);
context.Remove(orgToRemove);
}
}
}
}
I then process them in OnGetAsync in Edit.cshtml.cs:
public async Task<IActionResult> OnGetAsync(int? id)
{
this.TreeData = GetOrgTreeData();
if (id == null)
{
return NotFound();
}
App = await _context.App
.Include(i => i.OrgAppJoins).ThenInclude(i => i.Org)
.Include(i => i.RoleAppJoins).ThenInclude(i => i.Role)
.AsNoTracking()
.FirstOrDefaultAsync(m => m.ID == id);
if (App == null)
{
return NotFound();
}
PopulateAssignedAppRoleData(_context, App);
PopulateAssignedAppOrgData(_context, App);
return Page();
}
and OnPostAsync
public async Task<IActionResult> OnPostAsync(int? id, string[] selectedOrgs, string[] selectedRoles)
{
if (!ModelState.IsValid)
{
return Page();
}
var appToUpdate = await _context.App
.Include(i => i.OrgAppJoins).ThenInclude(i => i.Org)
.Include(i => i.RoleAppJoins).ThenInclude(i => i.Role)
.FirstOrDefaultAsync(s => s.ID == id);
if (await TryUpdateModelAsync<App>(
appToUpdate,
"app", // Prefix for form value.
c => c.AppName, c => c.AppDescription, c => c.DisplayOrder))
{
UpdateAppOrgs(_context, selectedOrgs, appToUpdate);
UpdateAppRoles(_context, selectedRoles, appToUpdate);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
UpdateAppOrgs(_context, selectedOrgs, appToUpdate);
UpdateAppRoles(_context, selectedRoles, appToUpdate);
PopulateAssignedAppOrgData(_context, App);
PopulateAssignedAppRoleData(_context, App);
return Page();
}
This works fine when using multiselect dropdowns and reads and writes correctly to the many to many join tables.
I am using kendo controls at the moment, but I'm happy to use generic if I can find a solution to my problem.
I need to have a treeview for the Org model to display as a multi select, so I have two examples I am trying to get working, that behave differently, the DropDownTreeview is not essential but in some pages it will be handy, the TreeView is essential.
The first is the TreeView:
#(Html.Kendo().TreeView()
.Name("selectedOrgNames")
.DataTextField("OrgName")
.Checkboxes(checkboxes => checkboxes
.Name("ischecked")
.CheckChildren(true))
.HtmlAttributes(new { style = "width:100%" })
.DataSource(d => d
.Read(read =>
read.Url("/Apps/Edit?handler=Read")
)
)
)
The second is the DropDownTreeview:
#(Html.Kendo().DropDownTree()
.Placeholder("Select ...")
.Name("selectedOrgs")
.DataTextField("OrgName")
.DataValueField("ID")
.Checkboxes(checkboxes => checkboxes
.Name("ischecked")
.CheckChildren(true))
.AutoClose(false)
.Value(Model.selectedOrgNames)
.HtmlAttributes(new { style = "width:100%" })
.DataSource(d => d
.Read(read =>
read.Url("/Apps/Edit?handler=Read")
)
)
)
Both examples read the datasource from here in edit.cshtml.cs:
public IActionResult OnGetRead(int? id)
{
var result = from e in _context.Org
where id.HasValue ? e.ParentID == id : e.ParentID == null
select new
{
id = e.ID,
hasChildren = (from q in _context.Org
where (q.ParentID == e.ID)
select q
).Count() > 0,
OrgName = e.OrgName,
selected = (from s in _context.OrgAppJoin
where (s.OrgID == e.ID) && (s.AppID == 2)// <--this works, this doesn't--> (s.AppID == app.ID)
select s
).Count() > 0,
ischecked = (from s in _context.OrgAppJoin
where (s.OrgID == e.ID) && (s.AppID == 2)// <--this doesn't work, this doesn't either-->
(s.AppID == app.ID)
select s
).Count() > 0
};
return new JsonResult(result);
}
My first issue is probably very simple, I'm new to this platform: I can't seem to find a way to get the value of the AppID from the page into the OnGetRead Module ( i have hard coded s.AppID == 2 as an test example to see if it works) I have tried all sorts of variables and other methods.
The id passed into OnPostAsync and OnGetAsync is the id of the App, but the id passed into the OnGetRead is the id of the Org, which is correct and works, but how do I use the AppID from the page to replace the number two in this line? where (s.OrgID == e.ID) && (s.AppID == 2)?
My second issue is getting the checkboxes to read and write.
The DropDownTree above writes to database correctly, but doesn't read and populate checkboxes.
The Treeview doesn't populate checkboxes or write to the database, however it DOES read the selected value from the join table for app number 2 (or any other number I manually insert) in OnGetRead, and displays different font colours etc for the correct items as selected, but not as checked (aschecked may not even be a valid call theere, but I can't find a reference for that).
I feel like I'm close but I have tried for so long to get this to work to no avail, including to and fro with telerik who have tried help but they don't really help much with modelling to your own code, just with static data, and they seem to be light on for a Razor app knowledgebase and keep giving examples of controllers with hard coded data.
Please advise if I need to split this into two questions or more but any help will be much appreciated
Thanks
So I've got a nested data structure like this:
public class ContractTerm
{
public int ContractId { get; set; }
public string SectionId { get; set; }
public string SubsectionId { get; set; }
public string TermId { get; set; }
public int TermOrder { get; set; }
public TermItem TermNavigation { get; set; }
}
public class TermItem
{
public string SectionId { get; set; }
public string SubsectionId { get; set; }
public string TermId { get; set; }
public string Text { get; set; }
public ICollection<ContractTerm> ContractNavigation { get; set; }
}
I've also got a class to map the section/subsection pairings in a more EF-friendly way (IRL this is an enum with attribute values and a helper, but this class abstracts away some work not necessary to reproduce the issue):
public class Section
{
public string Name { get; set; }
public string SectionId { get; set; }
public string SubsectionId { get; set; }
}
Both ContractTerm and TermItem have their own collections in a DbContext, and I'm trying to get a collection of all text entries assigned to specific Sections for a given ContractId. I have the following class to contain it:
public class TextsBySection
{
public string SectionName { get; set; }
public IEnumerable<string> Texts { get; set; }
}
I want to select a collection of TextsBySection, and have something like this:
public class ContractManager
{
//insert constructor initializing MyContext here
private MyContext Context { get; }
public IEnumerable<MyOutputClass> GetTerms(int contractId, IEnumerable<Section> sections)
{
Func<string, string, IEnumerable<string>> getBySection =
(section, subsection) => context.ContractTerms.Include(x => x.TermNavigation)
.Where(x => x.ContractId == contractId
&& x.SectionId == section
&& x.SubsectionId == subsection)
.Select(x => x.TermNavigation.Text);
var result = sections.Select(x => new MyOutputClass
{
SectionName = x.Name,
Texts = getBySection(x.SectionId, x.SubsectionId)
}).ToList();
return result;
}
}
This works fine and dandy, but it hits the database for every Section. I feel like there's got to be a way to use Join and/or GroupBy to make it only query once, but I can't quite see it. Something like this, perhaps:
var result = context.ContractTerms.Include(x => x.TermNavigation)
.Where(x => x.ContractId == contractId)
.Join(sections,
term => //something
section => //something
(term, section) => /*something*/)
If all this were in SQL, selecting the necessary data would be easy:
SELECT sections.name,
term_items.text
FROM contract_terms
JOIN term_items
ON term_items.section_id = contract_terms.section_id
AND term_items.subsection_id = contract_terms.subsection_id
AND term_items.term_id = contract_terms.term_id
JOIN sections --not a real table; just corresponds to sections argument in method
ON sections.section_id = contract_terms.section_id
AND sections.subsection_id = contract_terms.subsection_id
...and then I could group the results in .NET. But I don't understand how to make a single LINQ query that would do the same thing.
I changed my answer, well I would do something like this... maybe this may help you.
public static void Main(string[] args)
{
List<Section> sections = new List<Section>();
List<ContractTerm> contractTerms = new List<ContractTerm>();
List<TermItem> termItens = new List<TermItem>();
//considering lists have records
List<TextsBySection> result = (from contractTerm in contractTerms
join termItem in termItens
on new
{
contractTerm.SectionId,
contractTerm.SubsectionId,
contractTerm.TermId
}
equals new
{
termItem.SectionId,
termItem.SubsectionId,
termItem.TermId
}
join section in sections
on new
{
contractTerm.SectionId,
contractTerm.SubsectionId
} equals new
{
section.SectionId,
section.SubsectionId
}
select
new
{
sectionName = section.Name,
termItemText = termItem.Text
}).GroupBy(x => x.sectionName).Select(x => new TextsBySection()
{
SectionName = x.Key,
Texts = x.Select(i=> i.termItemText)
}).ToList();
}
Multiple answers have led me to the following 2 solutions, but both of them do not seem to be working correctly.
What I have are 2 objects
public class DatabaseAssignment : AuditableEntity
{
public Guid Id { get; set; }
public string User_Id { get; set; }
public Guid Database_Id { get; set; }
}
public class Database : AuditableEntity
{
public Guid Id { get; set; }
public string Name { get; set; }
public string Server { get; set; }
public bool IsActive { get; set; }
public Guid DatabaseClientId { get; set; }
}
Now, the front-end will return all selected Database objects (as IEnumerable) for a given user. I am grabbing all current DatabaseAssignments from the database for the given user and comparing them to the databases by the Database.ID property. My goal is to find the DatabaseAssignments that I can remove from the database. However, my solutions keep returning all DatabaseAssignments to be removed.
if (databases != null)
{
var unitOfWork = new UnitOfWork(_context);
var userDatabaseAssignments = unitOfWork.DatabaseAssignments.GetAll().Where(d => d.User_Id == user.Id);
//var assignmentsToRemove = userDatabaseAssignments.Where(ud => databases.Any(d => d.Id != ud.Database_Id));
var assignmentsToRemove = userDatabaseAssignments.Select(ud => userDatabaseAssignments.FirstOrDefault()).Where(d1 => databases.All(d2 => d2.Id != d1.Database_Id));
var assignmentsToAdd = databases.Select(d => new DatabaseAssignment { User_Id = user.Id, Database_Id = d.Id }).Where(ar => assignmentsToRemove.All(a => a.Database_Id != ar.Database_Id));
if (assignmentsToRemove.Any())
{
unitOfWork.DatabaseAssignments.RemoveRange(assignmentsToRemove);
}
if (assignmentsToAdd.Any())
{
unitOfWork.DatabaseAssignments.AddRange(assignmentsToAdd);
}
unitOfWork.SaveChanges();
}
I think u are looking for an Except extension, have a look at this link
LINQ: Select where object does not contain items from list
Or other way is with contains see below Fiddler link :
https://dotnetfiddle.net/lKyI2F
The following are the entity classes to make more understanding of relationships:
public class EmployeeCv : UserEntity
{
public byte ProfileImage { get; set; }
public virtual List<Header> Headers { get; set; }
public virtual List<ProjectExperience> ProjectExperiences { get; set; }
public virtual List<Tag> Tags { get; set; } //many to many relationship between employeeCv and tags
[NotMapped]
public List<TagsByTypes> TagsbyTypes
{
get
{
List<TagsByTypes> ListOfTagTypes = new List<TagsByTypes>();
if (Tags != null)
{
var GroupedList = Tags.GroupBy(x => x.TagType.Title).ToList().Select(grp => grp.ToList());
foreach (var currentItem in GroupedList)
{
var TagType = new TagsByTypes()
{
Title = currentItem.FirstOrDefault().TagType.Title,
Tags = currentItem
};
ListOfTagTypes.Add(TagType);
}
return ListOfTagTypes;
}
else
return null;
}
}
}
public class Tag : AuditableEntity<int>
{
public string Title { get; set; }
public virtual List<EmployeeCv> EmployeeCv { get; set; }
public virtual TagType TagType { get; set; }
//To post Id's Not added to the database
[NotMapped]
public int TagTypeId { get; set; }
[NotMapped]
public int EmployeeCv_Id { get; set; }
}
public class TagType : AuditableEntity<int>
{
public string Title { get; set; }
public virtual List<Tag> Tags { get; set; }
}
I am writing a function to add new tag to the employeeCv based on the existing tag type. I have got Unit of work and Repositories setup to add/update/delete records in DB. Here is my implementation:
public void UpdateEmployeeCVWithTag(Tag tag)
{
using (var repository = new UnitOfWork<EmployeeCv>().Repository)
{
var EmployeeCv = repository.GetSingleIncluding(tag.EmployeeCv_Id,
x => x.Headers, x => x.Tags,
x => x.ProjectExperiences,
x => x.ProjectExperiences.Select(p => p.AssociatedProject),
x => x.ProjectExperiences.Select(p => p.ProjectSkills));
//x => x.ProjectExperiences.Select(p => p.ProjectSkillTags.Select(s => s.AssociatedSkill)));
//tag.TagType = EmployeeCv;
var repositoryTagType = new UnitOfWork<TagType>().Repository;
var tagtype = repositoryTagType.GetItemById(tag.TagTypeId);
tag.TagType = tagtype; //even after assignment new tagtype is creating everytime code runs
//repositoryTag.UpdateItem(tagtype);
EmployeeCv.Tags.Add(tag);
//EmployeeCv.ProjectExperiences[projectId - 1].ProjectSkills.Add(tag);
repository.UpdateItem(EmployeeCv);
}
}
This function works correctly except one issue. It is creating a new TagType in the database and ignoring the one that already exist. Below is my updateItem code in the repository classs:
public virtual void UpdateItem(TEntity entityToUpdate)
{
var auditableEntity = entityToUpdate as IAuditableEntity;
if (auditableEntity != null)
{
auditableEntity.UpdatedDate = DateTime.Now;
}
//_context
//Attach(entityToUpdate);
_context.Entry(entityToUpdate).State = EntityState.Modified;
_context.SaveChanges();
}
My guess without seeing the full functionality, is that you are using different context for this.
You should update the foreign key not the entire object so there is no need to add the entire TagType object since the tagTypeId is already set. The foreign key should work as is.
Please look into this link for further information.
I am trying to achieve a functionality very similar to what we have here on SO - The Tagging System. I enter tags, it looks if they exist - and if not, they are being created an associated with the post via a Join Table (Many-To-Many).
It works like this: The User enters the Tags, in a ", "-Seperated Value (The TagList). I split the TagList with a RegEx to extract the different tags - I try to look the tag up in the Database. And If it doesnt exist, I create it.
So far, this is what I have:
Recipe.cs
public class Recipe
{
[Key]
public int RecipeId { get; set; }
[Required]
public string Name { get; set; }
public string Subtitle { get; set; }
public int Serving { get; set; }
public string Instructions { get; set; }
public int PrepTime { get; set;}
public int CookingTime { get; set; }
public IList<Wine> Wines { get; set; }
public IList<Pairing> Pairings { get; set; }
public ICollection<UsedIngredient> UsedIngredients { get; set; }
[NotMapped]
public string TagList { get; set; }
public IList<Tag> Tags { get; set; }
}
Tag.cs
public class Tag
{
public int TagId { get; set; }
public string Name { get; set; }
public ICollection<Recipe> Recipes { get; set; }
}
The Join Table
CreateTable(
"dbo.TagRecipes",
c => new
{
Tag_TagId = c.Int(nullable: false),
Recipe_RecipeId = c.Int(nullable: false),
})
.PrimaryKey(t => new { t.Tag_TagId, t.Recipe_RecipeId })
.ForeignKey("dbo.Tags", t => t.Tag_TagId, cascadeDelete: true)
.ForeignKey("dbo.Recipes", t => t.Recipe_RecipeId, cascadeDelete: true)
.Index(t => t.Tag_TagId)
.Index(t => t.Recipe_RecipeId);
TagRepo - FindOrCreate Method
public Tag FindOrCreateTag(string tagName)
{
Tag tag = context.Tags.Where(t => t.Name == tagName).Include("Recipes").FirstOrDefault();
if (tag == null)
{
tag = new Tag
{
Name = tagName,
Recipes = new List<Recipe>()
};
context.Tags.Add(tag);
}
return tag;
}
RecipeRepo - GetTagList
private IList<String> GetTagList(string tagString)
{
IList<string> tagList = new List<string>(Regex.Split(tagString, #"\,\s*"));
return tagList;
}
RecipeRepo - AssignTags
public void AssignTags(Recipe recipe, string tagString)
{
if (recipe.Tags == null)
recipe.Tags = new List<Tag>();
IList<string> tags = GetTagList(tagString);
foreach (string tagName in tags)
{
Tag tag = tagRepository.FindOrCreateTag(tagName);
if (tag.Recipes == null)
tag.Recipes = new List<Recipe>();
if (!tag.Recipes.Any(r => r.RecipeId == recipe.RecipeId))
tag.Recipes.Add(recipe);
if (recipe.Tags.All(t => t.TagId != tag.TagId))
recipe.Tags.Add(tag);
}
}
And in the end, Im calling this.
RecipeRepo - Update
public bool Update(Recipe recipe)
{
if (recipe.TagList != null)
AssignTags(recipe, recipe.TagList);
Recipe dbEnt = context.Recipes.Find(recipe.RecipeId);
context.Entry(dbEnt).CurrentValues.SetValues(recipe);
return Save();
}
What happens is - It takes the String, splits it up correctly - but after that, things seem to go south a bit. Instead of simply assigning the new tag, it duplicates the Recipe Object.
Whats is it, that Im missing?
Object that is retrieved from the context (such as a result of Find) should be 'Attached' before it has changes tracking capabilities, and is possible to be updated in the DB.
You should also mark it as 'Modified'.
For example, a proper update method should be something like:
public bool Update(Recipe recipe)
{
if (recipe.TagList != null)
AssignTags(recipe, recipe.TagList);
Recipe dbEnt = context.Recipes.Find(recipe.RecipeId);
if (context.Entry(dbEnt).State == EntityState.Detached)
{
context.Set<Recipe>().Attach(dbEnt);
}
context.Entry(dbEnt).State = EntityState.Modified;
context.Entry(dbEnt).CurrentValues.SetValues(recipe);
return Save();
}
If you would like to make a generic update function, it should look as follows:
public void Update(TEntity entityToUpdate)
{
if (context.Entry(entityToUpdate).State == EntityState.Detached)
{
context.Set<TEntity>().Attach(entityToUpdate);
}
context.Entry(entityToUpdate).State = EntityState.Modified;
}