Gå til innhold

Anbefalte innlegg

Vi tenkte å lage en extension method for å gjøre det enklere å filtrere ut ting fra en IQueryable. Gikk på med friskt mot, og trodde det skulle gå relativt smooth. Men så møtte vi på et lite problem... og har ærlig talt ikke helt peiling på hvordan, eller om det i det hele tatt er mulig, å få det til på en god måte. La tanken litt fra oss, men fant ut vi kunne prøve å poste problemet her. Vi har laget en alternativ løsning, men den er ikke like smooth. Og så var det litt frustrerende å ikke få det til... vet det er mange smarte her, så håper på at noen av dere har en genial løsning ;)

 

Cluet er at vi vil kunne lage en liste med for eksempel datoer eller ider, nærmere bestemt en liste med flere sett av fra-verdi og til-verdi. Tanken var så å kunne ta en IQueryable med ansatte eller fisk eller hva som helst og filtrere ut de som faller innenfor en av disse intervallene ved å kjøre noe lignende dette:

 var filtrertFisk = datacontext.Fisk.AsQueryable().Amongst(listOfIdRanges, Fisk.Id);
- eller
var filtrertFisk = datacontext.Fisk.AsQueryable().Where(ø => ø.Id.Amongst(listOfIdRanges));

eller no sånt...

 

Det var tanken. Så kommer det vi har kommet på av kode så langt:

 

LinqKit sin PredicateBuilder:

 

using System;
using System.Linq;
using System.Linq.Expressions;
using System.Collections.Generic;

namespace SMSDevelopmentAndSupport.RmsV.bi.Common.LinqKit
{
public static class PredicateBuilder
{
	public static Expression<Func<T, bool>> True<T>() { return f => true; }
	public static Expression<Func<T, bool>> False<T>() { return f => false; }

	public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> expr1,
														Expression<Func<T, bool>> expr2)
	{
		var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>());
		return Expression.Lambda<Func<T, bool>>
			  (Expression.Or(expr1.Body, invokedExpr), expr1.Parameters);
	}

	public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> expr1,
														 Expression<Func<T, bool>> expr2)
	{
		var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>());
		return Expression.Lambda<Func<T, bool>>
			  (Expression.And(expr1.Body, invokedExpr), expr1.Parameters);
	}
}
}

 

 

Vårt lille extension method skall:

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;

namespace SMSDevelopmentAndSupport.RmsV.bi.Common.Helpers
{
public struct Betweenie<T>
{
	public T From { get; set; }
	public T To { get; set; }
}


public static class IQueryableHelper
{
	public static IQueryable<TSource> Amongst<TSource, TField>(
		this IQueryable<TSource> Source, 
		TField Field,
		ICollection<Betweenie<TField>> Betweenies)
	{
		var predicate = PredicateBuilder.False<TSource>();

		foreach (Betweenie<TField> b in Betweenies)
		{
			TField from = b.From;
			TField to = b.To;

			predicate = predicate.Or(ø => ø.Field >= from && ø.Field <= to);
		}

		return Source.Where(predicate);
	}
}
}

 

 

Problemet ligger i Field. For det bør jo kunne være hva som helst. fisken sin id, fisken sin fødselsdag, etc. Eneste vi på en måte "vet" er at den må ha samme type som disse fra og til verdiene (og naturligvis at det er et felt som finnes på for eksempel fisken). men hvordan kan vi få oppgitt den i metode kallet? og hvordan kan vi bruke den etterpå? Kan tenke meg at en må/kan/bør bruke en slik lambda expression eller noe sånt, men det er jeg litt usikker på hvordan å bruke... :hmm:

 

Noen smarte folk her som har ideer? Er ikke veldig viktig, men bare synes det hadde vært så skrekkelig artig å fått det til, hehe. *føle seg nerd* :ermm:

Endret av Svish
Lenke til kommentar
Videoannonse
Annonse

Lag et interface med fra og til. Da kan du skrive:

 

public interface IFromTo
{
DateTime From {get;}
DateTime To {get;}
}
public static class IQueryableHelper
{
	public static IQueryable<TSource> Amongst<TSource, TField>(
		this IQueryable<TSource> Source, 
		TField Field,
		ICollection<Betweenie<TField>> Betweenies) where TField : IFromTo
{
//Field.From
//Field.To
...
}

 

Har ikke testet koden, men det burde i alle fall gi muligheten for å kunne si noe om hvilke properties Field skal ha.

Lenke til kommentar

problemet er jo at field ikke har noen properties. field _er_ propertyen. Source inneholder en liste med objekter som har forskjellige properties. Det man er ute etter er å plukke ut en enkelt av disse propertyene, og så se om den er blant rangene i betweenies. For eksempel hvis betweenies inneholder rangene 0-5,7-4,10-20. og har source som Fisk, så skal en kunne oppgi for eksempel Id som field. alle Fisk i Source som har en id fra 0-5 eller 7-4 eller 10-20 skal så bli sluppet igjennom, mens resten blir filtrert bort. men en skal også for eksempel kunne gi den Weight istedet for Id og den fungerer på samme måte. Så sant Weight er en int, ettersom det er int vi har i den rangen. hadde Weight vært decimal så måtte listen med betweenies inneholdt decimal ranger. om en sier at TField skal være Compareable og bruker CompareTo istedet for <= og >= skal en jo kunne bruke det med strenger og det hele også.

 

gir det noe mening, eller bare roter jeg? :p

Lenke til kommentar

Er ikke helt sikker på hva du skal frem til her, men jeg gjør et forsøk:

 

delegate bool QueryProc(DataTypenDin);
IEnumerable<DataTypenDin> Query(QueryProc)
{
 foreach(DataTypenDin item in collection)
if(QueryProc(item))
  yield return item;
}
(...)
foreach(DataTypenDin item in Objektet.Query(delegate(DataTypenDin i) { return i.Name == "Hei"; }))
 Console.WriteLine("Ett objekt som het hei!");

Kunne sikkert blitt gjort bedre med linq, men jeg har ikke kommet så langt enda.

[/code]

Lenke til kommentar

:hmm:

 

Tror ikke jeg skjønner alt hva du skal, men ville det vært mulig om du forutsatte at det var en metode på objektetene du sender som evaluerer om det skal slippe gjennom? Altså noe slikt:

 

public interface IUse
{
bool UseThis(List<object>);
}
public static class IQueryableHelper
{
	public static IQueryable<TSource> Amongst<TSource>(
		this IQueryable<TSource> Source, 
		List<object> Betweenies) where TSource : IUse
{
foreach(TSource s in Source)
{
if(s.UseThis(Betweenies))
yield return s; //eventuelt noe annet som funker :P
}

 

Som sagt...tror ikke jeg har helt oversikten over hva du trenger, men jeg skal pokker meg ha for at jeg prøver :p

Endret av The Jackal
Lenke til kommentar

Du bare roter ;)

 

Du har feil fokus og overdesigner løsningen. Det enkleste er ofte det beste.

 

Det du har behov for er en filter funksjon. Altså anbefaler jeg deg å skrive en test først som forteller deg hvordan løsningen skal fungere, så får du løsningen til å fungere ihht testen.

 

 

[TestMethod]
public void TestFiskTilhorerRange()
{
ISpesifikasjon forsteRange = new FiskTilhørerRangeById(0,5);

Fisk fisk = new Fisk(3, "Malle");

Assert.IsTrue(forsteRange.TilfredsstillesAv(fisk);
}

[TestMethod]
public void TestFiskTilhorerIkkeRange()
{
ISpesifikasjon forsteRange = new FiskTilhørerRangeById(0,5);

Fisk fisk = new Fisk(6, "Ørret");

Assert.IsFalse(forsteRange.TilfredsstillesAv(fisk);
}

 

 

Når du har gjort dette, så kan du skrive en test for en liste. Så etter hvert kan du skrive følgende test.

 

[TestMethod]
public void TestFiskTilhorerEnAvRangene()
{
ISpesifikasjon forsteRange = new FiskTilhørerRangeById(0,5);
ISpesifikasjon andreRange = new FiskTilhørerRangeById(7,10);

ISpesifikasjon ellerRangene = forsteRange || andreRange;

Fisk fisk = new Fisk(3, "Malle");

Assert.IsTrue(alleRangene.TilfredsstillesAv(fisk);
}

[TestMethod]
public void TestFiskTilhorerIngenAvRangene()
{
ISpesifikasjon forsteRange = new FiskTilhørerRangeById(0,5);
ISpesifikasjon andreRange = new FiskTilhørerRangeById(7,10);

ISpesifikasjon alleRangene = forsteRange || andreRange;

Fisk fisk = new Fisk(6, "Ørret");

Assert.IsFalse(alleRangene.TilfredsstillesAv(fisk);
}

 

For å teste lister så gjør du bare følgende:

 

[TestMethod]
public void TestFiskeneTilhorerRangen()
{
ISpesifikasjon forsteRange = new FiskTilhørerRangeById(0,5);

List<Fisk> fiskene = new List<Fisk>
									   {
										   new Fisk(1, "Torsk"),
										   new Fisk(2, "Sei"),
										   new Fisk(6, "Ørret")
									   };

Assert.AreEqual(2, fiskene.Where(fisk => forsteRange.TilfredsstillesAv(fisk)).Count());
}

 

Håper dette er forståelig.

 

Edit: Noen syntaxfeil her og der!

Endret av BennyXNO
Lenke til kommentar

Hvis du vil ha en mer generisk Metode, kan du gjøre følgende:

 

[TestMethod]
public void TestFiskIdTilhørerMellomVerdiRange()
{
  ISpesifikasjon spesifikasjon = new HelTallsRangeSpesifikasjon(0, 5);

  Fisk malle = new Fisk(3, "Malle);

  Assert.IsTrue(spesifikasjon.TilfredsstillesAv(malle.Id));
}

TestMethod]
public void TestFiskeneTilhorerRangen()
{
ISpesifikasjon spesifikasjon = new HelTallsRangeSpesifikasjon(0,5);

List<Fisk> fiskene = new List<Fisk>
									   {
										   new Fisk(1, "Torsk"),
										   new Fisk(2, "Sei"),
										   new Fisk(6, "Ørret")
									   };

Assert.AreEqual(2, fiskene.Where(fisk => spesifikasjon.TilfredsstillesAv(fisk.Id)).Count());
}

Endret av BennyXNO
Lenke til kommentar

Opprett en konto eller logg inn for å kommentere

Du må være et medlem for å kunne skrive en kommentar

Opprett konto

Det er enkelt å melde seg inn for å starte en ny konto!

Start en konto

Logg inn

Har du allerede en konto? Logg inn her.

Logg inn nå
×
×
  • Opprett ny...