Best Practices for Building a Search App? [closed] - asp.net

Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 2 years ago.
Improve this question
I will be starting a simple datastore-and-search project soon. Basically, one of those "put my huge Excel spreadsheet into a database, build a web GUI for it, and make it searchable" type-things.
One thing that's been bugging me is the actual search logic that will be used when the user enters some criteria. I am imagining a search interface with a text field, and a few other filtering tools - drop down combo boxes and check boxes and such.
While that gives me very strong, granular control over the filtering I can perform, I am wondering what SO's thoughts are on actually performing the search. I'll be using ASP.NET, MS SQL Server, and Linq-To-SQL here, so keep those technologies in mind.
Off the top of my head, I think I'd do something like:
var results = from s in db.Stuff
where (s.Prop1.Contains(textFilter) ||
s.Prop2.Contains(textFilter) ||
s.Prop3.Contains(textFilter)) &&
checkbox1.IsChecked ?
s.Prop4.ToLower().Equals(combobox1.Text) : true
select s;
Here's what I know:
How to do grouping and joins if necessary
I can use the Contains() method on individual properties to generate SQL LIKE queries
I can filter things property-by-property, building my search logic as above.
Here's what I'm asking:
Is there a way to search all properties (without pulling all objects into memory - which I assume means building a list of each object's properties with reflection, stringifying them, and then checking is out)? If not, this seems incredibly cumbersome as I'd have to build new logic for every new property I might add. Something like s.Contains(textFilter) in the above would be ideal.
How does a SQL LIKE query actually work? Is that something I want to do?
Is there a standard way of implementing search rules such as quoted strings for full-matching and logical operators such as AND and OR? I would be surprised if every application that implemented them did so with custom parsing logic.
Am I barking up the wrong tree? Did I miss something?

I had to create a similar search for a comments system recently. What I did was I created some extension methods off of the comments which allowed me to pass in a generic filtering object.
Here is the sample code that I used:
This is just a partial method and does not have the return but it will give you a picture of what I am doing:
public List<oComment> GetComments(oCommentSearch filters)
{
using (CommentDataContext db = CommentContextFactory.CreateContext())
{
var query = from comment in db.COMMENTs.FilterComments(filters)
select comment;
}
}
As you can see off the COMMENTs i have FilterComments. This is an extension method. This method looks like this (this is the entire class I have):
public static class CommentExtensions
{
public static IQueryable<COMMENT> FilterComments(this IQueryable<COMMENT> Comments, oCommentSearch Filters)
{
Filters = CheckFilter(Filters);
IQueryable<COMMENT> tempResult = Comments;
if(Filters.Classes.Count() > 0)
{
tempResult = from t in tempResult
where
Filters.Classes.Contains(t.CLASS_ID)
select t;
}
if (Filters.Flags.Count() > 0)
{
tempResult = from t in tempResult
where
Filters.Flags.Contains((int) t.FLAG_ID)
select t;
}
if (Filters.Types.Count() > 0)
{
tempResult = from t in tempResult
where
Filters.Types.Contains(t.CommentTypeId)
select t;
}
return tempResult;
}
private static oCommentSearch CheckFilter(oCommentSearch Filters)
{
Filters.Classes = CheckIntArray(Filters.Classes);
Filters.Flags = CheckIntArray(Filters.Flags) ;
Filters.Types = CheckIntArray(Filters.Types) ;
return Filters;
}
private static int[] CheckIntArray(int[] ArrayToCheck)
{
return ArrayToCheck == null || ArrayToCheck.Count() == 0 ? new int[] {} : ArrayToCheck;
}
}
This should get you started in the right direction for what you are trying to do.
Hope this helps!

You didn't mention it in your list of technologies that you're using, but don't overlook using Lucene.NET. It does searching very well and is fairly easy to setup. Basically, you add documents to an index, and Lucene efficiently manages the index. That way, you can search the index instead of loading documents one by one and looking at their properties.

Is there a way to search all properties (without pulling all objects into memory - which I assume means building a list of each object's properties with reflection, stringifying them, and then checking is out)? If not, this seems incredibly cumbersome as I'd have to build new logic for every new property I might add. Something like s.Contains(textFilter) in the above would be ideal.
We are using MsSql full text search functionality for this.

Related

Where should I put a logic for querying extra data in CQRS command flow

I'm trying to implement simple DDD/CQRS architecture without event-sourcing for now.
Currently I need to write some code for adding a notification to a document entity (document can have multiple notifications).
I've already created a command NotificationAddCommand, ICommandService and IRepository.
Before inserting new notification through IRepository I have to query current user_id from db using NotificationAddCommand.User_name property.
I'm not sure how to do it right, because I can
Use IQuery from read-flow.
Pass user_name to domain entity and resolve user_id in the repository.
Code:
public class DocumentsCommandService : ICommandService<NotificationAddCommand>
{
private readonly IRepository<Notification, long> _notificationsRepository;
public DocumentsCommandService(
IRepository<Notification, long> notifsRepo)
{
_notificationsRepository = notifsRepo;
}
public void Handle(NotificationAddCommand command)
{
// command.user_id = Resolve(command.user_name) ??
// command.source_secret_id = Resolve(command.source_id, command.source_type) ??
foreach (var receiverId in command.Receivers)
{
var notificationEntity = _notificationsRepository.Get(0);
notificationEntity.TargetId = receiverId;
notificationEntity.Body = command.Text;
_notificationsRepository.Add(notificationEntity);
}
}
}
What if I need more complex logic before inserting? Is it ok to use IQuery or should I create additional services?
The idea of reusing your IQuery somewhat defeats the purpose of CQRS in the sense that your read-side is supposed to be optimized for pulling data for display/query purposes - meaning that it can be denormalized, distributed etc. in any way you deem necessary without being restricted by - or having implications for - the command side (a key example being that it might not be immediately consistent, while your command side obviously needs to be for integrity/validity purposes).
With that in mind, you should look to implement a contract for your write side that will resolve the necessary information for you. Driving from the consumer, that might look like this:
public DocumentsCommandService(IRepository<Notification, long> notifsRepo,
IUserIdResolver userIdResolver)
public interface IUserIdResolver
{
string ByName(string username);
}
With IUserIdResolver implemented as appropriate.
Of course, if both this and the query-side use the same low-level data access implementation (e.g. an immediately-consistent repository) that's fine - what's important is that your architecture is such that if you need to swap out where your read side gets its data for the purposes of, e.g. facilitating a slow offline process, your read and write sides are sufficiently separated that you can swap out where you're reading from without having to untangle reads from the writes.
Ultimately the most important thing is to know why you are making the architectural decisions you're making in your scenario - then you will find it much easier to make these sorts of decisions one way or another.
In a project i'm working i have similar issues. I see 3 options to solve this problem
1) What i did do is make a UserCommandRepository that has a query option. Then you would inject that repository into your service.
Since the few queries i did need were so simplistic (just returning single values) it seemed like a fine tradeoff in my case.
2) Another way of handling it is by forcing the user to just raise a command with the user_id. Then you can let him do the querying.
3) A third option is ask yourself why you need a user_id. If it's to make some relations when querying the data you could also have this handles when querying the data (or when propagating your writeDB to your readDB)

ASP.Net checkboxes and if statements

I am asked to develop an application where a member can select 1 plan, a combination of plans, all plans, or none.
plan1, plan2, plan3, .... plan12
I ran the truth table to find out how many possibilities there are, and it turned out to be 4096. (Ridiculous!)
My plan was to write an if statement for each possibility like this:
if (plan1.Checked == true && plan2.Checked == false && ... && plan12.Checked == false){
// insert into into table test VALUES('Plan1')
}
and so on! Obviously, there must be a better and easier way than this. Any suggestions would help. Thank you all.
If you used a CheckBoxList or similar component this would be one approach.
CheckBoxList checkBoxList = ....; // Just an example here
// You would add the different project names into the CheckBoxList
String message = "";
for (int i = 0; i < checkBoxList.Items.Count; i++) // Look at every project name
{
if (checkBoxList.Items[i].Selected) // See if it's selected
{
message += checkBoxList.Items[i].Text; // Add the name to the message
}
}
Put message in DB; // <-- Store the message into your database here
Since you're only interested in selected items you wouldn't have to deal with non-selected items at all.
Note: There is probably a better/more efficient way of creating strings than this and you might be able to use lambda expressions. This is just showing a simple approach.
Andrew_CS answer is suitable and I prefer it, but here is another way:
Handle the checked event in the code behind of all checkboxes (use one event, but allow all checkboxes to be handled by it), and depending on the sender, then you can load the information you want.
It may be that you use a Select statement to load the relevant information for each checkbox during the checkbox checked event (might be a little easier to understand than a for loop!)

Designing a Generic Toolmanager

I want to handle multiple operations on a UI Component (button ,textfield, swfs,custom objects etc)
in like scaling,skewing ,color,rotations etc etc and save them too. Earlier the actions were done
using a single tool and single mxml file but now the tool is separated into different tools.
Was wondering how i can design / use something like Toolmanager class to handle actions etc?
Also the tricky part is that some objects can have more operations defined for them .
Like 'object1' has 3 operations that can be performed on it and 'object2' has 5 operations defined on it.
We are using MVC design pattern but no frameworks as such.
What are the different design patterns that can be used to do this?
Edit:
To be more precise i want implement this in AS3 OO way.
The application is similar to drawing application which supports addition of various images,text,audio,swfs etc. One added user can perform various operations of the objects..like
adding color,scaling skewing,rotation etc etc and custom effects and after that export the drawing as PNG.Likewise some effects that are applicable to text are not applicable to images
and vice versa. and some effects can be common.
Any ideas?
Probably you could have a toolbar, tools(inheriting from a common base), and some kind of property panel, these objects are accessible from a manager class which wrappes them together and makes some variables accessible for all classes.
Probably you want a selection array or vector in the manager class and a select tool to manipulate this collection
like (in the manager)
protected var _selection:Vector.<EditableBase> = new Vector.<EditableBase>();
public function get selection() { return _selection;}
and a collection about the editbase extensions and the tools avaiable for them.
every time the selection tool updates the selection collection (probably calling a method on manager from the select tool's onMouseUp method) you can update the toolbar to display the apropriate tools for the current selection
(in manager)
protected var _ToolsByType:Object (or Dictionary) = {"EditableAudio": ["toolA", "toolB", etc]};
protected var _defaultSet:Array = ["toolA", "toolB"];
and after the selection has benn manipulated (in manager also)
public function onSelectionChangedCallback():void
{
var toolsToDisplay:Array = _defaultSet;
if(_selection.length == 1)
{
//not actual syntax
var type:String = getQualifiedClassName(_selection[0]);
if(type in _ToolsByType) toolsToDisplay = _ToolsByType[type];
}
myToolBar.showSet(toolsToDisplay);
}
the ancestor for the tools should look something like this:
public class ToolBase
{
protected var _manager:ToolManager;
function ToolBase(manager:ToolManager)//and probably a bunch of other params as well
{
_manager = manager;
}
function onSelect()
{
//you can manipulate the properties panel here
}
function onDeSelect()...
function onMouseDown(mouseData:event/whateverWrapperYouHaveCreated)...
function onMouseMove...
function onMouseUp...
}
and so and so on :)
kinda straight forward.
check photoshop plugin tutorials, or google around "extending {any adobe stuff here, like flash or something}
thats javascript but the concept can be applied here as well
maybe you could use the Strategy Design Pattern by creating some extra classes in your MVC implementation
http://en.wikipedia.org/wiki/Strategy_pattern
algo, check this tool for images:
http://www.senocular.com/demo/TransformToolAS3/TransformTool.html
bye! :)

"EntityCollection already initialized" error with entity as model in Asp.Net MVC?

I'm having great difficulties with creating a complex object. I have an EF model with a Consultant table that has one-to-many relationships to a number of other tables. I wanted to use the Consultant object as the model as is because it would be very simple and easy (and as it's done in the NerdDinner tutorial), as I've done with other objects that didn't have one-to-many relationships. The problem is that these relationships cause this error: "EntityCollection already initialized" when I try to post to the Create method.
Several people have advised me to use a ViewModel instead, and I posted a question about this (ViewModels and one-to-many relationships with Entity Framework in MVC?) because I don't really understand it. The problem is the code gets reeeeally ridiculous... It's a far cry from the simplicity I've so far appreciated in MVC.
In that question I forgot to mention that besides the Create method, the Edit method uses the same CreateConsultant method (the name may be misleading, it actually populates the Consultant object). And so in order not to have additional, say "Programs" added when editing, I needed to complicate that method further. So now it looks like this:
private Consultant CreateConsultant(ConsultantViewModel vm, Consultant consultant) //Parameter Consultant needed because an object may already exist from Edit method.
{
consultant.Description = vm.Description;
consultant.FirstName = vm.FirstName;
consultant.LastName = vm.LastName;
consultant.UserName = User.Identity.Name;
if (vm.Programs != null)
for (int i = 0; i < vm.Programs.Count; i++)
{
if (consultant.Programs.Count == i)
consultant.Programs.Add(vm.Programs[i]);
else
consultant.Programs.ToList()[i] = vm.Programs[i];
}
if (vm.Languages != null)
for (int i = 0; i < vm.Languages.Count; i++)
{
if (consultant.Languages.Count == i)
consultant.Languages.Add(vm.Languages[i]);
else
consultant.Languages.ToList()[i] = vm.Languages[i];
}
if (vm.Educations != null)
for (int i = 0; i < vm.Educations.Count; i++)
{
if (consultant.Educations.Count == i)
consultant.Educations.Add(vm.Educations[i]);
else
consultant.Educations.ToList()[i] = vm.Educations[i];
}
if (vm.WorkExperiences != null)
for (int i = 0; i < vm.WorkExperiences.Count; i++)
{
if (consultant.WorkExperiences.Count == i)
consultant.WorkExperiences.Add(vm.WorkExperiences[i]);
else
consultant.WorkExperiences.ToList()[i] = vm.WorkExperiences[i];
}
if (vm.CompetenceAreas != null)
for (int i = 0; i < vm.CompetenceAreas.Count; i++)
{
if (consultant.CompetenceAreas.Count == i)
consultant.CompetenceAreas.Add(vm.CompetenceAreas[i]);
else
consultant.CompetenceAreas.ToList()[i] = vm.CompetenceAreas[i];
}
string uploadDir = Server.MapPath(Request.ApplicationPath) + "FileArea\\ConsultantImages\\";
foreach (string f in Request.Files.Keys)
{
var filePath = Path.Combine(uploadDir, Path.GetFileName(Request.Files[f].FileName));
if (Request.Files[f].ContentLength > 0)
{
Request.Files[f].SaveAs(filePath);
consultant.Image = filePath;
}
}
return consultant;
}
This is absurd, and it's probably due to my incompetence, but I need to know how to do this properly. Just the answer "use a ViewModel" obviously won't suffice, because that's what got me into this trouble to begin with. I want the simplicity of the simple entity object as model but without the "EntityCollection has already been initialized" error. How do I get around this?
Of course, if I'm just doing the ViewModel strategy the wrong way, suggestions on that are welcome too, but mainly I want to know what is causing this error if I do it the simple "NerdDinner" simple object way. Please keep in mind also that the View in question is restricted to authorized users of the site. I would love to do it the "correct" way, but if using ViewModels implies having code that is this hard to maintain, I'll forgo it...
Please help!
UPDATE:
Turns out this code doesn't even work. I just checked after calling Edit to update values, and it doesn't update them. So only the Create part works.
This is the part that doesn't work:
consultant.Programs.ToList()[i] = vm.Programs[i];
I sort of had a hunch I couldn't use ToList and update an item in the EntityCollection. But this makes it even harder. So now I don't know how to do it with the entity directly, which I would prefer (see above). And I don't know how to get this ViewModel stuff working, let alone get it clean...
Any ideas? There must be something really wrong here, and I'm hoping someone will spot how I've just missed something simple that turns all of this code on its head!
Ok, so in my experience the EF stuff doesn't easily play nicely when used on the wire, full stop (meaning, it's not a good format for passing data in whether you're in MVC or WCF, or whatever). It's an EF issue, to me, not a MVC issue because the problem you're experiencing with the direct use of your EF models has to do w/ how they're tracked in the EF objectcontext. I've been told that there's solutions to that involving re-attaching the passed entity, but I've found it to be more trouble than it's worth; as have others which is why most folks say "use viewmodels" for your input parameters in this situation.
I agree that your code above is kind unpleasant, there's a couple ways to fix it. First, check out AutoMapper, which helps a lot in that you can skip defining the obvious (e.g. when both models have a simple property like "Name" of hte same type & name.
Second, what you're doing w/ the loops is a bit off anyway. You should not assume that your list being posted from the client is the same order, etc as the list that EF is tracking (lists, rather; referring to your Programs, etc). Instead, think of the scenarios that might occur and account for those. I'll use Programs as an example.
1) a consultant has a program, but details about that program have changed.
2) a consultant does not have a program that is in the viewmodel
2a) the program already exists in the database somewhere, just not part of this consultant
2b) the program is new.
3) a consultant has a program that's not in the viewmodel (something you don't currently account for).
I don't know what you want to have happen in each of those cases (i.e. is the viewmodel complete and canonical, or is the entity model, during an update?), but let's say you're looping over your viewmodel's Programs as you do above. For the scenario 1 (which seems to be your main problem), you will want to do something like (using AutoMapper):
var updated = vm.Programs[i];
var original = consultant.Programs.SingleOrdefault(p=>p.ID == uppdated.ID);
Mapper.Map(updated,original);
yourEfContext.SaveChanges(); // at some point after this, doesn't have to be inside the loop
EDIT:
one other thought that might be useful to you is that I when you fetch your consultant out of the database (not sure what mechanism you use for that), make sure you call Include on the collections that you're going to update as well. Otherwise, each of those iterations you do will be another round-trip to the database, which obviously you could avoid if you just used Include to eager-load them all in one shot.

Fastest way to get an Objects values in as3

Ok, so I swear this question should be all over the place, but its not.
I have a value object, inside are lots of getters/setters. It is not a dynamic class. And I desperately need to search an ArrayCollection filled with them. The search spans all fields, so and there are about 13 different types of VOs I'll be doing this with.
I've tried ObjectUtil.toString() and that works fine and all but it's slow as hell. There are 20 properties to return and ObjectUtil.toString() adds a bunch of junk to the output, not to mention the code is slow to begin with.
flash.utils.describeType() is even worse.
I'll be pleased to hear I'm missing something obvious.
UPDATE:
I ended up taking Juan's code along with the filter algorithm I use for searching and created ArrayCollectionX. Which means that every ArrayCollection I use now handles it's own filters. I can search through individual properties of the items in the AC, or with Juan's code it handles full collection search like a champ. There was negligible lag compared to the same solution with external filters.
If I understand your problem correctly, what you want is a list of the getters defined for certain objects. As far as I know, you'll have to use describeType for something like this (I'm pretty sure ObjectUtils uses this method under the hood).
Calling describeType a lot is going to be slow, as you note. But for only 13 types, this shouldn't be problematic, I think. Since these types are not dynamic, you know their properties are fixed, so you can retrieve this data once and cache it. You can build your cache up front or as you find new types.
Here's is a simple way to do this in code:
private var typePropertiesCache:Object = {};
private function getPropertyNames(instance:Object):Array {
var className:String = getQualifiedClassName(instance);
if(typePropertiesCache[className]) {
return typePropertiesCache[className];
}
var typeDef:XML = describeType(instance);
var props:Array = [];
for each(var prop:XML in typeDef.accessor.(#access == "readwrite" || #access == "readonly")) {
props.push(prop.#name);
}
return typePropertiesCache[className] = props;
}

Resources