C# LINQ single query to complete an incomplete model - c#

I have a list of incomplete product models. Everyone is missing an owner and a price.
Can these deficiencies be filled with a single query to context? Without this foreach loop?
foreach(var item in products)
{
item.Owner = context.Products.Where(x => x.Id == item.Id).Select(x => x.ProductOwner).FirstOrDefault();
item.Price = context.Products.Where(x => x.Id == item.Id).Select(x => x.ProductPrice).FirstOrDefault();
}
I would like one query to fill in the missing fields in IEnumerable products

// build list of Id for which we need data
var idsToUpdate = products.Select(o => o.Id).ToList();
var dataById = Context.Products
// get matching entries (server side)
.Where(x => idsToUpdate.Contains(x.Id))
// get only relevant data
.Select(x => new { x.Id, x.ProductOwner, x.Price })
// ensure uniqueness (server side, free if Id is a PK)
.DistinctBy(x => x.Id)
// we will not update db
.AsNoTracking()
// now client side
.AsEnumerable()
// arrange the data
.ToDictionary(x => x.Id, x => new { x.ProductOwner, x.Price });
foreach (var item in products)
{
if (!dataById.TryGetValue(item.Id, out var data))
continue;
item.ProductOwner = data.ProductOwner;
item.Price = data.Price;
}
If data is not many then try query once, maybe?
Select all the target id
Get all products from DB
Do as you please with data(two lists) you have
ref : Using LINQ, is it possible to output a dynamic object from a Select statement? If so, how?

Since "products" is coming from external service and "context.Products" is from your DB. Why don't you join "context.Products" with "products" and return properties of "products" by applying value for "owner" and "price".
Example
var result = (from p in products
join dbP in context.Products on dbP.Id == p.Id into gj
from subDbP in gj.DefaultIfEmpty()
select new {
Owner = subDbP?.ProductOwner ?? string.Empty,
Price = subDbP?.ProductPrice ?? string.Empty,
Id = p.Id
}).ToList();

This is highly depends on the DataType of products. If this is a List, there is an method available, called ForEach.
If you are using something different, you have to write an extension method somewhere within your code. This can be something like this:
public static class EnumerableExtensions
{
public static void ForEach<T>(this IEnumerable<T> values, Action<T> predicate)
{
foreach(var value in values)
{
predicate(value);
}
}
}
Afterwards, you can use this extension method like LINQ:
products.ForEach(item =>
{
var product = context.Products.Where(x => x.Id == item.Id);
item.Owner = product.Select(x => x.ProductOwner).FirstOrDefault();
item.Price = product.Select(x => x.ProductPrice).FirstOrDefault();
});
Hope this helps :-)

Related

LINQ efficiency

Consider the following LINQ statements:
var model = getModel();
// apptId is passed in, not the order, so get the related order id
var order = (model.getMyData
.Where(x => x.ApptId == apptId)
.Select(y => y.OrderId));
var orderId = 0;
var orderId = order.LastOrDefault();
// see if more than one appt is associated to the order
var apptOrders = (model.getMyData
.Where(x => x.OrderId == orderId)
.Select(y => new { y.OrderId, y.AppointmentsId }));
This code works as expected, but I could not help but think that there is a more efficient way to accomplish the goal ( one call to the db ).
Is there a way to combine the two LINQ statements above into one? For this question please assume I need to use LINQ.
You can use GroupBy method to group all orders by OrderId. After applying LastOrDefault and ToList will give you the same result which you get from above code.
Here is a sample code:
var apptOrders = model.getMyData
.Where(x => x.ApptId == apptId)
.GroupBy(s => s.OrderId)
.LastOrDefault().ToList();
Entity Framework can't translate LastOrDefault, but it can handle Contains with sub-queries, so lookup the OrderId as a query and filter the orders by that:
// apptId is passed in, not the order, so get the related order id
var orderId = model.getMyData
.Where(x => x.ApptId == apptId)
.Select(y => y.OrderId);
// see if more than one appt is associated to the order
var apptOrders = model.getMyData
.Where(a => orderId.Contains(a.OrderId))
.Select(a => a.ApptId);
It seems like this is all you need:
var apptOrders =
model
.getMyData
.Where(x => x.ApptId == apptId)
.Select(y => new { y.OrderId, y.AppointmentsId });

Why EF code is not selecting a single column?

I have used this to pick just a single column from the collection but it doesn't and throws casting error.
ClientsDAL ClientsDAL = new DAL.ClientsDAL();
var clientsCollection= ClientsDAL.GetClientsCollection();
var projectNum = clientsCollection.Where(p => p.ID == edit.Clients_ID).Select(p => p.ProjectNo).ToString();
Method:
public IEnumerable<Clients> GetClientsCollection(string name = "")
{
IEnumerable<Clients> ClientsCollection;
var query = uow.ClientsRepository.GetQueryable().AsQueryable();
if (!string.IsNullOrEmpty(name))
{
query = query.Where(x => x.Name.Contains(name));
}
ClientsCollection = (IEnumerable<Clients>)query;
return ClientsCollection;
}
As DevilSuichiro said in comments you should not cast to IEnumerable<T> just call .AsEnumerable() it will keep laziness.
But in your case it looks like you do not need that at all because First or FirstOrDefault work with IQueryable too.
To get a single field use this code
clientsCollection
.Where(p => p.ID == edit.Clients_ID)
.Select(p => p.ProjectNo)
.First() // if you sure that at least one item exists
Or (more safe)
var projectNum = clientsCollection
.Where(p => p.ID == edit.Clients_ID)
.Select(p => (int?)p.ProjectNo)
.FirstOrDefault();
if (projectNum != null)
{
// you find that number
}
else
{
// there is no item with such edit.Clients_ID
}
Or even simpler with null propagation
var projectNum = clientsCollection
.FirstOrDefault(p => p.ID == edit.Clients_ID)?.ProjectNo;

How to efficiently search records and get parent records with NHibernate QueryOver

I am building a platform where webshops can create their own shop.
Each shop has their own set of Productcategory and relations (CategoryProductX) to Product records. So Product records can have a relation with ProductCategory from different shops.
My tables look like this:
ProductCategory
Id
Name
ShopId
CategoryProductX
Id
CategoryId
ProductId
Product
Id
Name
Mapping of the relation between ProductCategory and Product looks like this:
private IList<CategoryProductX> products = new List<CategoryProductX>();
[Bag(0, Name = "Products", Table = "CategoryProductX", Inverse = true)]
[Key(1, Column = "CategorieId")]
[OneToMany(2, ClassType = typeof(CategoryProductX))]
public virtual IEnumerable<CategoryProductX> Products
{
get { return products; }
protected set { products = (IList<CategoryProductX>)value; }
}
I had previously implemented a search function which search products only within the ProductCategory the user had selected and executed the search on the client entirely. We want to change this however so that it searches through the entire catalogue of the current Shop.
As I don't want to always retrieve the entire catalogue from the database just to be able to execute the search on the client, we have to move this functionality on the server.
The searchresults should be a List<ProductCategoryDTO> containing a property public List<ProductDTO> Products.
I have something that works but I want to make sure this is efficient as it can be, because it will be executed each time the user enters a character in the searchbox.
ProductCategory categoryAlias = null;
CategoryProductX categoryProductAlias = null;
Product productAlias = null;
var query = session.QueryOver<ProductCategory>(() => categoryAlias)
.Where(() => categoryAlias.Shop.Id == shopId)
.JoinQueryOver(() => categoryalias.Products, () => categoryProductAlias)
.JoinQueryOver(() => categoryProductAlias.Product, () => productAlias);
query.Where(Restrictions.Disjunction()
.Add(Restrictions.InsensitiveLike("Name", searchTerm, MatchMode.Anywhere)));
ProductCategoryDTO category = null;
ProductDTO product = null;
var productResults = query.SelectList(list => list
.Select(() => productAlias.Id).WithAlias(() => product.Id)
.Select(() => productAlias.Name).WithAlias(() => product.Name)
.Select(() => categoryProductAlias.CategoryId).WithAlias(() => product.CategoryId).TransformUsing(Transformers.AliasToBean<ProductDTO>())
.List<ProductDTO>();
var categories = query
.Where(c => c.Id.IsIn(productResults.Select(p => p.CategoryId).ToArray()))
.SelectList(list => list
.Select(() => categoryAlias.Id).WithAlias(() => category.Id)
.Select(() => categoryAlias.Name).WithAlias(() => category.Name)
)
.TransformUsing(Tranformers.AliasToBean<CategoryDTO>())
.List<CategoryDTO>().Distinct();
if(categories.Any())
{
foreach(var category in categories)
{
category.Product = productResults.Where(p => p.CategoryId == category.Id).ToList();
}
}
Result = categories;
return Task.FromResult(0);

LINQ: How to Select specific columns using IQueryable()

I need to select only two columns from Hospital table, HospitalId and Name.
i tried the below code it selects all columns from Hospital table which lead to slow performance. Please help me to select only two columns from Hospital table
public HttpResponseMessage GetAvailableHospitalsByAjax(System.Guid? DirectorateOfHealthID = null, System.Guid? UnitTypeID = null, string DeviceTypeIDs = null)
{
Context db = new Context();
var query = db.Hospitals.AsQueryable();
if (UnitTypeID != null)
{
query = query.Where(j => j.HospitalDepartments.Any(www => www.Units.Any(u => u.UnitTypeID == UnitTypeID)));
}
if (DirectorateOfHealthID != null)
{
query = query.Where(h => h.DirectorateHealthID == DirectorateOfHealthID);
}
query = query.Where(j => j.HospitalDepartments.Any(u => u.Units.Any(d => d.Devices.Any(s => s.Status == Enums.DeviceStatus.Free)))
&& j.HospitalDepartments.Any(hd => hd.Units.Any(u => u.Beds.Any(b => b.Status == Enums.BedStatus.Free))));
var list = query.ToList().Select(w => new HospitalInfo()
{
Id = w.ID,
Name = w.Name
}).ToList();
return Request.CreateResponse(HttpStatusCode.OK, list);
}
IQueryable<T> executes select query on server side with all filters. Hence does less work and becomes fast.
IEnumerable<T> executes select query on server side, load data in-memory on client side and then filter data. Hence does more work and becomes slow.
List<T> is just an output format, and while it implements IEnumerable<T>, is not directly related to querying.
So,
var list = query.ToList().Select(w => new HospitalInfo()
{
Id = w.ID,
Name = w.Name
}).ToList();
In your code you use query.ToList(). This means at first it pull all data into memory then apply Select query.If you want to retrieve HospitalID and Name then remove ToList() then your code like
var list = query.Select(w => new HospitalInfo
{
Id = w.ID,
Name = w.Name
}).ToList();
Remove the ToList call before the projection:
var list = query.Select(w => new HospitalInfo()
{
Id = w.ID,
Name = w.Name
}).ToList();
With that ToList call you are materializing your query before do the projection
Because you do query.ToList() this materialises the entire query with all columns into memory. It's actually a bad habit to get into. Instead, remove that, you already have it at the end anyway. The Select projection you have will only retrieve the relevant columns though:
var list = query.Select(w => new HospitalInfo()
{
Id = w.ID,
Name = w.Name
}).ToList();

GroupBy Mongodb with Driver C#

I'm making a stadistics module and I need to do a GroupBy Operation with a Count to take the total visitors by country. I'm using MongoRepository (Link to Library)
I have this code in C# working fine, but I don't like the solution:
var repositoryIpFrom = new MongoRepository<IpFrom>();
var countries = repositoryIpFrom.Where(s => s.id == id).Select(s => s.Country).Distinct();
foreach (var country in countries)
{
statisticsCountryDto.Add(new StatisticsCountryDto()
{
Country = country,
Count = repositoryIpFrom.Count(s => s.id == id && s.Country == country)
});
}
return statisticsCountryDto;
And this one with Linq (but it's not working... the error says GroupBy is not supported)
var tst = repositoryIpFrom.Where(s=>s.id == id).GroupBy(s => s.Country).Select(n => new
{
Country = n.Key,
Count = n.Count()
});
Can I have any options to make the GroupBy and not make a lot of queries?
Thanks!!
Since you are not filtering through the query, you can resolve the query by inserting a AsEnumerable operation and then perform the grouping operations on the local data after the MongoDb driver is no longer involved.
var tst = repositoryIpFrom
.Where(s=>s.id == id)
.AsEnumerable()
.GroupBy(s => s.Country)
.Select(n => new
{
Country = n.Key,
Count = n.Count()
});

Categories

Resources