tags:

views:

306

answers:

4

Hi
I have two tables, Tags(tagid, postid, tagname) and posts(postid, name, ...)
now i want to make a query that returns me all posts that have a generic amount of tags. like: i want all posts that have the tag asp.net AND jquery

as i said, the amount of tags to look for is generic

how can i do something like that?

thx

update 17.11.2009: there is one problem: the relation betwenn the tables does not exist, because my primary key is on 2 fields (for versioning) how can i make it without a relation? Im using Linq To Entities

also, the query should have good performance, and should not make thousands of server requests.

+6  A: 

You don't say if you're using L2S, L2O, L2E, or what. So here's an extension method which works for all of them. I'm making some guesses about the layout of your objects, so you may have to correct some of this.

public static IQueryable<Post> WhereHasAllTags(this IQueryable<Post> posts, IEnumerable<string> tags)
{
    var q = posts;
    foreach (var tag in tags)
    {
        q = q.Where(p => p.Tags.Any(t => t.Name == tag));
    }
    return q;
}

Now use it:

var filtered = Context.Posts.WhereHasAllTags(new [] { "asp.net", "jquery" } );
Craig Stuntz
this look like what i need, but there is one problem:the relation betwenn the tables does not exist, because my primary key is on 2 fields (for versioning) how can i make it without a relation?(im using EF btw)
k0ni
Hard to answer that without seeing the metadata. Want to post it as a separate question with more info?
Craig Stuntz
You can just do a 'join' in EF, so you can 'fake' a relationship.
Jan Jongboom
Jan, that would give up on all the benefits of the relationship, as well as making the model wrong. Better to get the model right to begin with.
Craig Stuntz
There can be some reasons not to have the FK relationship, mostly performance. So it's quite viable. Anyhow, +1, as this seems the most decent solution.
Jan Jongboom
as i said, i can't have relations because i my primarykey is on 2 fiels for versioning of the data. (an int field and a datetime field)but the solution bobwah has posted works
k0ni
I agree with bobwah; you should rethink your DB schema.
Craig Stuntz
how would you then handle versioning of data in the db?that every change i made will be saved in the db?
k0ni
I would not make it part of the PK. As you've discovered, this makes FKs (even at the relational level) odd. One way is to use a separate, history table. Another is to use dedicated versioning features in the DB. A third way is to use an OLAP DB.
Craig Stuntz
+4  A: 

Your best bet is to look into LinqKit to dynamically build predicates. That will allow you to join multiple predicates together, so you would just loop over the tags to build up the final expression.

There are special considerations if you are using Entity Framework versus Linq2SQL, so it's a little difficult to post the specific code, but the examples on that page should be enough to point you in the right direction.

mkedobbs
here, have your first upvote.
Martinho Fernandes
+1  A: 
IQueryable<Post> FilterByTags(IQueryable<Post> posts, IEnumerable<Tag> tags)
{
    foreach (var tag in tags)
    {
        posts = posts.Where(post => post.Tags.Contains(tag));
    }
    return posts;
}

The IQueryable<Post> you pass in will have filters added to it to ensure it references each Tag in the list.

This is a generic implementation which you may have to tweak depending on your LINQ provider

Garry Shutler
You do realize this code does *nothing*? It's a void method with only one side effect: enumerating `tags`.
Martinho Fernandes
Good point, been a while since I've used LINQ, it's immutable isn't it.
Garry Shutler
What tricked you is not immutability *per se*. It's deferred execution. Calling `Where` will not perform any query, but instead will "create" a query. Only when you enumerate it later (either with `foreach`, `ToList`, `ToArray` or maybe something else) will the query be run.
Martinho Fernandes
If you google for "LINQ deferred execution" you will get enough resources to understand that.
Martinho Fernandes
+1  A: 

I guess you can't use .Contains() using EF 3.5 which I have had a problem with. I got around this by using this "WhereIn" extension

http://stackoverflow.com/questions/374267/contains-workaround-using-linq-to-entities

put this into a static class then you could use something like:

IQueryable<Post> PostsWithByTags(IEnumerable<string> tagNames)
{
    var postIds = context.Tags.Select(t=>t.postid); 

    foreach (var tag in tagNames)
    {
       postIds = context.Tags
                     .WhereIn(t=> t.postid, postIds)
                     .Where(t=>t.tagname == tag);
    }

    return context.Tags.Where( t=> t.posId, postIds)
}

I really think you should look at having relationships between your tables though.

bobwah