Multiply filtering in MongoDB - c#

I have table with records in HTML and many filtering options by properties. User can choose none, one or more. I'm using mongoDb database and MongoDb driver in C#. How I can realize this filtering?
Filter
public class ConsultantFiltersModel
{
public string RecruiterName { get; set; }
public int? RecruiterCode { get; set; }
public bool? WithouRecruiter { get; set; }
public string FutureWorkerName { get; set; }
public string PhoneNumber { get; set; }
}
I pass this filter from client side, some properties are Nullable. I need to filter records by only non-null properties.
I've tried to realize it like this
public Consultant FilterRecords(ConsultantFiltersModel consultantFiltersModel)
{
return consultantsCollection
.AsQueryable()
.Where(recruiterNameFilter =>
string.IsNullOrEmpty(consultantFiltersModel.RecruiterName)
? true
: recruiterNameFilter.Recruiter.Value == consultantFiltersModel.RecruiterName)
.Where(recruiterCodeFilter =>
consultantFiltersModel.RecruiterCode.HasValue
? true
: recruiterCodeFilter.Recruiter.Key == consultantFiltersModel.RecruiterCode)
.Where(phoneNumberFilter =>
string.IsNullOrEmpty(consultantFiltersModel.PhoneNumber)
? true
: phoneNumberFilter.FutureWorker.Phone.Contains(consultantFiltersModel.PhoneNumber))
.Where(futureWorkerNameFilter =>
string.IsNullOrEmpty(consultantFiltersModel.FutureWorkerName)
? true
: futureWorkerNameFilter.FutureWorker.Name.Contains(consultantFiltersModel.FutureWorkerName))
.FirstOrDefault();
}
But I get this exception
An exception of type 'System.Reflection.TargetException' occurred in
System.Private.CoreLib.ni.dll but was not handled in user code
Additional information: Non-static method requires a target.
Maybe it's not supported by mongoDbDriver? Any advises?

You may have better luck using the native mongodb Find() either using Lambda directly, or using the Builders to create your filters. They might be easier to build dynamically than the query above
var builder = Builders<YourObject>.Filter;
var filter = builder.Eq(x => x.YourField, "SomeValue");
var andFilter = builder.And(filter, builder.Eq(x => .SomeOtherField, "SomeOtherValue"));
var results = consultantsCollection.Find(andFilter).FirstOrDefault();
The Builders have a number of extensions methods for your filtering, as shown above, i am using .Eq() and .And(), but explore it and you will find an ectensive list to help you

Related

.net Linq command doesn't return certain fields

I'm having an issue where objects are coming back as null even if they passed linq tests and I can see the values in the db, and I am stuck unsure where to go to fix this. I'm not normally a c# developer so this is new territory for me.
My table looks like
Class Meeting {
...
public virtual List<MeetingParticipant> Participants { get; set; }
...
}
Class MeetingParticipant {
public bool isOrganiser { get; set; }
public Account Participant { get; set; }
public ParticipatingState ParticipatingState { get; set; }
public string responseText { get; set; }
}
the only bind i have is: modelBuilder.Entity<Meeting>().OwnsMany(meeting => meeting.Participants);
and my linq command is:
var meetings = (from m in _context.Meetings
where m.Participants.Any(val => val.Participant.PhoneNumber == passedInPhoneNumber && val.ParticipatingState == ParticipatingState.Pending)
select m);
Annoyingly when I dig into the meetup objects that are returned, there is participants however their Account object is null. However, for the meetup to pass the linq request, it had to exist so I could compare its phone number.
What am I missing?
A simple adjustment to your Linq command should get you the results you want:
var meetings = from m in _context.Meetings.Include(val => val.Participant)
where m.Participants.Any(val => val.Participant.PhoneNumber == passedInPhoneNumber && val.ParticipatingState == ParticipatingState.Pending)
select m;
The .Include(val => val.Participant) is the magic here - it tells EF to "eagerly" load and populate that entity in your results.
Learn more about eager loading here: https://www.entityframeworktutorial.net/eager-loading-in-entity-framework.aspx
Edit: As mentioned in Beau's comment, for this to work, you need to add the following using statement:
using System.Data.Entity;

Need to convert Left join SQL to linq query - help appreciated

Here is my code the issue I have is the less than comparison in the On clause ... Since Linq doesn't allow this .... Migrating down into the where clause wont work as I am comparing one of the fields to null.
Here is the sql query (THE a.UserID= is hardcoded for now)
SELECT A.Policy, A.Comments, A.EventDTTM, A.Status, A.Reason, A.FollowUp
FROM PP_PolicyActivity A
LEFT JOIN PP_PolicyActivity B
ON(A.Policy = B.Policy AND A.EventDTTM < B.EventDTTM)
WHERE A.UserID = 'Ixxxxxx'
AND B.EventDTTM IS NULL AND a.status = 'open - Pending'
order by A.EventDTTM DESC
I need the result set from the above query as an IEnumerable list to populate a view
I'm tasked with rebuilding an old VB ASP NET that has a set of standing production databases behind it ... i don't have the option of changing the db design. I connecting to the server and database and this query was going against a table on that database.. the model also reflects the layout of the actual table.
The problem is with A.EventDTTM < B.EventDTTM - I can't move this to the where clause as I also have to deal with B.EventDTTM IS NULL in the where clause.
I need to retool the query someway so that it is 'linq' friendly
public class PolicyActivityModel
{
public string Policy { get; set; }
public int PolicyID { get; set; }
public string Status { get; set; }
public string Reason { get; set; }
public string Comments { get; set; }
public DateTime EventDTTM { get; set; }
public string UserID { get; set; }
public DateTime FollowUp { get; set; }
}
Company policy prohibits me from showing the connection string.
I am extremely new to Linq, Any help greatly appreciated
thank you
You can use the navigation property after you get the policy from the database.
var policy = DbContext.First(x => x.Id == 1000);
var otherPolicies = policy.ConnectedPolicies.Where(p => ...);
It's weird being a self-join but this is the most direct translation to Linq:
var query = from leftPP in PP_PolicyActivity
join rightPP in PP_PolicyActivity
on new { Policy = leftPP.Policy, EventDTTM = leftPP.EventDTTM }
equals new { Policy = rightPP.Policy, EventDTTM = rightPP.EventDTTM }
into pp from joinedRecords.DefaultIfEmpty()
where leftPP.UserId == 1
&& leftPP.EventDTTM < rightPP.DTTM)
&& rightPP.EventDTTM == null
&& leftPP.status = "open - Pending"
select new
{
leftPP,
rightPP
}
I free typed this, without models or Intellisense, thus there might be some smaller errors.
You could add the order by in that clause, but it's also still an IQUeryable, so I'd leave it.
And then, to get a List of models:
var results = query.OrderByDescending(x => x.EventDTTM).ToList();
The actual join is lines 2,3,4 and 5. It's verbose and "backwards" from SQL, and most importantly uses anonymous types. Accessing indidual properties will look something like:
results[0].leftPP.PolicyId

Entity Framework ObjectQuery.Include()

I have an object with two objects as properties (User, PrimaryNode), both could potentially be null, see below:
public class Item
{
[Key]
public int ItemId { get; set; }
public string ItemName { get; set; }
public Node PrimaryNode { get; set; }
public User User { get; set; }
}
I'm using Entity Framework 6 to populate the Item object and using chained includes to populate the PrimaryNode and User objects within it.
When the first chained Include has a null object then the whole object returns as null, for example:
using (var db = new MyContext())
{
var item = db.Items.Include(i => i.User).Include(n => n.PrimaryNode).FirstOrDefault(i => i.ItemId == id);
}
If in the above example i.User is null then the item variable is null. Whats the best way of populating both the sub-objects in a way that if a sub-object is null then the parent object and the other sub-object will still be populated?
I don't think your issue is due to the Include calls. According with the documentation:
This extension method calls the Include(String) method of the
IQueryable source object, if such a method exists. If the source
IQueryable does not have a matching method, then this method does
nothing.
In other words is going to be translated to:
var item = db.Items.Include("User").Include("PrimaryNode").FirstOrDefault(i => i.ItemId == id);
My question is, are you sure you have an Item with that id properly related with existing rows in Users and PrimaryNodes tables in your DB?. When you call Include method at the end is going to be translated to a join, so if the FK of your relationship doesn't match with the PK that reference, your query should not return what you are expecting.
Anyways, if you want to try another variant to load related properties you can use Explicit Loading:
var item = db.Items.FirstOrDefault(i => i.ItemId == id);
context.Entry(item).Reference(p => p.PrimaryNode).Load();
context.Entry(item).Reference(p => p.User).Load();
I think it would be better if you use Lazy loading int his situation. Just make the User and PrimaryNode virtual:
public class Item
{
[Key]
public int ItemId { get; set; }
public string ItemName { get; set; }
public virtual Node PrimaryNode { get; set; }
public virtual User User { get; set; }
}
And then:
var db = new MyContext();
var item = db.Items.FirstOrDefault(i => i.ItemId == id);
As others have mentioned, I think your issue is not due to the Includes. However, I think the following method has value. It is functionally equivalent to what you are already doing with the chained includes, but I think it has several benefits including making the intention of the code clear to the user.
The includes can be placed in Extension methods:
using System.Data.Entity;
using System.Linq;
namespace Stackoverflow
{
public static class EntityExtensions
{
public static IQueryable<Item> IncludePrimaryNode(this IQueryable<Item> query)
{
// eager loading if this extension method is used
return query.Include(item => item.PrimaryNode);
}
public static IQueryable<Item> IncludeUser(this IQueryable<Item> query)
{
// eager loading if this extension method is used
return query.Include(item => item.User);
}
}
}
Then, you can use the extensions as follows:
using (var db = new MyContext())
{
var itemQuery = db.Items.IncludeUser();
itemQuery = itemQuery.IncludePrimaryNode();
var item = itemQuery.FirstOrDefault(i => i.Id == 1);
}
It's just another way of doing the same thing, but I like the clarity it adds to the code.

How to filter children object using WHERE statement in lambda expression

here is the scenario:
- I have a list of products.
- each product has a number of parts.
- The parts has two types: MAIN and OPTIONAL.
I want to bring the list of the main parts of a product. What would be the statement?
I am currently using this statement but returns an error:
#Html.DisplayFor(p => p.Products.FirstOrDefault().Parts.Where(i => i.PartType == percobaan2.Models.PartType.Main))
Thanks for you help
UPDATE:
here is the error:
An exception of type 'System.InvalidOperationException' occurred in System.Web.Mvc.dll but was not handled in user code
Additional information: Templates can be used only with field access, property access, single-dimension array index, or single-parameter custom indexer expressions.
ADDITIONAL INFORMATION:
this code works though. But I need to add the where filter:
#Html.DisplayFor(p => p.Products.FirstOrDefault().Parts)
UPDATED 2:
Here is my viewmodel class updated with mainparts property:
public class ProductDetail
{
public IEnumerable<Product> Products { get; set; }
public IEnumerable<Part> Parts { get; set; }
public IEnumerable<percobaan2.Models.Version> Versions { get; set; }
public IEnumerable<Category> Categories { get; set; }
public IEnumerable<Producer> Producers { get; set; }
public IEnumerable<Part> MainParts
{
get
{
return Parts.Where(p => p.PartType == PartType.Main);
}
}
}
DisplayFor can't be called on an IEnumerable like that. It needs to be a direct property of a model.
#Html.DisplayFor(p => p.Products.FirstOrDefault().Parts) works because Parts is a property that exists on your Product type.
If you need to be able to filter the parts but still want the HTML helper, define another property like so:
public class Product
{
public IEnumerable<Part> MainParts
{
get
{
return Parts.Where(i => i.PartType == PartType.Main);
}
}
}
Then you can use
#Html.DisplayFor(p => p.Products.FirstOrDefault().MainParts)
If you're using an EF-generated class, just make this a partial class and use it as-is.

Dynamic Expression for Ordering Child Collections with Entity Framework

I am new to EF. I am trying to get Entity Framework 4.2 to do a sort by a calculated property (not mapped).
Here is what my entity look like:
public class Site : Entity
{
public Site()
{
Equipments = new HashSet<Equipment>();
Forecasts = new HashSet<Forecast>();
}
[StringLength(8)]
public string Number { get; set; }
[StringLength(50)]
public string EquipmentShortCLLI { get; set; }
[StringLength(50)]
public string Location { get; set; }
public virtual Central Central { get; set; }
public virtual ICollection<Equipment> Equipments { get; set; }
public virtual ICollection<Forecast> Forecasts { get; set; }
#region Calculated Items
public bool IsEmbargo {
get { return Equipments.Count > 0 && Equipments.SelectMany(x => x.EquipmentDetails).Any(e => e.IsEmbargo); }
}
//...
public int PortsCapacity
{
get
{
return Equipments.Count > 0
? Equipments.SelectMany(x => x.Slots).Sum(x => x.PortsCapacity)
: 0;
}
}
#endregion
//...
By trying to order using any of my readonly properties I am getting the exception:
The specified type member 'PortsCapacity' is not supported in LINQ to Entities. Only initializers, entity members, and entity navigation properties are supported.
Which makes sense because EF is trying to build an sql orderby with a field that does not exist in the database (my understanding..).
Now, by using some dynamic linq code I was able to make this work for my many-to-one columns by passing "Central.SomeField" (as opposed to making a ReadOnly Property that returns Central.SomeField).
I.E.:
query.OrderBy("Central.SomeField");
However, I still face the same issue when it comes to a collection of items (Equipments). I am trying to make this as dynamic as possible by using a string coming from the client side and avoiding a long switch case, but at this point I will accept any ideas, so long as the sorting happens on the database side.
Edit 1:
Following what Ladislav Mrnka says, how would one execute an OrderBy clause on one-to-many child items using lambdas or expression?
I don't think that Dynamic Linq is capable of this. You need a real Linq subquery to compute aggregations on Equipements so it will simply not work. If the user selects ordering by IsEmbargo or PortsCapacity you must have some switch / if block to handle this case by appending special part of the query - no other way.

Categories

Resources