Issue to compose a nested relation query with Entity Framework - c#

my relation is hierarchical like customer -> address -> contacts
a single customer may have multiple address and a single address may have multiple contacts. see my class structures.
public class CustomerBase
{
public int CustomerID { get; set; }
public string FirstName { get; set; }
public string LastName { 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 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; }
}
this way i am population data in db table with EF code first.
using (var db = new TestDBContext())
{
var customer = new Customer
{
FirstName = "Test Customer2",
LastName = "Test Customer2",
Addresses = new List<Addresses>
{
new Addresses
{
Address1 = "foo1",
Address2 = "foo2",
IsDefault=true,
Contacts = new List<Contacts>
{
new Contacts { Phone = "22222222", Fax = "1-999999999" }
}
}
}
};
db.Customer.Add(customer);
db.SaveChanges();
now i want to query data. suppose i want to fetch customer whose customerid is 1 and want to load address related to customer id is 1 and address Isdefault is true and default address related contacts details.
i try to compose it this way hence could not complete because i am in EF and LINQ query.
var bsCustomer = db.Customer.Where(c => c.CustomerID == 2).Include(a=>
a.Addresses.Where(a=> a.IsDefault==true)).Include(c=> c.)
so please tell me what will be the query as a result customer id 1 related address will load whose isdefault would be true and address related contact will be loaded. thanks

You can try as shown below.
Query based syntax :
var dbquery = from cu in db.Customers
where (cu.CustomerID == 1)
select new {
cu,
Addresses= from ad in cu.Addresses
where (ad.IsDefault == true)
from ct in ad.Contacts
select ad,
};
You can iterate it as you wish :
var customers = dbquery.AsEnumerable()
.Select(c => c.cu);
foreach(var customer in customers )
{
foreach(var address in customer.Addresses)
{
//your code;
}
}
Method based syntax :
var dbquery = db.Customers.Where(cu=>cu.CustomerID == 1)
.Select(cus=> new {
cus,
Addresses= cus.Addresses.Where(ad=>ad.IsDefault == true).Include(c=>c.Contacts)
}).AsEnumerable()
.Select(f => f.cus).ToList();

Related

EF Core 5 single property projection

I want to reduce duplicated code. In order to achieve that I want to reference the projections of my Entities.
Entities
public class Category
{
public string Id { get; set; }
public string CategoryName { get; set; }
public static Expression<Func<Category, Category>> Proj() => c => new Category
{
CategoryName = c.CategoryName
};
}
public class Image
{
public string Id { get; set; }
public string Url { get; set; }
public static Expression<Func<Image, Image>> Proj() => i => new Image
{
Url = i.Url
};
}
public class Product
{
public string Id { get; set; }
public string Name { get; set; }
public ICollection<Image> Images { get; set; }
public Category Category { get; set; }
}
Projection Query
var categoryProjection = Category.Proj().Compile();
var products = _ctx.Products.Select(p => new Product
{
Id = p.Id,
Name = p.Name,
Images = p.Images.AsQueryable().Select(Image.Proj()).ToHashSet(),
Category = categoryProjection.Invoke(p.Category)
});
When I execute the projection then it will work correctly for Product and Images. But for Category the genereted SQL will contain all Columns (Id and CategoryName).

How to order by with related entity properties in EF core

Hello I want to sort my end result using related entity property which is in this case Locality. I got the keyword from client end as a string that includes column name and sort direction eg. "locality=asc" but when I do orderby with any parent entity properties it run fine however, the property with related entity gives me an error by saying that customer object does not have any locality property
here is my both class customer and Address
public class Customer : IEntity
{
public Guid Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public string Mobile { get; set; }
public Guid UserId { get; set; }
public DateTime DateCreated { get; set; }
public DateTime LastUpdated { get; set; }
[ForeignKey("Address")]
public Guid AddressId { get; set; }
public virtual Address Address { get; set; }
}
public class Address: IEntity
{
public Guid Id { get; set; }
public string Lat { get; set; }
public string Lon { get; set; }
public string Address1 { get; set; }
public string Address2 { get; set; }
public string Locality { get; set; }
}
Here I am trying to sort it with Address property like locality
int skip = (pageNum - 1) * pageSize;
if (skip < 0)
{
skip = 0;
}
searchTerm = searchTerm.ToLower();
var query = _context.Customers.Include(q => q.Address)
.Where(c => c.FirstName.ToLower().Contains(searchTerm)
|| c.LastName.ToLower().Contains(searchTerm)
|| c.Email.ToLower().Contains(searchTerm)
|| c.Mobile.ToLower().Contains(searchTerm));
//var sortOrderSplit = sortOrder.Split('=');
if (sortOrderSplit[0].ToLower() != "locality")
{
query = query.OrderByField("Email", "asc");
}
{
query = query.OrderByField("locality", "asc"); //that gives me an error because type is Address not Customer
}
var customers = query
.Skip(skip)
.Take(pageSize)
.ToList();
u want order by Locality ASC,right?
I think Class type of query is IEnumerable,so you can use lumbda expression.
because Locality is in Address Class,should follow the flow Customer => Address => Locality,not only search property Locality.
if (sortOrderSplit[0].ToLower() != "locality")
{
query = query.OrderBy(o => o.Email);
}
else
{
query = query.OrderBy(o => o.Address.Locality);
}
If your two entity classes have One-to-One relationship, you must add
public Customer Customer { get; set; }
into your Address class too.
Do this and try again.

AP.NET webforms using LINQ to SQL datasource and databind with foreign keys across multiple tables

I'm attempting to build an asp.net webforms application to serve as the front end of an sql database.
see database table relations in this image
Simply getting table values is not a problem.
For example displaying values from 'Item' table like this:
var items = from i in db.Items
select new
{
ID = i.Item_ID,
Name = i.Name,
Barcode = i.BarCode,
Description = i.Description,
ItemType = i.ItemType1.ItemTypeName,
LocationCount = i.Location_Item_Juncs.Count
};
GridView1.DataSource = items;
GridView1.DataBind();
looks like this in webforms webpage
Problem is getting something like supplier information for an item.
An item can have multiple 'Supplier', 'Location' and 'ReceivedDate'!
In SQL I can query that information like this:
select Supplier.Name, Supplier.Adress, Supplier.Email, Supplier.Phone, Supplier.Supplier_Zipcode
from item, Supp_Company, Supplier
where Item_ID = 8 and Item_ID = ItemSub_ID and SupplierJunc_ID = Supplier_ID
results look like this in linqpad
These are the suppliers' information for an item with item id of 8.
Notice, there are 3 tables involved in the query (Item, Supp_Company, Supplier) and 2 pairs of values must match to select valid values.
I want to replicate that query in LINQ to use in my web forms app.
I believe the solution to this problem will apply to getting locations and 'received dates' for an item as well.
Is it possible to use a similar 'where' clause in LINQ as I can in SQL? What would the syntax look like?
sure you can, it all depends on the way you build your mappings though.
you've got a many to many scenario in here and you can
map entities relying on link table
map entities relying on link entity
link table approach (notice the modelBuilder HasMany WithMany)
void Main()
{
using (var context = new YourContext())
{
var query = from item in context.Items
from supplier in item.Suppliers
where item.ItemId == 8
select new
{
Name = supplier.Name,
Adress = supplier.Address,
Email = supplier.Email,
Phone = supplier.Phone,
Zip = supplier.Zip,
};
//...
}
}
public class YourContext : DbContext
{
public DbSet<Item> Items { get; set; }
public DbSet<Supplier> Suppliers { get; set; }
public YourContext() : base("MyDb")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Item>()
.HasMany(item => item.Suppliers)
.WithMany(supplier => supplier.Items)
.Map(m =>
{
m.MapLeftKey("ItemSub_ID");
m.MapRightKey("SupplierJunc_ID");
m.ToTable("Supp_Company");
});
}
}
public class Item
{
public int ItemId { get; set; }
public string Name { get; set; }
public ICollection<Supplier> Suppliers { get; set; }
}
public class Supplier
{
public int SupplierId { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public string Email { get; set; }
public string Phone { get; set; }
public string Zip { get; set; }
public ICollection<Item> Items { get; set; }
}
link entity approach (notice the modelBuilder maps every table to an entity)
void Main()
{
using (var context = new YourContext())
{
var query = from item in context.Items
join link in context.SupplierItems
on item.ItemId equals link.ItemId
join supplier in context.Suppliers
on link.SupplierId equals supplier.SupplierId
where item.ItemId == 8
select new
{
Name = supplier.Name,
Adress = supplier.Address,
Email = supplier.Email,
Phone = supplier.Phone,
Zip = supplier.Zip,
};
//...
}
}
public class YourContext : DbContext
{
public DbSet<Item> Items { get; set; }
public DbSet<Supplier> Suppliers { get; set; }
public DbSet<SupplierItem> SupplierItems { get; set; }
public YourContext() : base("MyDb")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Item>()
// ...
;
modelBuilder.Entity<Supplier>()
// ...
;
modelBuilder.Entity<SupplierItem>()
// ...
;
}
}
public class Item
{
public int ItemId { get; set; }
public string Name { get; set; }
}
public class Supplier
{
public int SupplierId { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public string Email { get; set; }
public string Phone { get; set; }
public string Zip { get; set; }
}
public class SupplierItem
{
public int ItemId { get; set; }
public int SupplierId { get; set; }
}

c#. EF entity sql. How to get entity with related objects?

I have made simple model for example.
public class Publisher
{
public int Id { get; set; }
public string Title { get; set; }
public Address Location { get; set; }
public virtual ICollection<Book> Books { get; set; }
}
public class Address
{
public string Country { get; set; }
public string City { get; set; }
public string Street { get; set; }
public string HouseNumber { get; set; }
}
public class Book
{
public int Id { get; set; }
public string Title { get; set; }
public string Author { get; set; }
public int LanguageId { get; set; }
public int? PublisherId { get; set; }
}
I need to get publishers with related books. I know how to do it using linq to entities. Is it possible to solve a problem using entity sql?
public class CatalogContext : DbContext {...}
public List<Publisher> GetByCity(string city)
{
var result = new List<Publisher>();
string queryString;
queryString = String.Format(#"SELECT VALUE row(a,b)
FROM CatalogContext.Publishers AS a
join CatalogContext.Books AS b on a.Id = b.PublisherId
WHERE a.Location.City = '{0}'", city);
var rows = ((IObjectContextAdapter)_context).ObjectContext.CreateQuery<DbDataRecord>(queryString).ToList();
return ???
}
Query returns required data but it's List<DbDataRecord> - list of pairs <publisher, book>. How to translate it to list of publishers with filled navigation property "Books"?
Is it possible to write query which directly returns List<Publisher>?
you can do the following:
var result = ObjectContext.Publishers.Include("Books").Include("Locations")
.Where(c => c.Location.City = "SOME_CITY").Select(c => c);
Include - basically joins the table.
Then you can drill down to books by doing the following:
var test = result[0].Books;
Why are you using direct sql command instead of Entity Framework code style?

Editing/Listing multiple models within one view

I am new to the MVC and EF world. I am targeting MVC 4 EF 5 using code first.
I am looking for the best practice for editing two related models using one view. For simplicity I have the following two Models:
namespace AddressBook.Models
{
public class Contact
{
public int ID { get; set; }
public string First_Name { get; set; }
public string Last_Name { get; set; }
public List<PhoneNumber> PhoneNumbers { get; set; }
}
}
and
namespace AddressBook.Models
{
public class PhoneNumber
{
public int ID { get; set; }
public string Number { get; set; }
public bool Primary { get; set; }
}
}
with the following context:
using System.Data.Entity;
namespace AddressBook.Models
{
public class DataContext : DbContext
{
public DbSet<Contact> Contacts { get; set; }
public DbSet<PhoneNumber> PhoneNumbers { get; set; }
}
}
The relationship between the Contact and PhoneNumber is one to many, However I would like to be able to edit the first_name, last_name and Number when the Primary is set to true, so we would be editing only one phone number per contact record.
I have seen similar posts that point to using a ViewModel but the only examples of viewmodels I have seen are when used instead of the viewbag when passing the information for a dropdown.
I guess I have a few questions:
would the ViewModel look like below?
public class ContactPrimaryNumberViewModel
{
public Contact ContactToEdit {get; set;}
public PhoneNumber PhoneNumberToEdit {get;set;}
}
what would the edit(post) and edit(get) look like?
Any help would be appreciated to help me wrap my head around this ...
here is the Edit(get) modified to support if contact does not have phone number associated
' // GET: /Contact/Edit/5
public ActionResult Edit(int id = 0)
{
ContactPrimaryNumberViewModel ContactPrimaryNumber = (from pn in db.PhoneNumbers
where pn.ContactID == id && pn.Primary == true
select new ContactPrimaryNumberViewModel { ContactID = pn.ContactID, First_Name = pn.Contact.First_Name, Last_Name = pn.Contact.Last_Name, Number = pn.Number }).SingleOrDefault();
if (ContactPrimaryNumber == null)
{
ContactPrimaryNumber = (from c in db.Contacts
where c.ID == id
select new ContactPrimaryNumberViewModel { ContactID = c.ID, First_Name = c.First_Name, Last_Name = c.Last_Name, Number = null }).Single();
}
return View(ContactPrimaryNumber);
}'
so the final solution after everyones help is:
the models:
public class PhoneNumber
{
public int ID { get; set; }
public string Number { get; set; }
public bool Primary { get; set; }
[Required]
public int ContactID { get; set; }
public Contact Contact { get; set; }
}
public class Contact
{
public int ID { get; set; }
public string First_Name { get; set; }
public string Last_Name { get; set; }
public List<PhoneNumber> PhoneNumbers { get; set; }
}
The controler edit(get and post)
// GET: /Contact/Edit/5
public ActionResult Edit(int id = 0)
{
ContactPrimaryNumberViewModel ContactPrimaryNumber = (from c in db.Contacts
join pn in db.PhoneNumbers
on c.ID equals pn.ContactID into outer
from _pn in outer.Where(p => p.Primary ==true).DefaultIfEmpty()
where c.ID == id
select new ContactPrimaryNumberViewModel { ContactID = c.ID, First_Name = c.First_Name, Last_Name = c.Last_Name, Number = ((_pn == null) ? "" : _pn.Number) }).FirstOrDefault();
if (ContactPrimaryNumber == null)
{
return HttpNotFound();
}
return View(ContactPrimaryNumber);
}
// POST: /Contact/Edit/5
[HttpPost]
public ActionResult Edit(ContactPrimaryNumberViewModel ContactPrimaryNumber)
{
Contact c = db.Contacts.Find(ContactPrimaryNumber.ContactID);
PhoneNumber pn = db.PhoneNumbers.FirstOrDefault(x => x.ContactID == ContactPrimaryNumber.ContactID && x.Primary == true);
if (ModelState.IsValid)
{
c.First_Name = ContactPrimaryNumber.First_Name;
c.Last_Name = ContactPrimaryNumber.Last_Name;
if (pn == null) //if there is no phone number associated with the contact in the DB
{
if (!String.IsNullOrEmpty(ContactPrimaryNumber.Number))
{
//Add a new phonenumber in the database
PhoneNumber Px = new PhoneNumber();
Px.ContactID = ContactPrimaryNumber.ContactID;
Px.Number = ContactPrimaryNumber.Number;
Px.Primary = true;
db.PhoneNumbers.Add(Px);
}
}
else //if there is a phone number associated with the contactin the DB
{
if (String.IsNullOrEmpty(ContactPrimaryNumber.Number))
{
//delete the existing number
db.PhoneNumbers.Remove(pn);
}
else
{
//modify the existing number
pn.Number = ContactPrimaryNumber.Number;
}
}
db.SaveChanges();
return RedirectToAction("Index");
}
return View(c);
}
and the viewmodel
public class ContactPrimaryNumberViewModel
{
public int ContactID { get; set; }
public string First_Name { get; set; }
public string Last_Name { get; set; }
public string Number { get; set; }
}
thanks again for your help
I think your view model should look like this:
public class ContactPrimaryNumberViewModel
{
public int ID { get; set; }
public string First_Name { get; set; }
public string Last_Name { get; set; }
public string Number { get; set; }
}
And your update would look something like:
Contact c = context.Contacts.Find(id);
PhoneNumber p = context.PhoneNumbers
.FirstOrDefault(x => x.id == id && x.Primary == true);
//validate input
//update as necessary
//SaveChanges() etc...
from your comment - you new up the model class ContactPrimaryNumberViewModel:
var ContactPrimaryNumber =
from pn in db.PhoneNumbers
where pn.ContactID == id && pn.Primary == true
select new ContactPrimaryNumberViewModel() {
ContactID = pn.ContactID,
First_Name = pn.Contact.First_Name,
Last_Name = pn.Contact.Last_Name,
Number = pn.Number
};
Okay, try this:
PhoneNumber
public class PhoneNumber
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Number { get; set; }
public bool Primary { get; set; }
[ForeignKey("Contact"), DatabaseGenerated(DatabaseGeneratedOption.None)]
public int? ContactId { get; set; }
public virtual Contact Contact { get; set; }
}
Contact
public class Contact
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ContactId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public List<PhoneNumber> PhoneNumbers { get; set; }
}
SaveContact: depending on how you have set up your Repositories or Ef class. This can either go in your EfRepository implemention or your EfDb class.
public void SavePlayer(Contact contact)
{
using (var context = new EfDb())
{
if (contact.ContactId == 0)
{
context.Contacts.Add(contact);
}
else if (contact.ContactId > 0)
{
var currentContact = context.Contacts
.Include(c => c.PhoneNumber)
.Single(c => c.ContactId== contact.ContactId);
context.Entry(currentContact).CurrentValues.SetValues(contact);
currentContact.PhoneNumber= contact.PhoneNumber;
}
context.SaveChanges();
}
}
Edit action
[HttpGet]
public ActionResult Edit(int id)
{
var contact= _dataSource.Contacts.FirstOrDefault(c => c.Id == id);
return View(player);
}
[HttpPost]
public ActionResult Edit(Contact contact)
{
try
{
if (ModelState.IsValid)
{
_dataSource.SaveContact(contact);
return RedirectToAction("About", "Home");
}
}
catch (Exception)
{
ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator.");
}
return View(contact);
}
Views
In your Contact View Folder add EditorTemplates folder. Then Scaffold a Create Strongly Typed PhoneNumber Partial View to this folder and name it PhoneNumber like its model.
Scaffold a Create Strongly Typed Contact View name it Create
Then add #Html.EditorFor(model => model.PhoneNumber) to the master Create View.

Categories

Resources