ASP Entity Framework - Code FIrst - Data Design - Keys - c#

I am attempting to structure my database appropriately for my web application. I am unsure the correct way to proceed with the keys and relationships. Basically..
My project contains a Product
Which can contain many Reports, however, the same Report can not relate to multiple Products.
I have Users, which may have many reports available to them.. regardless of the reports parent product.
My structure thus far
public class Product
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
}
public class Report
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public virtual Product Product { get; set; }
}
public class User
{
[Key]
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public virtual ICollection<Report> Reports { get; set; }
}
And to Seed
protected override void Seed(Insight.Data.InsightDb context)
{
context.Products.AddOrUpdate(p => p.ProductID,
new Product { ProductName = "Product 1" },
new Product { ProductName = "Product 2" }
);
context.Reports.AddOrUpdate(p => p.ReportID,
new Report { ReportName = "Report A", ProductID = 1, },
new Report { ReportName = "Report B", ProductID = 1, },
new Report { ReportName = "Report C", ProductID = 1, },
new Report { ReportName = "Report D", ProductID = 2, },
new Report { ReportName = "Report E", ProductID = 2, }
);
context.Users.AddOrUpdate(u => u.UserID,
new User { FirstName = "Frank", LastName = "Reynolds", },
new User { FirstName = "Dee", LastName = "Reyonolds", }
);
}
I am unsure how to properly assign the Reports to the Users, or even know if I am on the right track. Also, is there a better practice than using int as a key?

While I haven't used the approach you have above.. here's my stab at it:
protected override void Seed(Insight.Data.InsightDb context)
{
var product1 = new Product{ Name = "Product 1"};
var product2 = new Product{ Name = "Product 2"};
var reports1 = new List<Report>
{
new Report { Name = "Report A", Product = product1, },
new Report { Name = "Report B", Product = product1, },
new Report { Name = "Report C", Product = product1, },
new Report { Name = "Report D", Product = product2, },
new Report { Name = "Report E", Product = product2, }
};
context.Products.AddOrUpdate(p => p.ProductID,
product1,
product2
);
context.Reports.AddOrUpdate(p => p.ReportID,
reports1
);
context.Users.AddOrUpdate(u => u.UserID,
new User { FirstName = "Frank", LastName = "Reynolds", Reports = reports1 },
new User { FirstName = "Dee", LastName = "Reyonolds" },
);
}
I'm just not sure if setting up the reports part is correct (adding a list to the AddOrUpdate function), so that's something you may have to look in to if this doesn't work.

I have found a solution. It turns out I was interested in a many-to-many relationship, with a bridge table in between Reports and Users. These are my entities to establish this relationship.
public class Report
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public virtual Product Product { get; set; }
public virtual ICollection<User> Users { get; set; }
}
public class User
{
[Key]
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public virtual ICollection<Report> Reports { get; set; }
}
Then to populate my data with a code first approach
protected override void Seed(Insight.Data.InsightDb context)
{
Product product1 = new Product { Name = "Product 1" };
Product product2 = new Product { Name = "Product 2" };
Report reportA = new Report { Name = "Report A", Product = product1 };
Report reportB = new Report { Name = "Report B", Product = product1 };
Report reportC = new Report { Name = "Report C", Product = product1 };
Report reportD = new Report { Name = "Report D", Product = product2 };
Report reportE = new Report { Name = "Report E", Product = product2 };
List<User> users = new List<User>();
users.Add(new User { FirstName = "Dee", LastName = "Reynolds", Reports = new List<Report>() { reportA, reportB, reportC } });
users.Add(new User { FirstName = "Frank", LastName = "Reynolds", Reports = new List<Report>() { reportC, reportD, reportE } });
users.ForEach(b => context.Users.Add(b));
}

Related

LINQ group list and combine collections

Given these classes:
public class Employee
{
public int EmployeeId { get; set; }
public int GroupId { get; set; }
public List<Plans> Plans { get; set; }
}
public class Plan
{
public int PlanYearId { get; set; }
public string Name { get; set; }
}
And given a setup like so:
var employees = new List<Employee> {
new Employee {
EmployeeId = 1,
GroupId = 1,
Plans = new List<Plan> {
new Plan {
PlanReferenceId = 1111,
Name = "Benefit 1"
}}};
new Employee {
EmployeeId = 1,
GroupId = 1,
Plans = new List<Plan> {
new Plan {
PlanReferenceId= 2222,
Name = "Benefit 2"
},
new Plan {
PlanReferenceId= 2222,
Name = "Benefit 3"
}}}};
How can I use LINQ to group these employees by both EmployeeId and GroupId and then combine the two List<Plan> properties so that i would end up with something like this:
var employee = new Employee
{
EmployeeId = 1,
GroupId = 1,
Plans = new List<Plan> {
new Plan {
PlanReferenceId = 1111,
Name = "Benefit 1"
},
new Plan {
PlanReferenceId = 2222,
Name = "Benefit 2"
},
new Plan {
PlanReferenceId = 2222,
Name = "Benefit 3"
}
}
}
Just use combination of GroupBy and SelectMany:
var result = employees
.GroupBy(e => new { e.EmployeeId, e.GroupId })
.Select(g => new Employee
{
EmployeeId = g.Key.EmployeeId,
GroupId = g.Key.GroupId,
Plans = g.SelectMany(e => e.Plans).ToList()
}).ToList();

Order GroupBy Asc based on Two more Properties using LINQ C#

My GroupBy is performing well. I'm getting the Output
I need to Sort the Group Names
The Brown Color Block, represents the Group.
The Red Color Box within the Brown Color Block, represents the Manager
Peter Block (Brown Box) Should Come first
Raj Block (Brown Box) Should Come Second
Sunny Block (Brown Box) Should Come Third
Each Block Should Group By Boss(Manager) and Assistant (Boss don't have the
SID). After GroupBy the Name should be in Sorted Order, within the Group
the Assistant Names are also in the Sorted Order.
The Model Classes:
public class Person
{
public int ID { get; set; }
public int SID { get; set; }
public string Name { get; set; }
public string Department { get; set; }
public string Gender { get; set; }
public string Role { get; set; }
}
public class Boss
{
public int ID { get; set; }
public int SID { get; set; }
public string Name { get; set; }
public string Department { get; set; }
public string Gender { get; set; }
public string Role { get; set; }
public List<Person> Employees { get; set; }
}
The Main Functionality Source Code:
void Main()
{
List<Boss> BossList = new List<Boss>()
{
new Boss()
{
ID = 101,
Name = "Sunny",
Department = "Development",
Gender = "Male",
Role = "Manager",
Employees = new List<Person>()
{
new Person() {ID = 101, SID = 102, Name = "Peter", Department = "Development", Gender = "Male", Role = "Assistant"},
new Person() {ID = 101, SID = 103, Name = "Emma Watson", Department = "Development", Gender = "Female", Role = "Assistant"},
}
},
new Boss()
{
ID = 104,
Name = "Raj",
Department = "Development",
Gender = "Male",
Role = "Manager",
Employees = new List<Person>()
{
new Person() {ID = 104, SID = 105, Name = "Kaliya", Department = "Development", Gender = "Male", Role = "Assistant"},
new Person() {ID = 104, SID = 103, Name = "Emma Watson", Department = "Development", Gender = "Female", Role = "Assistant"},
},
},
new Boss()
{
ID = 102,
Name = "Peter",
Department = "Development",
Gender = "Male",
Role = "Manager",
Employees = new List<Person>()
{
new Person() {ID = 102, SID = 105, Name = "Kaliya", Department = "Development", Gender = "Male", Role = "Assistant"},
new Person() {ID = 102, SID = 103, Name = "Emma Watson", Department = "Development", Gender = "Female", Role = "Assistant"},
}
}
};
List<Person> EmpList = BossList.SelectMany(i =>
new[] {
new Person()
{
ID = i.ID,
SID = i.SID,
Name = i.Name,
Gender = i.Gender,
Department = i.Department,
Role = i.Role
}
}.Concat(i.Employees)
).ToList().GroupBy(s => s.ID).SelectMany(h => h.GroupBy(g => g.SID).SelectMany(u => u.OrderBy(k=> k.Name))).ToList();
}
You can do by adding the ThenBy extension method after the Order by to apply the secondary sort. In fact, the ThenBy can be called multiple times for sorting on multiple property. I have modified the last line of your code to show how you can achieve this.
).ToList().GroupBy(s => s.ID).SelectMany(h => h.GroupBy(g => g.SID).SelectMany(u => u.OrderBy(k=> k.Name).ThenBy(l => l.<<secondproperty>>))).ToList();
The datastructure already establishes the groups. There is no need to re-group.
List<Person> result = (
from boss in BossList
order by boss.Name
let orderedEmployees = boss.Employees.OrderBy(emp => emp.Name)
let bossPerson = new Person(boss)
let people = new List<Person>() { bossPerson }.Concat(orderedEmployees)
from person in people
select person).ToList();
If you prefer the lambda syntax:
List<Person> result = BossList
.OrderBy(boss => boss.Name)
.SelectMany(boss => {
IEnumerable<Person> orderedEmployees = boss.Employees.OrderBy(emp => emp.Name);
Person bossPerson = new Person(boss);
return new List<Person>() { bossPerson }.Concat(orderedEmployees);
})
.ToList();

Flat Data to Hierarchical Model C#

I have some flat data coming from the database that looks like this:
List<FlatDataGroup> elements = new List<FlatDataGroup>()
{
new FlatDataGroup {Text = "", GroupID = 1, ParentGroupID = 0, GroupName = "Admin", UserID = 1, UserName = "John Doe"},
new FlatDataGroup {Text = "", GroupID = 1, ParentGroupID = 0, GroupName = "Admin", UserID = 2, UserName = "Jane Smith"},
new FlatDataGroup {Text = "", GroupID = 2, ParentGroupID = 1, GroupName = "Support", UserID = 3, UserName = "Johnny Support"},
new FlatDataGroup {Text = "", GroupID = 3, ParentGroupID = 2, GroupName = "SubSupport", UserID = 4, UserName = "Sub Johnny Support"},
new FlatDataGroup {Text = "", GroupID = 4, ParentGroupID = 1, GroupName = "Production", UserID = 5, UserName = "Johnny Production"}
};
I would like to convert it to this:
List<Group> model = new List<Group>
{
new Group()
{
ID = 1,
Name = "Admin",
ParentGroupID = 0,
Type = "Group",
Users = new List<User>()
{
new User()
{
ID = 1,
Name = "John Doe",
GroupID = 1,
Type = "User",
},
new User()
{
ID = 2,
Name = "Jane Smith",
GroupID = 1,
Type = "User",
},
},
Groups = new List<Group>
{
new Group()
{
ID = 2,
Name = "Support",
ParentGroupID = 1,
Type = "Group",
Users = new List<User>()
{
new User()
{
ID = 3,
Name = "Johnny Support",
GroupID = 2,
Type = "User",
}
},
Groups = new List<Group>()
{
new Group()
{
ID = 3,
Name = "SubSupport",
ParentGroupID = 2,
Type = "Group",
Users = new List<User>()
{
new User()
{
ID = 4,
Name = "Sub Johnny Support",
GroupID = 3,
Type = "User",
}
},
Groups = null
}
}
},
new Group()
{
ID = 4,
Name = "Production",
ParentGroupID = 1,
Type = "Group",
Users = new List<User>()
{
new User()
{
ID = 5,
Name = "Johnny Production",
GroupID = 4,
Type = "User",
}
},
Groups = null
}
}
}
};
which will ultimately display like this in a treeview:
+Admin (Group)
John Doe (User)
Jane Smith (User)
+Support (Group)
Johnny Support (User)
+SubSupport (Group)
Sub Johnny Support (User)
+Production (Group)
Johnny Production (User)
This is what I've come up with so far to transform the flat data into the model above:
List<Group> model = new List<Group>();
var parentGrouping = elements.GroupBy(x => x.ParentGroupID);
foreach (var parentGroup in parentGrouping)
{
var grouping = parentGroup.GroupBy(y => y.GroupID);
foreach (var group in grouping)
{
Group groupItem = new Group()
{
ID = group.FirstOrDefault().GroupID,
Name = group.FirstOrDefault().GroupName,
ParentGroupID = group.FirstOrDefault().ParentGroupID,
Type = "Group",
Users = new List<User>()
};
foreach (var user in group)
{
groupItem.Users.Add(new User()
{
ID = user.UserID,
Name = user.UserName,
GroupID = user.GroupID,
Type = "User",
});
}
model.Add(groupItem);
}
}
All my groups come out along with their children users but the hierarchy is not preserved. I think I may need to do this recursively but I can't seem to get my head around it. Any help would be greatly appreciated.
Here are the models for the sake of completeness:
public class FlatDataGroup
{
public string Text { get; set; }
public int GroupID { get; set; }
public int ParentGroupID { get; set; }
public string GroupName { get; set; }
public int UserID { get; set; }
public string UserName { get; set; }
}
public class Group
{
public int ID { get; set; }
public int ParentGroupID { get; set; }
public string Name { get; set; }
public List<Group> Groups { get; set; }
public List<User> Users { get; set; }
public string Type { get; set; }
}
public class User
{
public int ID { get; set; }
public int GroupID { get; set; }
public string Name { get; set; }
public string Type { get; set; }
}
I'd do this in 3 passes:
Create all Group classes and populate them with data other than child groups, adding them incrementally to a dictionary mapping ID to Group.
Loop through all the groups in the dictionary and add children to their parents' Groups list of children.
Return a filtered list of all groups with no parent group -- these are the root groups. (I also sorted them by ID to remove the random ordering that the dictionary will introduce.)
Thus:
public static class FlatDataGroupExtensions
{
public const string UserType = "User";
public const string GroupType = "Group";
public static List<Group> ToGroups(this IEnumerable<FlatDataGroup> elements)
{
// Allocate all groups and index by ID.
var groups = new Dictionary<int, Group>();
foreach (var element in elements)
{
Group group;
if (!groups.TryGetValue(element.GroupID, out group))
groups[element.GroupID] = (group = new Group() { ID = element.GroupID, Name = element.GroupName, ParentGroupID = element.ParentGroupID, Type = GroupType });
group.Users.Add(new User() { GroupID = element.GroupID, ID = element.UserID, Name = element.UserName, Type = UserType });
}
// Attach child groups to their parents.
foreach (var group in groups.Values)
{
Group parent;
if (groups.TryGetValue(group.ParentGroupID, out parent) && parent != group) // Second check for safety.
parent.Groups.Add(group);
}
// Return only root groups, sorted by ID.
return groups.Values.Where(g => !groups.ContainsKey(g.ParentGroupID)).OrderBy(g => g.ID).ToList();
}
}
I also modified your Group class a little to automatically allocate the lists:
public class Group
{
List<Group> groups = new List<Group>();
List<User> users = new List<User>();
public int ID { get; set; }
public int ParentGroupID { get; set; }
public string Name { get; set; }
public string Type { get; set; }
public List<Group> Groups { get { return groups; } }
public List<User> Users { get { return users; } }
public override string ToString()
{
return string.Format("Group: ID={0}, Name={1}, Parent ID={2}, #Users={3}, #Groups={4}", ID, Name, ParentGroupID, Users.Count, Groups.Count);
}
}

AutoMapping from a different source collection type

Given the classes below, I need to establish the mapping from Person to PersonDTO. The problem I am having is that PersonDTO's address field mapping did not occur.
public class Address
{
public int Id { get; set; }
public string Value { get; set; }
}
public class RegisteredAddresses
{
public int Id { get; set; }
public List<Address> Addresses { get; set; }
public RegisteredAddresses()
{
this.Addresses = new List<Address>();
}
}
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public List<RegisteredAddresses> AddressGroups { get; set; }
public Person()
{
this.AddressGroups = new List<RegisteredAddresses>();
}
}
public class PersonDTO
{
public string Name { get; set; }
public List<Address> Addresses { get; set; }
public PersonDTO()
{
this.Addresses = new List<Address>();
}
}
class Program
{
static void Main(string[] args)
{
RegisteredAddresses registeredAddresses1 = new RegisteredAddresses();
RegisteredAddresses registeredAddresses2 = new RegisteredAddresses();
Person person = new Person();
registeredAddresses1.Addresses.Add(new Address { Id = 1, Value = "address 1" });
registeredAddresses1.Addresses.Add(new Address { Id = 2, Value = "address 2" });
registeredAddresses1.Addresses.Add(new Address { Id = 3, Value = "address 3" });
registeredAddresses1.Addresses.Add(new Address { Id = 4, Value = "address 4" });
registeredAddresses1.Addresses.Add(new Address { Id = 5, Value = "address 5" });
registeredAddresses2.Addresses.Add(new Address { Id = 1, Value = "address A" });
registeredAddresses2.Addresses.Add(new Address { Id = 2, Value = "address B" });
registeredAddresses2.Addresses.Add(new Address { Id = 3, Value = "address C" });
registeredAddresses2.Addresses.Add(new Address { Id = 4, Value = "address D" });
registeredAddresses2.Addresses.Add(new Address { Id = 5, Value = "address E" });
person.Name = "person 1";
person.AddressGroups.Add(registeredAddresses1);
person.AddressGroups.Add(registeredAddresses2);
Mapper.CreateMap<Person, PersonDTO>();
// after this line....
var personDto = Mapper.Map<Person, PersonDTO>(person);
// .... personDTO.Addresses.Count() is zero; where it should be 10
}
}
You need to tell AutoMapper how to map from the addresses contained in the AddressGroups to the addresses in PersonDTO. Something like:
Mapper.CreateMap<Person, PersonDTO>()
.ForMember(pd => pd.Addresses,
opt => opt.ResolveUsing(p => p.AddressGroups
.SelectMany(ra => ra.Addresses)
)
);

How to Display the Members of a Class

I'm trying to create a wrapper for selecting multiple items from a single array. I get the result at the end of the code below. Not sure what I'm doing wrong.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Tester.cs
{
class Program
{
static void Main(string[] args)
{
var customers = new[]
{
new { CustomerID = 1, FirstName = "Orlando", LastName = "Gee",
CompanyName = "A Bike Store" },
new { CustomerID = 2, FirstName = "Keith", LastName = "Harris",
CompanyName = "Bike World" },
new { CustomerID = 3, FirstName = "Donna", LastName = "Carreras",
CompanyName = "A Bike Store" },
new { CustomerID = 4, FirstName = "Janet", LastName = "Gates",
CompanyName = "Fitness Hotel" },
new { CustomerID = 5, FirstName = "Lucy", LastName = "Harrington",
CompanyName = "Grand Industries" },
new { CustomerID = 6, FirstName = "David", LastName = "Liu",
CompanyName = "Bike World" },
new { CustomerID = 7, FirstName = "Donald", LastName = "Blanton",
CompanyName = "Grand Industries" },
new { CustomerID = 8, FirstName = "Jackie", LastName = "Blackwell",
CompanyName = "Fitness Hotel" },
new { CustomerID = 9, FirstName = "Elsa", LastName = "Leavitt",
CompanyName = "Grand Industries" },
new { CustomerID = 10, FirstName = "Eric", LastName = "Lang",
CompanyName = "Distant Inn" }
};
var addresses = new[] {
new { CompanyName = "A Bike Store", City = "New York", Country = "United States"},
new { CompanyName = "Bike World", City = "Chicago", Country = "United States"},
new { CompanyName = "Fitness Hotel", City = "Ottawa", Country = "Canada"},
new { CompanyName = "Grand Industries", City = "London", Country = "United Kingdom"},
new { CompanyName = "Distant Inn", City = "Tetbury", Country = "United Kingdom"}
};
IEnumerable<Names> customerfullName = customers.Select(data => new Names {
FirstName = data.FirstName,
LastName = data.LastName});
foreach (Names entry in customerfullName)
{
Console.WriteLine(entry);
}
}
}
class Names
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
}
Tester.cs.Names is what i get repeated when I run the program.
Console.WriteLine uses the ToString method of the object class. By default, that displays the name of the class.
This method is overridden by classes derived from object to display whatever they want. You have not overridden it, so you get the default.
You can reproduce your problem, without LINQ, as follows:
class Names
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
var name = new Names {FirstName = "John", LastName = "Saunders"};
Console.WriteLine(name); // Will display "Tester.cs.Names"
default the ToString will be used, use:
class Names
{
public string FirstName { get; set; }
public string LastName { get; set; }
public override string ToString()
{
return string.Format("{0} {1}", FirstName, LastName);
}
}
It's also possible to create an extra property for the fullname
class Names
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string FullName
{
get
{
return string.Format("{0} {1}", FirstName, LastName);
}
}
}
usage:
foreach (Names entry in customerfullName)
{
Console.WriteLine(entry.FullName);
}
Your Names class has not overridden the ToString method, so it is using the default implementation from object and printing out it's type name. You either need to override ToString in Names to print out the underlying strings, or you need to print out the individual string properties in your foreach loop.

Categories