Entity Framework Recursive Relationship Hierarchical Data - c#

Does Entity Framework support some kind of recursive LINQ or do I have to write my queries in SQL? (Using ParentId - Typical category subcategory problem)

I think you can solve it by using Include method inside extension method to get only piece of hierarchy or get it all. Even it can generate pretty ugly SQL.
using(var context = new HierarchyContext())
{
var depth = context
.Categories
.IncludeHierarchy(3, nameof(Category.Children));
var root = depth.Single(c => c.Id == 2);
}
public static IQueryable<T> IncludeHierarchy<T>(this IQueryable<T> source,
uint depth, string propertyName)
where T : Category
{
var temp = source;
for (var i = 1; i <= depth; i++)
{
var sb = new StringBuilder();
for (var j = 0; j < i; j++)
{
if (j > 0)
{
sb.Append(".");
}
sb.Append(propertyName);
}
var path = sb.ToString();
temp = temp.Include(path);
}
var result = temp;
return result;
}
public class Category
{
// Primary key
public int Id { get; set; }
// Category name
public string Name { get; set; }
// Foreign key relationship to parent category
public int ParentId { get; set; }
// Navigation property to parent category
public virtual Category Parent { get; set; }
// Navigation property to child categories
public virtual ICollection<Category> Children { get; set; }
}

Related

How to sum the value of child items from a hierarchical list in a loop

How to sum the value of child items from a hierarchical list in a loop.
I need that during the loop in the list the values of the amount and price property contain the sum of these properties of the children.
Below are the two classes to be used to help me solve my problem.
namespace SGP.Dto.Custo
{
public class PlanilhaCusto
{
public int id{ get; set; }
public int parenteId{ get; set; }
public string name { get; set; }
public decimal amount{ get; set; }
public decimal price{ get; set; }
public PlanilhaCusto(int pId, int pParenteId, pName, decimal pAmount, decimal pPrice)
{
id = pId;
parentId = pParentId;
name = pName;
amount = pAmount;
price = pPrice;
}
}
}
namespace SGP.Dto.Custo
{
public class ShowList
{
List<Dto.Custo.PlanilhaCusto> myList = new List<PlanilhaCusto>();
public void Show()
{
myList.Add(new PlanilhaCusto(1, null, "Projetos", 0, 0));
myList.Add(new PlanilhaCusto(2, 1, "Arquitetura", 5,10));
myList.Add(new PlanilhaCusto(3, 1, "Estrutura", 0, 0));
myList.Add(new PlanilhaCusto(4, 3, "Civil", 1, 50));
myList.Add(new PlanilhaCusto(5, 3, "Infra", 3, 75));
myList.Add(new PlanilhaCusto(6, null, "Pessoal", 0, 0));
myList.Add(new PlanilhaCusto(7, 6, "Mão de Obra", 20, 5700));
/*In this loop the value of the parent items must be updated
(calculated). The hierarchy of the list can be unlimited,
like a tree. I tried using a recursive method but I could
not do it.*/
foreach (var itemList in myList)
{
}
}
}
}
Assuming the Elements in the list are sorted the way they are in your example, the fastest way to do this should be to iterate through the list in reverse order and add the amount to the list entry with the correct id.
Iterate backwards through the list
When the current element has a parent add amount and amount*price to it
EDIT:
After reading your comment in your source, i assume you already knew what i just wrote.
My approach would be the following:
for (int i = myList.Count - 1; i > 1; i--)
{
var temp = myList.ElementAt(i);
if (temp.parentId != null)
{
var parent = myList.ElementAt(temp.parentId - 1);
parent.amount += temp.amount;
parent.price += (temp.amount * temp.price);
}
}
for creating a hierarchical structure, i modified your PlanilhaCusto to resemble a sort of a Node in tree, having both parent and children members, for ease the traverse
public class PlanilhaCusto
{
public int Id { get; set; }
public string Name { get; set; }
public int? ParentId { get; set; }
public decimal Amount { get; set; }
public decimal Price { get; set; }
public IEnumerable<PlanilhaCusto> Children { get; set; }
}
Given this structure and an initial input
you can build the entire tree structure regardless of how many levels there are relying on some recursion
public IEnumerable<PlanilhaCusto> Descendants(PlanilhaCusto parent, IEnumerable<PlanilhaCusto> source)
{
var query = from node in source
where node.ParentId == parent.Id
let children = Descendants(node, source)
select new PlanilhaCusto
{
Id = node.Id,
ParentId = node.ParentId,
Name = node.Name,
Amount = node.Amount,
Price = node.Price,
Children = children,
};
return query.ToList();
}
var hierarchy = from node in source
let children = Descendants(node, source)
where node.ParentId == null
select new PlanilhaCusto
{
Id = node.Id,
ParentId = node.ParentId,
Name = node.Name,
Price = node.Price,
Children = children,
};
that would project the initial data source into something similar
and from here you just need to traverse the hierarchy and compose the total price
public decimal? Total(PlanilhaCusto node)
{
decimal? price = node.Price * node.Amount;
if (node.Children != null)
{
foreach (var child in node.Children)
{
price += Total(child);
}
}
return price;
}
var totals = from node in hierarchy
select new
{
Id = node.Id,
Name = node.Name,
Total = Total(node),
};

using linq and c# to create a nested list object from a flat list object

I have a question. It's about linq in combination with c#.
I want to create a tree structure from a flatten structure in a pre defined object structure.
The following code which I've got work, but both are not exactly what i want.
In linq:
var result = listAgenderingen.GroupBy(records => records.Agnnummer)
.Select(group => new { AgnNummer = group.Key, Items = group.ToList()}).ToList();
the issue is that this does not result in the object I want.
So I've rewritten this to the following code
List<string> test = listAgenderingen.Select(x => x.Agnnummer).Distinct().ToList();
foreach (var item in test)
{
List<Agendering> listAgendering = listAgenderingen.Where(agend => agend.Agnnummer == item).OrderBy(ord => ord.Agnnummer).ToList();
AgnAgendering AgnAgendering = new AgnAgendering() {AgnNummer =item, Agenderingen = listAgendering };
}
this code actually works correct. but for 200000 records, it's taking a lot of time while the original linq takes a few seconds.
my question is can the linq be rewritten so it will create or convert to the richt object?
the structure of the classes:
public class Agendering
{
public int AgnID { get; set; }
public string Agnnummer { get; set; }
}
public class AgnAgendering
{
public string AgnNummer { get; set; }
public List<Agendering> Agenderingen { get; set; }
}
I hope someone has a sollution.
If I understand correctly, you want:
var result = listAgenderingen.GroupBy(records => records.Agnnummer)
.Select(group => new AgnAgendering { AgnNummer = group.Key, Agenderingen = group.ToList()}).ToList();
Your properties naming makes it absolutely unreadable and unclear.
Assuming that you have a flat structure like:
public class Item
{
public int ID { get; set; }
public int? ParentID { get; set; }
}
and you want a tree-like structure:
public class TreeItem
{
public int ID { get; set; }
public TreeItem Parent { get; set; }
public List<TreeItem> Children { get; set; }
public TreeItem(int id)
{
ID = id;
Children = new List<TreeItem>();
}
public TreeItem(int id, TreeItem parent) : this(id)
{
Parent = parent;
}
}
You can do most optimally in O(n) using Dictionary:
Item[] items = ...;
Dictionary<int, TreeItem> result = new Dictionary<int, TreeItem>();
foreach (var item in items.OrderBy(x => x.ParentID ?? -1))
{
TreeItem current;
if (item.ParentID.HasValue)
{
TreeItem parent = result[item.ParentID]; // guaranteed to exist due to order
current = new TreeItem(item.ID, parent);
parent.Children.Add(current);
} else {
current = new TreeItem(item.ID);
}
}
TreeItem[] treeItems = result.Values.ToArray();

Entity framework, Navigation Properties, call and store values in the database table

I am trying to parse a text file into 3 tables using C Sharp and T-SQL. Below are the table definitions -
1) TTransaction (TransactionID Identity(1,1), some other attributes)
2) TMatch (MatchID Identity(1,1), some other attributes)
3) TTransactionXTMatch (TransactionID ,MatchID )
Using C sharp, I parsed the data into tables TTransaction and TMatch. I am not sure how to use the navigation properties of Entity Framework to populate these 2 ID's in the TTransactionXTMatch (Bridge) Table. There is one to many relationship between TTransaction and TMatch.
See the code below:
TTransaction txn = new TTransaction();
txn.TRN = txnNo;
txn.Amount = Convert.ToDecimal(Amount);
txn.TransactionText = Convert.ToString(txnText);
txn.TransactionLocation = TxnLOC;
context.TTransactions.Add(txn);
context.SaveChanges();
TMatch Mtc = new TMatch();
Mtc.RiskWord = RiskWord;
Mtc.GoodGuyWord = GoodGuyWord;
Mtc.Origin = Origin;
Mtc.Location = Location;
context.TMatches.Add(Mtc); //Adding to the database
context.SaveChanges();
I am wondering how do call the TTransactionXTMatch table since it doesn't come up in the model and there is no class created for it. It does show in the navigation properties but I am not sure how to use that.
Can someone please shed some light on this. Kindly let me know if you need additional info on what I am doing.
Thanks in advance !
Here is a Code first sample for you. In this sample note that, there is a bunch of Customers created first, then a bunch of products (real world would look like that).
Then to show Many-To-Many, we add random number of products to a customer through Products property (navigation property) in a loop and EF does the rest.
string defaultConString = #"server=.\SQLExpress;Database=CodeFirstDbSample;Trusted_Connection=yes;";
void Main()
{
CreateSampleCodeFirstData();
ListData();
}
private void CreateSampleCodeFirstData()
{
var ctx = new MyContext(defaultConString);
for (int i = 0; i < 10; i++)
{
var c = new Customer { CustomerName="c" + i };
ctx.Customers.Add( c );
}
for (int i = 0; i < 10; i++)
{
var p = new Product { ProductName="p" + i };
ctx.Products.Add( p );
}
ctx.SaveChanges();
for (int i = 0; i < 10; i++)
{
var customer = ctx.Customers.Single (c => c.CustomerName == "c"+i);
var products = ctx.Products.OrderBy (p => Guid.NewGuid()).Take(3);
customer.Products = new List<Product>();
customer.Products.AddRange( products );
}
ctx.SaveChanges();
}
private void ListData()
{
var ctx = new MyContext(defaultConString);
Console.WriteLine ("By Customer");
Console.WriteLine ("".PadRight(50,'-'));
foreach (Customer c in ctx.Customers.Include("Products"))
{
Console.WriteLine ("{0}: {1}", c.CustomerId, c.CustomerName);
Console.WriteLine ("\t\t{0}",
string.Join(",", c.Products.Select (p => p.ProductName)));
}
Console.WriteLine ("".PadRight(50,'='));
Console.WriteLine ();
Console.WriteLine ("By Product");
Console.WriteLine ("".PadRight(50,'-'));
foreach (Product p in ctx.Products.Include("Customers"))
{
Console.WriteLine ("{0}: {1}", p.ProductId, p.ProductName);
Console.WriteLine ("\t\t{0}",
string.Join(",", p.Customers.Select (c => c.CustomerName) ));
}
Console.WriteLine ("".PadRight(50,'='));
Console.WriteLine ();
}
public class MyContext : DbContext
{
public MyContext(string connectionString)
: base(connectionString) {}
public DbSet<Customer> Customers { get; set; }
public DbSet<Product> Products { get; set; }
}
public class Product
{
public int ProductId { get; set; }
public string ProductName { get; set; }
public virtual List<Customer> Customers { get; set; }
}
public class Customer
{
public int CustomerId{ get; set; }
public string CustomerName{ get; set; }
public virtual List<Product> Products { get; set; }
}
IS your project using code first or database first migrations?
It is possible that the table was created without a migration meaning that Entity Framework would not work with this table.

Retrieve GrandChild collection from root parent

I have the following object graph:
Root ( Root_Id)
----Child (Child_Id,Root_Id)
-------GrandChild (GrandChild_Id, Child_Id)
And I want to bypass Child and return GrandChild collection having a Root object. So far i have tried this:
var child_Ids = db.Root
.SingleOrDefault( r => r.Root_Id == rootID )
.Childs
.Select( ch => new { Child_Id = ch.Child_Id} ).ToArray();
return db.GrandChilds.Where( gc => child_Ids.Contains( gc.Child_Id ) );
But that wont even compile with the following errors :
1) IEnumerable does not contain a definition for Contains...
2) Argument instance: can not convert from 'AnonymousType # 1 []' to 'System.Linq.IQueryable
How can i accomplish this?
db.Root
.SingleOrDefault( r => r.Root_Id == rootID )
.Childs.SelectMany(ch=>ch.GrandChilds).Distinct()
Use the .SelectMany extension to get the grandchildren collection
Try this
var child_Ids = db.Root
.SingleOrDefault( r => r.Root_Id == rootID )
.Childs
.Select( ch => ch.Child_Id)
.ToArray();
return
from grandChild in db.GrandChild
join child_id in child_Ids
on child_id == grandChild.HandlingUnit_Id
select grandChild;
P.S: I am still a bit unsure about your goal but it looks like working approximation of your original solution
EDIT:
If your hierarchy and classes are something like:
public class Db
{
public Db(IEnumerable<Root> roots)
{ this.Roots = new List<Root>(roots); }
public ICollection<Root> Roots { get; private set; }
}
public class Root
{
public Root(IEnumerable<Child> children )
{
this.Children = new List<Child>(children);
}
public ICollection<Child> Children { get; private set; }
}
public class Child
{
public Child(Int32 childId, Int32 rootId, IEnumerable<GrandChild> grandChildren)
{
this.Child_Id = childId;
this.Root_Id = rootId;
this.GrandChildren = new List<GrandChild>(grandChildren);
}
public Int32 Child_Id { get; private set; }
public Int32 Root_Id { get; private set; }
public ICollection<GrandChild> GrandChildren {get; private set;}
}
public class GrandChild
{
public GrandChild (Int32 grandChildId, Int32 childId)
{
this.GrandChild_Id = grandChildId;
this.Child_Id = childId;
}
public Int32 GrandChild_Id {get; private set;}
public Int32 Child_Id {get; private set;}
}
Then as it was already suggested by AD.NET you could try the SelectMany method
GrandChild gc1 = new GrandChild(1, 10);
GrandChild gc2 = new GrandChild(2, 10);
GrandChild gc3 = new GrandChild(3, 11);
Child c1 = new Child(10, 100, new GrandChild[]{ gc1, gc2 });
Child c2 = new Child(11, 100, new GrandChild[]{ gc3 });
Root r1 = new Root(new Child[]{c1, c2});
Db db = new Db(new Root[] { r1 });
var rootGrandChildren = db
.Roots
.FirstOrDefault()
.Children
.SelectMany(child => child.GrandChildren);
In query syntax it will look like
var rootGrandChildren = from child in db.Roots.FirstOrDefault().Children
from grandChild in child.GrandChildren
select grandChild;
But if your Child class does not know his GrandChildren and they(GrandChildren) are contained in Root:
public class Child
{
public Child(Int32 childId, Int32 rootId)
{
this.Child_Id = childId;
this.Root_Id = rootId;
}
public Int32 Child_Id { get; private set; }
public Int32 Root_Id { get; private set; }
}
public class Root
{
public Root(IEnumerable<Child> children, IEnumerable<GrandChild> grandChildren )
{
this.Children = new List<Child>(children);
this.GrandChildren = new List<GrandChild>(grandChildren );
}
public ICollection<Child> Children { get; private set; }
public ICollection<GrandChild> GrandChildren{ get; private set; }
}
you will have to use:
Root r1 = new Root(new Child[]{c1, c2}, new GrandChild[]{gc1, gc2, gc3});
Db db = new Db(new Root[] { r1 });
Root root = db.Roots.FirstOrDefault();
var rootGrandChildren = from child in root.Children
join grandChild in root.GrandChildren
on child.Child_Id equals grandChild.Child_Id
select grandChild;

Copy Entity Framework Object

I have a EF4.1 class X and I want to make copy of that plus all its child records.
X.Y and X.Y.Z
Now if I do the following it returns error.
The property 'X.ID' is part of the object's key information and cannot be modified.
public void CopyX(long ID)
{
var c = db.Xs.Include("Y").Include("W").Include("Y.Z").SingleOrDefault(x => x.ID == ID);
if (c != null)
{
c.ID = 0;
c.Title = "Copy Of " + c.Title;
for (var m = 0; m < c.Ys.Count; m++)
{
c.Ys[m].ID = 0;
c.Ys[m].XID=0-m;
for (var p = 0; p < c.Ys[m].Zs.Count; p++)
{
c.Ys[m].Zs[p].XID = 0 - m;
c.Ys[m].Zs[p].ID = 0 - p;
}
}
for (var i = 0; i < c.Ws.Count; i++)
{
c.Ws[i].ID = 0 - i;
c.Ws[i].XID = 0;
}
db.Entry<Content>(c).State = System.Data.EntityState.Added;
db.SaveChanges();
}
}
Or Is there other way of making copy of entity objects.
NOTE: there are multiple properties in each W,X,Y,Z.
In entity-framework-5, this is insanely easy with the DbExtensions.AsNotracking().
Returns a new query where the entities returned will not be cached in the DbContext or ObjectContext.
This appears to be the case for all objects in the object graph.
You just have to really understand your graph and what you do and don't want inserted/duplicated into the DB.
Lets assume we have objects like:
public class Person
{
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<Address> Addresses { get; set; }
}
public class Address
{
public int ID { get; set; }
public AddressLine { get; set; }
public int StateID { get; set; }
public ICollection<State> { get; set; }
}
So in order to Duplicate a person, I need to duplicate the addresses, but I don't want to duplicate the States.
var person = this._context.Persons
.Include(i => i.Addresses)
.AsNoTracking()
.First();
// if this is a Guid, just do Guid.NewGuid();
// setting IDs to zero(0) assume the database is using an Identity Column
person.ID = 0;
foreach (var address in person.Addresses)
{
address.ID = 0;
}
this._context.Persons.Add(person);
this._context.SaveChanges();
If you then wanted to then reuse those same objects again to insert a third duplicate, you'd either run the query again (with AsNoTracking()) or detach the objects (example):
dbContext.Entry(person).State = EntityState.Detached;
person.ID = 0;
foreach (var address in person.Addresses)
{
dbContext.Entry(address).State = EntityState.Detached;
address.ID = 0;
}
this._context.Persons.Add(person);
this._context.SaveChanges();
You need to make correct deep copy of the whole entity graph - the best way is to serialize the original entity graph to memory stream and deserialize it to a new instance. Your entity must be serializable. It is often used with DataContractSerializer but you can use binary serialization as well.
C is not a copy it is the record, the error you are getting is because you are trying to update it's primary key, even if you weren't it still wouldn't work. You need to make a new X entity and then copy the values from the properties of the retrieved entity and then insert the new entity.
Not sure if it works in 4.1, from http://www.codeproject.com/Tips/474296/Clone-an-Entity-in-Entity-Framework-4:
public static T CopyEntity<T>(MyContext ctx, T entity, bool copyKeys = false) where T : EntityObject
{
T clone = ctx.CreateObject<T>();
PropertyInfo[] pis = entity.GetType().GetProperties();
foreach (PropertyInfo pi in pis)
{
EdmScalarPropertyAttribute[] attrs = (EdmScalarPropertyAttribute[])pi.GetCustomAttributes(typeof(EdmScalarPropertyAttribute), false);
foreach (EdmScalarPropertyAttribute attr in attrs)
{
if (!copyKeys && attr.EntityKeyProperty)
continue;
pi.SetValue(clone, pi.GetValue(entity, null), null);
}
}
return clone;
}
You can copy related entites to your cloned object now too; say you had an entity: Customer, which had the Navigation Property: Orders. You could then copy the Customer and their Orders using the above method by:
Customer newCustomer = CopyEntity(myObjectContext, myCustomer, false);
foreach(Order order in myCustomer.Orders)
{
Order newOrder = CopyEntity(myObjectContext, order, true);
newCustomer.Orders.Add(newOrder);
}
I use Newtonsoft.Json, and this awesome function.
private static T CloneJson<T>(T source)
{
return ReferenceEquals(source, null) ? default(T) : JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source));
}

Categories

Resources