I have a WebApi which exposes a Contact field in my database. Currently it has endpoints for Post (create), Put (edit), Get(return whole list), Get(int id) (return specific field).
So the Get(int id) searches my DB for the contact with that id and returns it in JSON. I would like to implement a method by which the user can submit conditions to my first Get function such as:
GET http://urlformyapi.com/api/apiContact/?querystring
Where the query string might be for example:
firstname=phil
And return all the Phil's.
How is best to make this totally searchable for all of my data fields within contact?
public int contactid { get; set; }
[Required]
public string firstname { get; set; }
[Required]
public string lastname { get; set; }
[Required]
public string email { get; set; }
[Required]
public string mobile { get; set; }
public string company { get; set; }
public string position { get; set;}
public string notes { get; set; }
public string image { get; set; }
I could do an initial get of the whole list and then go through each query parameter like:
//where ContactList begins as the entire list of contacts.
if(notes != null){ ContactList = ContactList.Where(x => x.notes == notes).ToList(); }
Thus refining my list until returning it. But I wondered if there was an easier way which was more robust should my data model change/I want to make more fields searchable.
any thoughts?
If you have a lot of similar API methods, you can take a look on OData. Another variant try to use for this purpose Dynamic Linq with custom filter formatting. Otherwise my suggestion is create class which must contains query fields (search fields), for example: notes, ids, etc and then pass this object to API and filter your collections with those search fields and PredicateBuilder. Also good explanation how PredicateBuilder works.
Related
I have a class called Business, a class called Tag and a class called BusinessTagLink.
Both Business and Tag have the following property:
public virtual IList<BusinessTagLink>? BusinessTagLinks { get; set; }
and every business is linked to one or more tags via the BusinessTagLink table.
My BusinessTagLink class looks like this:
public class BusinessTagLink : BaseEntity
{
public int BusinessTagLinkId { get; set; }
public int BusinessId { get; set; }
public int TagId { get; set; }
public virtual Business Business { get; set; }
public virtual Tag Tag { get; set; }
}
I am trying to build a filter that will allow a user to filter down the list of businesses by interacting with a list of tags. I'm currently thinking they'll click on the tag and the ID of that tag will then get added to an int array, which will be passed to a filter method along with the full list of potential businesses. Something like:
public static IQueryable<Business> Filter(this IQueryable<Business> businesses, int[] tagIds) { }
What I'm really struggling with is how to use these parameters to return a list of filtered businesses. I think the LINQ query will make use of .Contains() and/or .Any() but I'm clueless beyond that.
return businesses.Where(tagIds.Contains(x => x.BusinessTagLinks.Select(y => y.TagId)))
return businesses.Where(x => x.BusinessTagLinks.Any(y => y.TagId == [no idea how to target array here]));
Any advice would be greatly appreciated.
return businesses.Where(x => x.BusinessTagLinks.Any(y => tagIdArray.Contains(y.TagId)));
This is basically saying give me the businesses where any of the business tag links has a tagId that exists in the array you're providing.
I'm building a Blog Comment and Reply section and I have these three classes mapped to my DB. The first class holds a collection of related comments to an article, the second class holds a collection of related remarks to the comments:
public class Article
{
public int ArticleID { get; set; }
public byte[] Image { get; set; }
public string Title { get; set; }
public string Body { get; set; }
public DateTime DatePublished { get; set; }
public string Author { get; set; }
public CategoryTyp Category { get; set; }
public virtual ICollection<Comment> Comments { get; set; }
}
public class Comment
{
public int CommentID { get; set; }
public int ArticleID { get; set; }
public int CategoryID { get; set; }
public int UserID { get; set; }
public string Description { get; set; }
public DateTime CommentDate { get; set; }
public virtual ICollection<Remark> Remarks { get; set; }
}
public class Remark
{
public int RemarkID { get; set; }
public int CommentID { get; set; }
public int ArticleID { get; set; }
public string RemarkDetail { get; set; }
public DateTime RemarkTime { get; set; }
}
And inside my Controller:
public ActionResult GetArticle(int id)
{
var article = db.Articles.Include("Comments").Where(a => a.ArticleID == id).SingleOrDefault();
return View(article);
}
I understand the basis of eager loading but my questions are:
How do you implement it when you're pulling data from multiple related tables?
What is the best practice of populating it to the View? Once I create a View Model how do I stuff the related collections?
1) With multiple related tables you can have two scenarios:
a) Multiple top level relations: you simply add multiple Include statements (I would suggest using lambda expressions instead of strings for this, to avoid typos).
db.Articles
.Include(a=>a.Comments)
.Include(a=>a.SomethingElse)
.FirstOrDefault(a=>ArticleID==id); // Side note: I would suggest this instead of your Where plus SingleOrDefault
For these scenarios I always use a helper method like this one.
b) Multiple nested related entities:
db.Articles
.Include(a=>a.Comments.Select(c=>c.Remarks)
.FirstOrDefault(a=>ArticleID==id);
2) It's a bit up to you how you pass the data to the views. One best practice I can tell you is that you shouldn't let views lazy load any dependant entities or collections. So your use of Include is correct, but I would even suggest to remove the virtual (deactivate lazy loading) to avoid missing an Include by accident.
Regarding the ViewModels you mention, you are actually not using view models, but your data models. This is OK in most cases, unless you need to format the data somehow or add extra information. Then you would need to create a View Model and map it from the data coming from EF.
Another scenario would be if you used WebAPI or an Ajax Action. In that case, I would suggest to use a DTO (equivalent to a ViewModel) to be able to better control the data returned and its serialization.
One last comment about ViewModels is that if you have heavy entities but you only need a few properties, a good choice is to use Projections, to instruct EF to only load the required properties, instead of the full object.
db.Articles
.Include(a=>a.Comments)
.Select(a=>new ArticleDto { Id = a.ArticleID, Title = a.Title })
.ToListAsync();
This will translate to a "SELECT ArticleID, Title FROM Articles", avoiding returning the article bodies and other stuff that you might not need.
You can chain the relationships with Include. For example:
var article = db.Articles.Include("Comments.Remarks").Where(a => a.ArticleID == id).SingleOrDefault();
I'm not sure what you mean by your second question, though. By issuing this query you already have all the comments and all the remarks for those comments. Therefore, you can access them off of the article instance out of the box:
foreach (var comment in article.Comments)
{
...
foreach (var remark in comment.Remarks)
{
...
}
}
How you handle that with your view model is entirely up to you. You could map the comments/remarks to view models of their own, set them directly on the view model, etc. That's all down to what the needs of your application are, and no one but you can speak to that.
I have two entities from my database exposed in an ASP.NET WebApi 2 OData: service Employee and Activity. For simplicity, let's assume they look like this:
public class Employee {
public int Id { get; set; }
public string Type { get; set; }
}
public class Activity {
public int Id { get; set; }
public int EmployeeId { get; set; }
public virtual Employee OpenedBy { get; set; }
}
Please note that the OpenedBy property corresponds to a navigation property, e.g. I can run the following OData query:
GET http://localhost/odata/Activities?$expand=OpenedBy
I would like to block certain Employee types from being shown in OData. Let's assume I can't do this on the data source, so I have to do it in code.
What I've done so far is to block these types in the EmployeesController (inherits from EntitySetController):
[Queryable]
public override IQueryable<Employee> Get() {
return dbContext.Employees.Where(e => e.Type != "Restricted").AsQueryable();
}
[Queryable]
protected override Employee GetEntityByKey([FromODataUri] int key) {
var employee = dbContext.Employees.Find(key);
if (employee == null || employee.Type == "Restricted") {
throw new ODataException("Forbidden");
}
return employee;
}
This works fine. However, I noticed that if I run the query:
GET http://localhost/odata/Activities?$expand=OpenedBy
I do not hit the code in the Employees controller and consequently the restricted employee records are visible. What is a good way to prevent this from happening?
In this case: http://localhost/odata/Activities?$expand=OpenedBy
I think you should change the code in ActivitiesController, $expand will hit Get Activity method there.
If you do not want to expand the OpenedBy all the time, you can add an attribute:
[NotExpandable]
Hope this can help :)
Since you say "I noticed that if I run the query" I get the impression that this is a side-effect that you are happy to restrict in all circumstances. If this is the case this article by Mike Wasson could be of use to you.
In that article he suggests two methods to restrict odata access to a property:
An attribute on your model
Programmatically removing it from your EDM
I didn't try the first and I'm not sure which namespace or libraries you would need to do it but, in the case of the question it would look like this:
public class Activity {
public int Id { get; set; }
public int EmployeeId { get; set; }
[IgnoreDataMember]
public virtual Employee OpenedBy { get; set; }
}
I have used the second method and this would look something like this for the example given in the question:
var activities = modelBuilder.EntitySet<Activity>("Activities");
activities.EntityType.Ignore(a => a.OpenedBy);
I have restricted some navigational Collections this way and it works very well.
I've started learning NoSQL on an example of RavenDB. I've started with a simplest model, let's say we have topics that were created by users:
public class Topic
{
public string Id { get; protected set; }
public string Title { get; set; }
public string Text { get; set; }
public DenormalizedUser User { get; set; }
}
public class DenormalizedUser
{
public string Id { get; set; }
public string Name { get; set; }
}
public class User
{
public string Id { get; protected set; }
public string Name { get; set; }
public DateTime Birthdate { get; set; }
//some other fields
}
We don't need the whole User for displaying a Topic, so I've denormalized it to DenormalizedUser, containing an Id and a Name.
So, here are the questions:
1) Is this approach correct for NoSQL?
2) How to handle cases when User changes the Name? Do I need to manually update all the Name fields in denormalized classes?
Shaddix you can use the Raven DB Include function to load the User using the UserId from your topic.
var topic = _session.Load<Topic>(topicId)
.Customize(x => x.Include<Topic>(y => y.UserId));
var user = _session.Load<User>(topic.UserId);
The Load for Topic will 'preload' the User and both Loads will only result in one GET request. (I couldn't reply directly to your response to Ayende due to my reputation).
You also use the alternative (and probably clearer) .Include() function without Customize().
http://docs.ravendb.net/consumer/querying/handling-document-relationships.html
shaddix,
You don't need to denormalize, you can hold a reference to the id and then Include that when you load from the server
1) Yes, this approach works fine and the result is, that you only need to load the topic-document when you want to display it along with the name of its user. However, as Ayende states, the perfomance will be nearly the same as if you didn't denormalize the user and just include it when needed. If you don't worry about multiple-server deployment I recommend that approach.
2) If you really want to denormalize the user, then you can update all topics referencing this user simply with a set based operation. Look at this: http://ravendb.net/faq/denormalized-updates
I currently have an Entity Framework model that collects data from a legacy database and I am currently using an int on my Id properties
I am attempting to build a search box with autocomplete capabilities and want to have the autocomplete function to return a subset of records based on whether the sample id either contains or starts with (final design decision not made yet) and I am running into problems with converting the integer id to a string as I would normally use a recs.Id.toString().StartsWith(recordId) but this is apparently not supported by the Entity Framework
Is there a way around this limitation ?
My code looks like the following
Model:
public class Sample
{
public Sample()
{
Tests = new List<Test>();
}
public int Id { get; set; }
public DateTime SampleDate { get; set; }
public string Container { get; set; }
public string Product { get; set; }
public string Name { get; set; }
public string Status { get; set; }
public virtual SamplePoint SamplingPoint { get; set; }
public virtual SampleTemplate SampleTemplate { get; set; }
public Customer ForCustomer { get; set; }
public virtual ICollection<Test> Tests { get; set; }
}
and the query I am currently trying to apply to this model
[HttpGet]
public JsonResult AutoComplete(string partialId)
{
var filteredSamples =
repo.AllSamples.Where( s =>
String.Compare(s.Status, "A", false) == 0
&& (s.Id.ToString()).StartsWith(partialId)
).ToList();
return Json(filteredSamples, JsonRequestBehavior.AllowGet);
}
Any ideas would be awesome I am out of ideas at this point
No matter what you do, this is going to result in some awful performance on large datasets, because you will not be able to use any indices. My recommendation would be to use a trigger or scheduled task to store the leading digit in a separate field and filter on that.
I ended up adding a view for autocomplete data and converting the data to string in the select statement and this solved my issue
Wild thought: how about your create a computed, persisted column on your database table, that converts your ID (INT) into a string?
Then you could:
put an index on that column
use a simple string comparison on that string column
Basically, you need this:
ALTER TABLE dbo.YourTable
ADD IDAsText AS CAST(ID AS VARCHAR(10)) PERSISTED
Now update you EF model - and now you should have a new string field IDAsText in your object class. Try to run your autocomplete comparisons against that string field.