views:

46

answers:

4

Sorry if this is a little abstract. I'm in the early stages of development.

I have two object types:

  1. An object that needs to store a series of user defined conditions.
  2. An object that matches zero or more of the conditions defined in the first object.

Here's a simple example of how it would run.

  1. A user creates several objects of type 2 and adds them to a collection.
  2. Then the user creates an object of type 1 and assigns several conditions to it.
  3. The system uses the conditions in object type 1 to generate a list of objects of type 2 sorted by what percentage of the conditions each object matches.

Here is a sample of the expected output:

Conditions                                            List
Quantity >= 2                                         Object5 100%
Value < 2                                             Object2  75%
Category = "Blah"                                     Object4  50%
Quality > 5                                           Object1  25%
                                                      Object3   0%

The first operand in each condition is the name of a property while the second operand is the value of that property.

How can I accomplish this?

My first thought is that it looks similar to a query language. If they were records in a DB table I could stitch together an SQL string for each condition, run each query and count the number of times each record id showed up in the query results. But these are plain old C# objects. I haven't decided on what to use for object persistence yet so I'd rather not tie myself to a db engine at this point. Any suggestions?

+1  A: 

This sounds like LINQ is what you want to use. In particular, I would look into LINQ To Objects and LINQ To SQL if a persistence store is eventually chosen.

Garett
A: 

also, there is an article on how Directly Execute SQL Queries (LINQ to SQL)

Northwnd db = new Northwnd(@"c:\northwnd.mdf");
IEnumerable<Customer> results = db.ExecuteQuery<Customer>
(@"SELECT c1.custid as CustomerID, c2.custName as ContactName
    FROM customer1 as c1, customer2 as c2
    WHERE c1.custid = c2.custid"
);
jebberwocky
A: 

Here's an example implementation using LINQ-to-Objects on strings:

var conditions = new Func<string, bool>[]
{
    s => s.Length < 4,                  // less than 4 characters
    s => s.Count(c => c == 'o') > 2,    // more than 2 'o's
    s => s == "fooo",                   // is "fooo"
    s => s.Contains('b'),               // contains 'b'
};
var words = new[] { "foo", "fooo", "foooo", "bar", "barr", "bazooo" };
var query = words.Select(Word => new
                                 {
                                     Word,
                                     Percentage = conditions.Average(c => c(Word) ? 1M : 0M)
                                 })
                 .Where(p => p.Percentage > 0)          // keep words matches some conditions
                 .OrderByDescending(p => p.Percentage)  // order by the percentage
                 .ThenBy(p => p.Word);                  // then order in alphabetical order

In retrospect, I think this example might work with LINQ-to-SQL too (with some tweaks on the conditions array).

This was just a quick example but you can certainly expand on the idea even more.

Jeff M
A: 

You can do this with a modified version of the Specification pattern. Start out with an interface that expresses results as percentages:

public interface ISpecification<T>
{
    double GetPercentSatisfiedBy(T target);
}

Next, create a specification which applies any arbitrary condition:

public sealed class Specification<T> : ISpecification<T>
{
    private readonly Func<T, bool> _predicate;

    public Specification(Func<T, bool> predicate)
    {
        _predicate = predicate;
    }

    public double GetPercentSatisfiedBy(T target)
    {
        return _predicate(target) ? 1 : 0;
    }
}

Now, create a specification which linearly combines the results of other specifications:

public sealed class CompositeSpecification<T> : ISpecification<T>
{
    private readonly IList<ISpecification<T>> _specifications;

    public CompositeSpecification(params ISpecification<T>[] specifications)
    {
        _specifications = specifications.ToList();
    }

    public double GetPercentSatisfiedBy(T target)
    {
        return _specifications.Average(
            specification => specification.GetPercentSatisfiedBy(target));
    }
}

Finally, build a specification which contains all of your desired conditions and apply it to a list of Foo objects:

var specification = new CompositeSpecification<Foo>(
    new Specification<Foo>(foo => foo.Quantity >= 2),
    new Specification<Foo>(foo => foo.Value < 2),
    new Specification<Foo>(foo => foo.Category == "Blah"),
    new Specification<Foo>(foo => foo.Quality > 5));

var foos = new List<Foo> { ... };

var results =
    from foo in foos
    let percentSatisfied = specification.GetPercentSatisfiedBy(foo)
    orderby percentSatisfied descending
    select new
    {
        Foo = foo,
        PercentSatisfied = percentSatisfied
    };

This design supports specifications of arbitrary complexity.

Bryan Watts