see my class structure first.
public class CustomerBase
{
public int CustomerID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Address1 { get; set; }
public string Address2 { get; set; }
public string Phone { get; set; }
public string Fax { get; set; }
}
public class Customer : CustomerBase
{
public virtual List<Addresses> Addresses { get; set; }
}
public class Addresses
{
[Key]
public int AddressID { get; set; }
public string Address1 { get; set; }
public string Address2 { get; set; }
public bool IsDefault { get; set; }
public virtual List<Contacts> Contacts { get; set; }
public int CustomerID { get; set; }
public virtual Customer Customer { get; set; }
}
public class Contacts
{
[Key]
public int ContactID { get; set; }
public string Phone { get; set; }
public string Fax { get; set; }
public bool IsDefault { get; set; }
public int AddressID { get; set; }
public virtual Addresses Customer { get; set; }
}
public class TestDBContext : DbContext
{
public TestDBContext()
: base("name=TestDBContext")
{
}
public DbSet<Customer> Customer { get; set; }
public DbSet<Addresses> Addresses { get; set; }
public DbSet<Contacts> Contacts { get; set; }
}
now this way i am trying to populate my customer base but getting error.
var bsCustomer1 = (from c in db.Customer
where (c.CustomerID == 2)
select new
{
CustomerID = c.CustomerID,
FirstName = c.FirstName,
LastName = c.LastName,
Addresses = (from ad in c.Addresses
where (ad.IsDefault == true)
from cts in ad.Contacts
where (cts != null && cts.IsDefault == true)
select ad).ToList(),
}).ToList()
.Select(x => new CustomerBase
{
CustomerID = x.CustomerID,
FirstName = x.FirstName,
LastName = x.LastName,
Address1 = x.Addresses.Select(a => a.Address1).SingleOrDefault(),
Address2 = x.Addresses.Select(a => a.Address2).SingleOrDefault(),
Phone = x.Addresses.Select(c => c.Contacts.Select(cd => cd.Phone).SingleOrDefault()),
Fax = x.Addresses.Select(c => c.Contacts.Select(cd => cd.Fax).SingleOrDefault())
}).ToList();
as per my situation a single customer may have multiple address but there should be one default one which i am pulling. a single address may have multiple contacts details but there should be one default one which i am pulling.
address1,address2, Phone and Fax are in base customer class. i want to pull single data from address and contacts tables based on isdefault is true and populate my customer. i am not very good in linq. so not being able to compose the query. please help me to compose it. thanks
Try the code below, guess it may fit about your request.
var bsCustomer1 = db.Customer.Where(p => p.CustomerID == 2)
.Select(x => new CustomerBase
{
CustomerID = x.CustomerID,
FirstName = x.FirstName,
LastName = x.LastName,
Address1 = x.Addresses.First(a => a.IsDefault).Address1,
Address2 = x.Addresses.First(a => a.IsDefault).Address2,
Phone = x.Addresses.First(a => a.IsDefault).Contacts.First(c => c.IsDefault).Phone),
Fax = x.Addresses.First(a => a.IsDefault).Contacts.First(c => c.IsDefault).Fax)
}).ToList();
Without knowing your actual meaning when you say: "i want to pull single data from address and contacts tables based on isdefault is true and populate my customer" that could mean two things:
I want to project a new object
I want to UPDATE the backing database.
Okay a few things about EF:
You have a context for CRUD (Create, Retrieve, Update, Delete) statements to the database.
The Context knows all the objects you identified in the database when setting up the EF file.
The t4 templates are created for entity context and the entity name itself and generate the context reference in the previous steps as well as create POCO class objects.
To create NEW objects you don't have to reference the object above it or below it. You just need to create it and then update the database with it.
So for an example of EF let's say I have two database tables:
I have a table tePerson that has fields: PersonId, FirstName, LastName, OrderId. This table has values
1 Brett X 1
2 Emily X 2
4 Ryan Y 1
10 Mark Z 1
OrderId is a foreign Key to a table teOrder with only has two fields: OrderId and Description.
1 Shirt
2 Dress
And my POCO objects generated from the T4 are:
public partial class tePerson
{
public int PersonId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public Nullable<int> OrderId { get; set; }
public virtual teOrder teOrder { get; set; }
}
It is important to note that that 'virtual teOrder' points to another POCO for me like this:
public partial class teOrder
{
public teOrder()
{
this.tePersons = new HashSet<tePerson>();
}
public int OrderId { get; set; }
public string Description { get; set; }
public virtual ICollection<tePerson> tePersons { get; set; }
}
Example for just projecting and for updating the database from the context as well as updating the database below. The key thing to remember is that objects when doing 'selects' with EF are not realized till you do a method like 'ToList()' to make them concrete. Else they are context db set which you cannot chain off of.
public class OtherPerson
{
public int PersonId { get; set; }
public string PersonLongName { get; set; }
public teOrder Order { get; set; }
}
static void Main(string[] args)
{
using (var context = new TesterEntities())
{
//Say I just want to project a new object with a select starting from orders and then traversing up. Not too hard
var newObjects = context.teOrders.Where(order => order.OrderId == 1)
//SelectMan will FLATTEN a list off of a parent or child in a one to many relationship
.SelectMany(peopleInOrderOne => peopleInOrderOne.tePersons)
.ToList()
.Select(existingPerson => new OtherPerson
{
PersonId = existingPerson.PersonId,
PersonLongName = $"{existingPerson.FirstName} {existingPerson.LastName}",
Order = existingPerson.teOrder
})
.ToList();
newObjects.ForEach(newPerson => Console.WriteLine($"{newPerson.PersonId} {newPerson.PersonLongName} {newPerson.Order.Description}"));
// Just an action clause to repeat find items in my context, the important thing to note is that y extends teOrder which is another POCO inside my POCO
Action<string, List<tePerson>> GetOrdersForPeople = (header, people) =>
{
Console.WriteLine(header);
people.ForEach(person => Console.WriteLine($"{person.FirstName} {person.LastName} {person.teOrder.Description}"));
Console.WriteLine();
};
//I want to look at a person and their orders. I don't have to do multiple selects down, lazy loading by default gives me a child object off of EF
GetOrdersForPeople("First Run", context.tePersons.ToList());
//Say I want a new order for a set of persons in my list?
var newOrder = new teOrder { Description = "Shoes" };
context.teOrders.Add(newOrder);
context.SaveChanges();
//Now I want to add the new order
context.tePersons.SingleOrDefault(person => person.PersonId == 1).teOrder = newOrder;
context.SaveChanges();
//I want to rexamine now
GetOrdersForPeople("After changes", context.tePersons.ToList());
//My newOrder is in memory and I can alter it like clay still and the database will know if I change the context
newOrder.Description = "Athletic Shoes";
context.SaveChanges();
GetOrdersForPeople("After changes 2", context.tePersons.ToList());
//Say I want to update a few people with new orders at the same time
var peopleBesidesFirst = context.tePersons.Where(person => person.PersonId != 1).ToList();
var firstPersonInList = context.tePersons.Where(person => person.PersonId == 1).ToList();
var newOrders = new List<teOrder> {
new teOrder { Description = "Hat", tePersons = peopleBesidesFirst },
new teOrder { Description = "Tie", tePersons = firstPersonInList }
};
context.teOrders.AddRange(newOrders);
context.SaveChanges();
GetOrdersForPeople("After changes 3", context.tePersons.ToList());
}
Console.ReadLine();
}
Related
I got 3 models: Human, Skill and HumanSkill. There is a many to many relationship between Human and Skill, the HumanSkill is the intermediary table between them.
My query to the database loads the collection of the intermediary table HumanSkill correctly, but does not load the reference navigation property Skill through which I want to load the Skill name (Human -> HumanSkill -> Skill -> Skill.name) using a query projection with select.
public IActionResult Preview(int humanId)
{
var currentHuman = this.db.Humans
.Where(x => x.Id == humanId)
.Select(r => new HumanPreviewViewModel
{
PrimaryData = r.PrimaryData,
// How should I write this line?
Skills = r.Skills.ToList(),
}).SingleOrDefault();
return View(currentResume);
}
Human model:
public class Human
{
public Human()
{
this.Skills = new HashSet<HumanSkill>();
}
public int Id { get; set; }
public virtual PrimaryData PrimaryData { get; set; }
public virtual ICollection<HumanSkill> Skills { get; set; }
}
HumanSkill model:
public class HumanSkill
{
public int Id { get; set; }
public int HumanId { get; set; }
public Human Human { get; set; }
public int SkillId { get; set; }
public Skill Skill { get; set; }
}
Skill model:
public class Skill
{
public Skill()
{
this.Humans = new HashSet<HumanSkill>();
}
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<HumanSkill> Humans { get; set; }
}
HumanPreviewViewModel:
public class HumanPreviewViewModel
{
public HumanPreviewViewModel()
{
}
public PrimaryData PrimaryData { get; set; }
public List<HumanSkill> Skills { get; set; }
}
}
How can I achieve this without using include?
If you use some data from Skills table in the Select, EF will perform the necessary joins to retrieve the data
var currentHuman = this.db.Humans
.Where(x => x.Id == humanId)
.Select(r => new HumanPreviewViewModel
{
PrimaryData = r.PrimaryData,
SkillNames = r.Skills.Select(hs => hs.Skill.Name).ToList(),
}).SingleOrDefault();
When projecting from entity to a view model, avoid mixing them. For example, do not have a view model contain a reference or set of entities. While it might not seem necessary, if you want a list of the skills with their ID and name in the HumanPreviewViewModel then create a serialize-able view model for the skill as well as the PrimaryData if that is another related entity. Where PrimaryData might be a one-to-one or a many-to-one the desired properties from this relation can be "flattened" into the view model.
[Serializable]
public class HumanPreviewViewModel
{
public Id { get; set; }
public string DataPoint1 { get; set; }
public string DataPoint2 { get; set; }
public List<SkillViewModel> Skills { get; set; }
}
[Serializable]
public class SkillViewModel
{
public int Id { get; set; }
public string Name { get; set; }
}
Then when you go to extract your Humans:
var currentHuman = this.db.Humans
.Where(x => x.Id == humanId)
.Select(r => new HumanPreviewViewModel
{
Id = r.Id,
DataPoint1 = r.PrimaryData.DataPoint1,
DataPoint2 = r.PrimaryData.DataPoint2,
Skills = r.Skills.Select(s => new SkillViewModel
{
Id = s.Skill.Id,
Name = s.Skill.Name
}).ToList()
}).SingleOrDefault();
The reason you don't mix view models and entities even if they share all of the desired fields is that your entities will typically contain references to more entities. When something like your view model gets sent to a Serializer such as to send to a client from an API or due to a page calling something an innocent looking as:
var model = #Html.Raw(Json.Encode(Model));
then the serializer can, and will touch navigation properties in your referenced entities which will trigger numerous lazy load calls.
I'm trying to figure out the best way to define a one to many relationship table as it pertains to Customers and Addresses. Each Customer can have multiple address (Mailing, Billing, Delivery, etc). The Type of address is stored in a separate table (AddressType).
Here's what I have:
public class Company
{
public int Id { get; set; }
public string Name { get; set; }
public List<Address> Addresses { get; set; }
}
public class Address
{
public int Id { get; set; }
public int AddressTypeId { get; set; }
public AddressType AddressType { get; set; }
public string Street1 { get; set; }
public string Street2 { get; set; }
public string City { get; set; }
public int StateId { get; set; }
public State State { get; set; }
public string PostalCode { get; set; }
public int CountryId { get; set; }
public Country Country { get; set; }
public bool Active { get; set; }
public int CompanyId { get; set; }
}
public class AddressType
{
public int Id { get; set; }
public string Display { get; set; }
public bool Active { get; set; }
}
Couple of questions ...
Would what I have above be considered good practice? If not, how would you define it?
Assuming that the AddressType table contains Mailing, Billing and Delivery, how would I issue a Linq query where I only want to pull the Mailing Address?
Thanks a bunch.
--- Val
I'd suggest a 'base' address class. Mailing, Billing, Delivery etc. would inherit from this base class.
public class Address
{
public string Street { get; set; }
public int HouseNumber { get; set; }
public string HouseNumberAddition { get; set; }
public string City { get; set; }
public string Country { get; set; }
}
In case of delivery, you might want to print a delivery note with the address label for the delivery driver. But is does not make sense to include a DeliveryNote in the base address class, because when would a billing address need a delivery note?
So you inherit from your base Address class to create specific address types.
For example:
public class DeliveryAddress : Address
{
public string DeliveryNote { get; set; } = "Please don't ring the bell after 10pm."
}
Assuming you use EF Code First, Entity framework creates an Address table, with a discriminator column. When you save a new address, the discriminator column defines the type of address.
var googleCompany = new Company
{
DeliveryAddress = new DeliveryAddress
{
Street = "Google Street",
HouseNumber = 1,
DeliveryNote = "Watch out for the dog."
},
CompanyAddress = new CompanyAddress()
};
var microsoftCompany = new Company
{
DeliveryAddress = new DeliveryAddress
{
Street = "Microsoft Street",
HouseNumber = 2,
DeliveryNote = "Don't bring an Apple device on the premise."
},
CompanyAddress = new CompanyAddress()
};
_context.Companies.Add(googleCompany);
_context.Companies.Add(microsoftCompany);
_context.SaveChanges();
Now to query the companies and specify the type of address you need, you just need to make a call to include and let EF Core include the address.
var companiesWithBothDeliveryAddress =
_context.Companies.Include(x => x.CompanyAddress)
.Include(x => x.DeliveryAddress).ToList();
var companiesWithOnlyDeliveryAddress =
_context.Companies.Include(x => x.DeliveryAddress).ToList();
The EF Fluent API configuration should be something like this:
protected override void OnModelCreating(ModelBuilder builder)
{
// Company to CompanyAddress, without inverse property on CompanyAddress.
builder.Entity<Company>()
.HasOne(x => x.CompanyAddress)
.WithOne()
.HasForeignKey<Company>(x => x.CompanyAddressId)
.IsRequired(false)
.OnDelete(DeleteBehavior.NoAction);
// Company to DeliveryAddress, without inverse property on DeliveryAddress.
builder.Entity<Company>()
.HasOne(x => x.DeliveryAddress)
.WithOne()
.HasForeignKey<Company>(x => x.DeliveryAddressId)
.IsRequired(false)
.OnDelete(DeleteBehavior.NoAction);
// We let all the Address types share the 'CompanyId' column,
// otherwise, EF would create a seperate CompanyId column for all of them.
builder.Entity<Address>()
.Property(x => x.CompanyId)
.HasColumnName(nameof(Address.CompanyId));
builder.Entity<CompanyAddress>()
.Property(x => x.CompanyId)
.HasColumnName(nameof(Address.CompanyId));
builder.Entity<DeliveryAddress>()
.Property(x => x.CompanyId)
.HasColumnName(nameof(Address.CompanyId));
base.OnModelCreating(builder);
}
public DbSet<Address> Addresses { get; set; }
public DbSet<DeliveryAddress> DeliveryAddresses { get; set; }
public DbSet<CompanyAddress> CompanyAddresses { get; set; }
The result would look like this:
Address Table (I left out some columns for conciseness)
Companies Table (I left out some columns for conciseness)
I'm trying to gather all the information of a specific customer from my database, but i'm not sure how to gather all the information in an optimized query, without querying the database several times.
I've got the following tables: https://i.imgur.com/o9PRrF1.png
What I want is to match 1 customer with the provided CustomerId. Then gather all the cards, accounts, loans and permenentorders related to that account.
I've managed to do it but using several queries to my _context. I want to learn how to do optimized queries when joining multiple tables.
Could someone more experienced with Linq provide an example of a query to gather all the cards, accounts, loans and permenentorders related to a customer with the CustomerId of "1"?
I would be very grateful to get some tips and help with this, knowing how to do optimized queries is a very essential skill to have. Thanks a lot! :-)
Example of what i've tried myself:
model.Customer = await _context.Customers.SingleOrDefaultAsync(c => c.CustomerId == request.CustomerId);
model.Accounts = await (from acc in _context.Accounts
join disp in _context.Dispositions on acc.AccountId equals disp.AccountId
where disp.CustomerId == request.CustomerId
select acc).ToListAsync();
model.Cards = await (from card in _context.Cards
join disp in _context.Dispositions on card.DispositionId equals disp.DispositionId
where disp.CustomerId == request.CustomerId
select card).ToListAsync();
Here's my viewmodel that i'm trying to fill with data:
public class GetCustomerDetailsViewmodel
{
public Customer Customer { get; set; }
public List<Account> Accounts { get; set; } = new List<Account>();
public decimal TotalBalance { get; set; }
public List<Card> Cards { get; set; } = new List<Card>();
public List<PermenentOrder> PermantentOrders { get; set; } = new List<PermenentOrder>();
public List<Loan> Loans { get; set; } = new List<Loan>();
}
Customers have a list of Dispositions, the link table between Customers - Accounts and Cards.
**Customers**
PK CustomerId
public virtual ICollection<Disposition> Dispositions { get; set; }
**Cards**:
PK public int CardId { get; set; }
FK public int DispositionId { get; set; }
public virtual Disposition Disposition { get; set; }
**Dispositions**:
PK public int DispositionId { get; set; }
FK public int CustomerId { get; set; }
public int AccountId { get; set; }
public virtual Account Account { get; set; }
public virtual Customer Customer { get; set; }
public virtual ICollection<Card> Cards { get; set; }
**Accounts**:
PK public int AccountId { get; set; }
public virtual ICollection<Disposition> Dispositions { get; set; }
public virtual ICollection<Loan> Loans { get; set; }
public virtual ICollection<PermenentOrder> PermenentOrder { get; set; }
public virtual ICollection<Transaction> Transactions { get; set; }
**Loans**
PK public int LoanId { get; set; }
public virtual Account Account { get; set; }
**PermenentOrder**
PK public int OrderId { get; set; }
FK public int AccountId { get; set; }
public virtual Account Account { get; set; }
I think you can use this:
model.Customer = await _context.Customers
.Include(x => x.Dispositions)
.ThenInclude(x => x.Cards)
// and other Entities you need, use Include or if entities are in Accounts
// or Cards can use ThenInclude
.SingleOrDefaultAsync(c => c.CustomerId == request.CustomerId);
Simplest query will be to query separately instead of cramming into single linq statement as the statement grows, linq becomes more and more inefficient.
With Lazy loading,
You can do something simple like,
var model = new GetCustomerDetailsViewmodel();
model.Customer = context.Customers.SingleOrDefault(c => c.CustomerId == id);
if (model.Customer != null)
{
model.Accounts = model.Customer.Dispositions.Select(x => x.Account).ToList();
model.Cards = model.Customer.Dispositions.SelectMany(x => x.Cards).ToList();
model.PermantentOrders = model.Accounts.SelectMany(x => x.PermenentOrder).ToList();
}
Without Lazy Loading,
You need to load everything in the single query, Beware this might not be the efficient query. Linq is never about efficiency, but convenience and ease of writing.
var customerProfile = context.Customers.Where(x => x.CustomerId == id).Select(x => new
{
Customer = x,
Accounts = x.Dispositions.Select(d => d.Account),
Cards = x.Dispositions.SelectMany(d => d.Cards).ToList(),
PermanentOrders = x.Dispositions.SelectMany(d => d.Account.PermenentOrder),
}).FirstOrDefault();
if (customerProfile != null)
{
var model = new GetCustomerDetailsViewmodel();
model.Customer = customerProfile.Customer;
model.Accounts = customerProfile.Accounts.ToList();
model.Cards = customerProfile.Cards.ToList();
model.PermantentOrders = customerProfile.PermanentOrders.ToList();
}
I have two objects with many to many relationship:
public class Order
{
public int OrderID { get; set; }
public string OrderName { get; set; }
public virtual Collection<Product> Products { get; set; } = new Collection<Product>();
}
public class Product
{
public int ProductID { get; set; }
public string ProductName { get; set; }
public int OrderID { get; set; }
public virtual Collection<Order> Orders { get; set; } = new Collection<Order>();
}
// Mapping table
public class OrderProduct
{
public int OrderId { get; set; }
public Order Order { get; set; }
public int ProductId { get; set; }
public Product Product { get; set; }
}
I would like to add a new Order with a collection of existing Products (my API would have an input of ProductID array) into the database, so I perform like:
private void AddOrder(int[] productIDs)
{
Order newOrder = new Order();
newOrder.Name = "New Order";
// Here I have no clue which would be the correct way...Should I do
// Approach A(fetch each related entity - Product from database then add them into the collection of my new base entity - Order):
productIDs.ToList().Foreach(p =>
{
newOrder.Products.Add(_dbContext.Product.FindById(p))
}
);
_dbContext.Orders.Add(newOrder);
var newOrderID = _dbContext.SaveChanges();
// then fetch each product from database and add my new Order to its collection and save
productIDs.ToList().Foreach(p =>
{
var existedProductFromDb = _dbContext.Product.FindById(p);
existedProductFromDb.Orders.Add(newOrder);
_dbContext.SaveChanges();
}
);
}
Do I really need the Mapping table between Order and Product in Entity Framework Core? Otherwise, what would be the correct way to deal with above scenario?
Your entities do not represent a many-to-many relationship using a joined table. In fact, you don't use the joining table at all.
So, start by fixing your entities:
public class Order
{
public int OrderID {get;set;}
public string OrderName {get;set;}
public ICollection<OrderProduct> Products { get; set; } = new HashSet<OrderProduct>();
}
public class Product
{
public int ProductID {get;set;}
public string ProductName {get;set;}
public ICollection<OrderProduct> Orders { get; set; } = new HashSet<OrderProduct>();
}
A new Order which contains many Products is as simple as a collection of new OrderProducts:
private void AddOrder(int[] productIDs)
{
Order newOrder = new Order();
newOrder.Name = "New Order";
foreach (int productId in productIDs)
{
newOrder.Products.Add(new OrderProduct { ProductId = productId });
}
_dbContext.Orders.Add(newOrder);
_dbContext.SaveChanges();
}
Also, do notice that SaveChanges returns the number of affected rows in the database, not the Id of an inserted item.
I am trying to update a record and its child at the same time. When I create the object from the database the child property is null (the property is a generic list).
I want to update the class and also update the child class without creating duplicated records in the system.
Here is how I generate the object:
var r = db.SupplierAs.Where(o => o.id == 1).First();
The SupplierA class has a property List. Using the above line of code this comes back null. I have been trying work out the code to initialize this property so I can update it but I am having no joy.
This is the original item I created:
db.Products.Add(new Product
{
name = "product test",
supplierA = new SupplierA
{
name = "supA",
price = 1.99m,
sku = "abc123",
otherCurrencies = new List<Currency>
{
new Currency
{
eur = 2.99m,
usd = 3.99m
}
}
},
});
db.SaveChanges();
I can update the supplier on its own easily like so:
var r = db.SupplierAs.Where(o => o.id == 1).First();
r.name = "Updated name";
db.SupplierAs.Attach(r);
db.Entry(r).State = EntityState.Modified;
db.SaveChanges();
But I cannot figure out how to generate the Currency object list as part of the SupplierAs object. Currencies doesnt seem to be in the db context.
Here are the class files:
public class Product
{
public int id { get; set; }
public string name { get; set; }
public virtual SupplierA supplierA { get; set; }
}
public class SupplierA
{
public int id { get; set; }
public string name { get; set; }
public string sku { get; set; }
public decimal price { get; set; }
public List<Currency> Currencies { get; set; }
}
public class Currency
{
public int id { get; set; }
public decimal eur { get; set; }
public decimal usd { get; set; }
}
The idea of products, suppliers and currencies doesn't make the greatest sense I know, I have extracted logic from my app in example, hopefully it makes enough sense what I am trying to achieve.