Laden...

Verzweifel an dynamischer IQueryable Zusammensetzung

Erstellt von Curse4Life vor 12 Jahren Letzter Beitrag vor 12 Jahren 10.487 Views
C
Curse4Life Themenstarter:in
452 Beiträge seit 2005
vor 12 Jahren
Verzweifel an dynamischer IQueryable Zusammensetzung

verwendetes Datenbanksystem: EF 4.1 Code first, MS SQL 2008

Hi, ich sitze hier im Urlaub und meine Freundin meckert schon weil ich vor meinem Laptop am verzweifeln bin, ich hoffe nun ihr könnt mir weiter helfen!

Ich versuche das Problem und die Situation so einfach wie möglich zu beschreiben, weil es ziemlich kompliziert ist.
Ich werde deshalb auch meist vereinfachten und minimierten Code verwenden.

Ich habe ein Projekt (MVC Webseite) mit einer Klasse "ListSeries". Nennen wir diese Typen von Klassen Definitionsklassen.


public class ListSeries<Series> : IGridModel
{
	public List<GridColumn> Columns;
	

	public ListSeries()
	{
		this.AddColumn(model => model.Title);
	}
	
	
	public void AddColumn<Series, TProperty>(Expression<Func<Series, TProperty>> expression)
	{
		GridColumn newColumn = new GridColumn("Name", expression);
		this.Columns.Add(newColumn);
	}

	
	public IQueryable<Series> GetData()
	{
		DBContext db = new DBContext();
		return db.Series.AsQueryable<Series>();
	}
}

Was diese im Prinzip macht, sammelt in sich GridColumns, diese haben vereinfacht zwei Eigenschaften nämlich Namen und die Expression.
Von diesen Klassen kann es n Stück geben und alle können einen anderen "Basistyp" haben, das was hier im Beispiel "Series" ist.

Das war die eine Hälfte.

Jetzt habe ich noch eine Klasse "Worker" mit der Funktion "PrepareData" die irgendwann mal automatisch aufgerufen wird.
Ich weiß hier nicht welche der Definitionsklassen sie aufgerufen hat, deshalb erstelle ich mir per Reflection eine Instanz.
Dann rufe ich die GetData Methode der Definitionsklasse auf.

BIS HIER HIN FUNKTIONIERT ALLES

Jetzt kommt der Teil den ich seit 2 Tagen intensiver Arbeit nicht hinbekomme 😭
Ich möchte nun gerne dynamisch die zürückgegebene IQueryable in dieser Klasse filtern.
Es können n Filter dieser Methode "PrepareData" übergeben werden, ich habs jetzt auf einen begrenzt.
In der Methode ApplyFilter seht ihr meine Verzweiflung und meine Versuche.


public class Worker
{
	public IQueryable PrepareData(GridColumn column, String value)
	{
		IGridModel model = (IGridModel)Activator.CreateInstance(assembly, id).Unwrap();
		
		MethodInfo GetDataMethod = model.GetType().GetMethod("GetData");
		Object result = GetDataMethod.Invoke(model, null);
		
		return this.ApplyFilter((IQueryable)result, column.Expression.Body, value);
	}
	
	
	private IQueryable ApplyFilter(IQueryable source, Expression columnExpression, String value)
	{
		MethodInfo contains = typeof(String).GetMethod("Contains");
		ParameterExpression param = Expression.Parameter(source.ElementType, "model");
		MethodCallExpression methodCall = Expression.Call(columnExpression, contains, Expression.Constant(value, typeof(String)));

		LambdaExpression lamda = Expression.Lambda(methodCall, param);
		
		
		// Bis hier hin schon nicht schlecht???
		
		
		
		//MethodCallExpression resultExp = Expression.Call(typeof(Queryable), "Where", new Type[] { source.ElementType, property.PropertyType }, source.Expression, Expression.Quote(lamda));
		
		
		//Delegate sss = asd.Compile();
		//Expression<Func<T, Boolean>> predicate = 
		
		//Func<T, Boolean> blub = new Func<T, bool>()
		//Expression asds = source.Expression;
		
		//BinaryExpression asd = Expression.AndAlso(source.Expression, predicate);
		//source.Expression
		//MethodCallExpression containsMethod = Expression.Call(typeof(String).GetMethod("Contains"));
		//source.Provider.CreateQuery<T>(resultExp);
		//return source.Provider.CreateQuery(lamda);
	}
}

Wie füge ich nun also die dynamisch erstellte Expression der IQueryable hinzu, so das diese bei einem .ToList() z.B. mit ausgeführt wird?

Ich hoffe ihr könnt mir helfen!

Liebe Grüße

2.891 Beiträge seit 2004
vor 12 Jahren

Nochmal zur Klarstellung: Du willst also zu einem IQueryable (den generischen Parameter kennst du zur Compilezeit nicht) eine Where-Einschränkung hinzufügen. Kannst du mal bitte eine entsprechende Query zeigen, die dann generiert werden soll?

T
156 Beiträge seit 2010
vor 12 Jahren

Hallo,

bin gerade ein bisschen kurz angebunden, da auf Arbeit. Aber denke, dieses Beispiel wird Dir weiterhelfen:


private IQueryable<T> ApplyFilter<T>(IQueryable<T> source, Expression<Func<T, bool>> filter)
{
   return source.Where(filter);
}

Der Aufruf ist dann wie folgt:


IQueryable<string> unfilterdList = new List<string>() { "wert1", "wert2", "wert3", "wert4", "wertx", "wertx", "dummy1", "dummy2" }.AsQueryable();
var werte = ApplyFilter(unfilterdList, (x) => x.StartsWith("wert"));
var werteMitX = ApplyFilter(werte, (x) => x.EndsWith("x")); 

LG, Marko

C
Curse4Life Themenstarter:in
452 Beiträge seit 2005
vor 12 Jahren

@dN!3L

Du hast exakt erkannt was ich machen möchte!
Gott sei dank, ich dachte ich hätte es vielleicht viel zu kompliziert erklärt 😃

Und mir viel noch eine Sache ein, die ich nicht beschrieben hatte.
Eine Serie hat natürlich Staffeln und eine Staffel hat eine Nummer.

Ich könnte also auch in der Definitisionsklasse folgendes machen:


// Macht keinen Sinn, nur als Beispiel
this.AddColumn(model => model.FirstSeason.Number);

Das nur noch als Zusatzinfo.

Und es soll am Ende so sein als hätte ich in die direkte Abfrage folgendes geschrieben:


db.Series.Where(model => model.Title.Contains(value) && model.FirstSeason.Number.Contains(value2))

Also einfach das ganze mit && an die IQueryable dranhängen, das würde ich gerne machen.

Liebe Grüße

2.891 Beiträge seit 2004
vor 12 Jahren
  
db.Series.Where(model => model.Title.Contains(value) && model.FirstSeason.Number.Contains(value2))  
  

Das Title.Contains und FirstSeason.Number.Contains ist also immer fest und das einzige variable ist value (bzw. value2)?
Kennst du den Typ von model zur Compilezeit?

C
Curse4Life Themenstarter:in
452 Beiträge seit 2005
vor 12 Jahren

Nein, diese Expressions wie Title oder FirstSeason.Number kommen dynamisch aus der Definitionsdatei, da lege ich ja Columns mit diesen Expressions an.

Und auch nehme ich nicht alle Columns, sondern nur die die per HTTP gepostet werden. Aber darüm kümmer ich mich schon.

Ich gehe dann halt durch alle Columns aus der Definitionsdatei und rufe die ApplyFilter Methode für die auf, die in Request.Form exestieren.

Und die sollen dann halt alle mit ".Contains" und "&&" der IQueryable hinzugefügt werden.


IGridModel model = (IGridModel)Activator.CreateInstance(assembly, id).Unwrap();
		
MethodInfo GetDataMethod = model.GetType().GetMethod("GetData");
Object result = GetDataMethod.Invoke(model, null);

// Hier in der GridColumn befindet sich die Expression als Eigenschaft, zur Eigenschaft des zugrundeligenden Models der Definitionsklasse, siehe mein erster Post
foreach (GridColumn loopColumn in model.Columns)
{
	if (Request.Form.AllKeys.Contains(loopColumn.ColumnName))
	{
                // Hier result = db.Series;
		result = this.ApplyFilter((IQueryable)result, loopColumn.Expression.Body, Request.Form[loopColumn.ColumnName]);
                // 1. Durchlauf db.Series.Where(model => model.Title.Contains(""));
                // 2. Durchlauf db.Series.Where(model => model.Title.Contains("") && model.FirstSeason.Number.Contains("2"));
	}
}

...
...


private IQueryable ApplyFilter(IQueryable source, Expression columnExpression, String value)
{
  // Sprachlich verdeutlicht
  source.AddAndCondition(columnExpression.Contains(value));
  return source;
}

Wenn es doch nur so einfach wäre 😃

1.378 Beiträge seit 2006
vor 12 Jahren

Das beste mit dem ich in kürze aufwarten kann:



    public class Person
    {
        public string Vorname { get; set; }
        public string Nachname { get; set; }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var persons = new List<Person>
            {
                new Person{ Vorname = "Max", Nachname = "Mustermann"}
            }.AsQueryable();

            persons = persons.Contains("Vorname", "x");

            foreach (var person in persons)
            {
                Console.WriteLine(person.Nachname + " " + person.Vorname);
            }
        }
    }

    public static class Extensions
    {
        public static IQueryable<T> Contains<T>(this IQueryable<T> queryable, string propertyName, string text)
        {
            PropertyInfo propertyInfo = typeof(T).GetProperty(propertyName);

            ParameterExpression instanceExpression = Expression.Parameter(typeof(T), "model");

            MemberExpression propertyExpression = Expression.Property(instanceExpression, propertyInfo);

            MethodInfo methodInfo = typeof(string).GetMethod("Contains");

            ConstantExpression stringContainsParameter = Expression.Constant(text, typeof(string));

            MethodCallExpression stringContainsCall = Expression.Call(propertyExpression, methodInfo, stringContainsParameter);


            Expression<Func<T, bool>> expression = Expression.Lambda<Func<T, bool>>(stringContainsCall, false, instanceExpression);

            return queryable.Where(expression);
        }
    }

Lg, XXX

C
Curse4Life Themenstarter:in
452 Beiträge seit 2005
vor 12 Jahren

Hi, also dank euch und dem Internet sieht es momentan so aus:


// Get complete data
MethodInfo GetDataMethod = model.GetType().GetMethod("GetData");
MethodInfo ApplyFilterMethod = this.GetType().GetMethod("ApplyFilter");
Object result = GetDataMethod.Invoke(model, new Object[] { parameters });
Type dataSource = ((IQueryable)result).ElementType;
ApplyFilterMethod = ApplyFilterMethod.MakeGenericMethod(new System.Type[] { ((IQueryable)result).ElementType });


var ToListMethod = typeof (Enumerable).GetMethod("ToList");
ToListMethod = ToListMethod.MakeGenericMethod(dataSource);
var resultList = ToListMethod.Invoke(null, new Object[] { result });

Und die ApplyFilter Methode sieht so aus:


public IQueryable<T> ApplyFilter<T>(IQueryable<T> source, Expression columnExpression, String value)
{
	MethodInfo contains = typeof(String).GetMethod("Contains");
	ParameterExpression param = Expression.Parameter(source.ElementType, "model");
	MethodCallExpression methodCall = Expression.Call(columnExpression, contains, Expression.Constant(value, typeof(String)));

	LambdaExpression lamda = Expression.Lambda(methodCall, param);
	Expression<Func<T, Boolean>> expression = Expression.Lambda<Func<T, Boolean>>(methodCall, false, param);

	return source.Where(expression);
}

Aber wenn ich jetzt die ToList invoke dann bekomme ich folgende Exception:


{"Der Parameter 'model' wurde nicht im angegebenen 'LINQ to Entities'-Abfrageausdruck gebunden."}

Also, ich denke wir sind ganz nah dran, Ideen?

Liebe Grüße

S
417 Beiträge seit 2008
vor 12 Jahren

Hi,

kannst Du vielleicht den zusammenhängenden Code posten?
Irgendwie fehlt doch da der Aufruf der ApplyFilter-Methode, oder?

1.378 Beiträge seit 2006
vor 12 Jahren

Das ist ein problematischer Fehlern soweit ich mich erinnere: In einer Expression, dürfen Parameter nur einmal erstellt werden(sofern sie den gleichen Namen haben) und müssen wiederverwendet werden. Deine Expression, die von außerhalb kommt, hat bereits einen Namen für die model Variable und das schlägt sich mit der von dir erzeugten Variable.

Aber wieso versteifst du dich auf diese Expressions? Reichts nicht, wenn du zu den Columns einfach den PropertyNamen übergibst? Was beinhalten diese Expressions, dass sie so wichtig machen?

Lg XXX

C
Curse4Life Themenstarter:in
452 Beiträge seit 2005
vor 12 Jahren

Ohh ja, das ist ja das geniale, die Expressions!
Ich habe das vorher mit Strings gemacht ala "Series.Title" oder "Series.FirstSeason.Number", dann das ganze mit Split getrennt und dann Rekursiv durchgelaufen und durchgehangelt mit Reflection durch die Objekte.
Das war aber ugly!

Nachteile:

  • Fehleranfällig, Tipfehler in dem String der ja durchaus ziemlich lang werden kann
  • Tiparbeit, kein Intillisense
  • Ugly Rekursive Funktion
  • Ich kann ja jetzt auch so schöne Sachen machen wie:
    Series.FirstSeason.FirstEpisode.FirstAirdDate.ToShortDataString()

Ebenfalls lerne ich ja jetzt auch super viel, es das erst mal das ich SO mit Expressions arbeite. Und ich würde jetzt so knapp vor dem Ziel wirklich ungern aufgeben, ich habe ja jetzt auch schon alles drum und dran darauf gebaut.

Also, wie kann ich dieses model Problem den lösen, mir ist es ja super egal wie der Parameter heißt, im Gegenteil, wäre natürlich klasse, wenn ich in der Definitionsdatei mal "model" und mal "schnubbelDieDupp" nutzen könnte, aber das wäre nur ein nice to have!

Der ganze Code, für die die mit MVC was anfangen können, der Controller, der die Datenbasis aus der Definitionsdatei holt und später als JSon zurück gibt (Nach dem ich ToList gemacht habe)


namespace lib
{
	using ...;


	public class GridController : Controller
	{
		public ActionResult GetData(String assembly, String id, Int32 page, Int32 rows, String sidx, String sord)
		{
			IGridModel model = (IGridModel)Activator.CreateInstance(assembly, id).Unwrap();
			model.DesignGrid();


			// Get url parameters and form parameters and delete the default grid parameters
			Dictionary<String, String> parameters = new Dictionary<String, String>();
			foreach (String loopFormValue in Request.Form.AllKeys)
			{
				if (!loopFormValue.StartsWith("jqgc_") && loopFormValue != "_search" && loopFormValue != "nd" && loopFormValue != "rows" && loopFormValue != "page" && loopFormValue != "sidx" && loopFormValue != "sord")
				{
					parameters.Add(loopFormValue, Request.Form[loopFormValue]);
				}
			}

			
			// Get complete data
			MethodInfo GetDataMethod = model.GetType().GetMethod("GetData");
			MethodInfo ApplyFilterMethod = this.GetType().GetMethod("ApplyFilter");
			Object result = GetDataMethod.Invoke(model, new Object[] { parameters });
			Type dataSource = ((IQueryable)result).ElementType;
			ApplyFilterMethod = ApplyFilterMethod.MakeGenericMethod(new System.Type[] { ((IQueryable)result).ElementType });

			
			try
			{								
				foreach (GridColumn loopColumn in model.Columns)
				{
					if (Request.Form.AllKeys.Contains(loopColumn.ColumnName))
					{
						result = ApplyFilterMethod.Invoke(this, new Object[] { result, loopColumn.Expression.Body, Request.Form[loopColumn.ColumnName] });
					}
				}
			}
			catch (Exception ex) 
			{
				result = "";
			}


			var ToListMethod = typeof (Enumerable).GetMethod("ToList");
			ToListMethod = ToListMethod.MakeGenericMethod(dataSource);
			var resultList = ToListMethod.Invoke(null, new Object[] { result });

			
			return Content("");
		}


		public IQueryable<T> ApplyFilter<T>(IQueryable<T> source, Expression columnExpression, String value)
		{
			MethodInfo contains = typeof(String).GetMethod("Contains");
			ParameterExpression param = Expression.Parameter(source.ElementType, "model");
			MethodCallExpression methodCall = Expression.Call(columnExpression, contains, Expression.Constant(value, typeof(String)));

			LambdaExpression lamda = Expression.Lambda(methodCall, param);
			Expression<Func<T, Boolean>> expression = Expression.Lambda<Func<T, Boolean>>(methodCall, false, param);

			return source.Where(expression);
		}
	}
}

Eine Definitionsdatei:


namespace project
{
	using ...;


	public class List : GridModel<Models.Database.Series>
	{
		public List() {  }


		public override void DesignGrid()
		{
			this.Caption = "Serien";
			this.ShowFilterToolbar = true;
			
	
			this.AddColumn(model => model.Title);
			this.AddColumn(model => model.FirstSeason.Series.OriginalTitle);
			this.AddColumn(model => model.Updated.ToShortDateString());
		}


		public override IQueryable<Series> GetData(Dictionary<String, String> parameters)
		{
			DBContext db = new DBContext();

			return db.Series.AsQueryable<Series>();
		}
	}
}

Und die GridColumn


namespace lib
{
	using ...;


	public class GridColumn
	{
		public String ColumnName { get; set; }
		public LambdaExpression Expression { get; set; }
		public GridColumnDesign Design { get; set; }
		

		public GridColumn(LambdaExpression expression, String alias = "")
		{
			this.Expression = expression;
			this.ColumnName = "jqgc_";
			if (alias == "")
			{
				if (expression.Body.NodeType == ExpressionType.MemberAccess)
				{
					this.ColumnName += ExpressionHelper.GetExpressionText(expression).Replace(".", "_").ToLower();
				}
				else if (expression.Body.NodeType == ExpressionType.Call)
				{
					this.ColumnName += "method";
				}
			}
			else
			{
				this.ColumnName += alias.ToLower();
			}

			this.Design = new GridColumnDesign(this);
		}


		public GridColumnDesign StartDesign()
		{
			return this.Design;
		}


		public String GenerateHtml()
		{
			StringBuilder result = new StringBuilder();

			result.Append("{");
			result.Append(String.Format("name:'{0}', index:'{0}'", this.ColumnName));
			result.Append("}");

			return result.ToString();
		}
	}
}

So, das sollte alles relevante sein.
Ich hoffe wir kriegen gemeinsam das ganz gelöst, ich danke auf jeden Fall jetzt schon mal für die vielen neuen Information die ich gelernt habe!

Liebe Grüße

2.760 Beiträge seit 2006
vor 12 Jahren

Ob das mit dem EF auch funktioniert ist zwar fraglich aber ein Versuch wäre es doch Wert: DynamicLINQ mit Linq2SQL funktioniert es vorzüglich.

C
Curse4Life Themenstarter:in
452 Beiträge seit 2005
vor 12 Jahren

Also ich brauche immer noch dringend Hilfe! 😭

Ich habe jetzt mal mit diesem Framework rumgespielt, aber auch damit komme ich nicht zum Ziel, es ist zum heulen!

2.760 Beiträge seit 2006
vor 12 Jahren

Bin erst jetzt dazu gekommen mir mal den kompletten Post anzusehen. Soll das ganze dann später ähnlich funktionieren wie das DataColumn.DisplayExpression Property? Das geht so wie du das gerne hättest mit dieser Library nicht.

Bleibt die Frage ob es überhaupt nötig ist. Würdest du es komplett zur Laufzeit evaluieren könntest du die Spaltendefinitionen in einer Konfigurationsdatei unterbringen und auch z.B. je nach Benutzerberechtigung setzen. Auch mit ein bisschen Sorgfalt und Test kannst du Fehler in der Spaltendefinition nahezu ausschließen.

ich sitze hier im Urlaub und meine Freundin meckert schon weil ich vor meinem Laptop am verzweifeln bin

Hmm... hat sie irgendwie recht. Viel spass noch im Urlaub :evil:

1.378 Beiträge seit 2006
vor 12 Jahren

Hab gerade ein relativ simples Beispiel hier gebastelt aber keine Ahnung ob das im EF durchgeht:


        public static IQueryable<T> Contains<T>(this IQueryable<T> queryable, Expression<Func<T, string>> expression, string text)
        {
            Expression<Func<T, bool>> filterExpression = CreateFilterExpression(expression, text);

            return queryable.Where(t => expression.Compile()(t).Contains(text));
        }

Lg XXX

1.378 Beiträge seit 2006
vor 12 Jahren

Und das ganze nochmal als Expression:


    public class Person
    {
        public string Vorname { get; set; }
        public string Nachname { get; set; }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var persons = new List<Person>
            {
                new Person{ Vorname = "Max", Nachname = "Mustermann"},
                new Person{ Vorname = "DEF", Nachname = "ABC"},
            }.AsQueryable();

            persons = persons.Contains(p => p.Vorname, "M");

            foreach (var person in persons)
            {
                Console.WriteLine(person.Nachname + " " + person.Vorname);
            }
        }
    }

    public static class Extensions
    {
        public static IQueryable<T> Contains<T>(this IQueryable<T> queryable, Expression<Func<T, string>> propertyAccessExpression, string text)
        {
            Expression<Func<T, bool>> filterExpression = CreateFilterExpression(propertyAccessExpression, text);

            return queryable.Where(filterExpression);
        }

        private static Expression<Func<T, bool>> CreateFilterExpression<T>(Expression<Func<T, string>> lambdaExpression, string text)
        {
            MethodCallExpression stringContainsCall = Expression.Call(
                GetMethodCallExpression(lambdaExpression),
                typeof(string).GetMethod("Contains"),
                Expression.Constant(text, typeof(string)));

            return Expression.Lambda<Func<T, bool>>(stringContainsCall, false, lambdaExpression.Parameters.First());
        }

        private static MethodCallExpression GetMethodCallExpression<T>(Expression<Func<T, string>> lambdaExpression)
        {
            ParameterExpression instanceExpression = lambdaExpression.Parameters.First();

            Func<T, string> compiledLambdyExpression = lambdaExpression.Compile();

            MethodInfo method = compiledLambdyExpression.GetType().GetMethod("Invoke");

            return Expression.Call(lambdaExpression, method, instanceExpression);
        }
    }

Lg XXX

S
417 Beiträge seit 2008
vor 12 Jahren

Hi,

ich denke Du musst einfach den Parameter aus der Expression wiederverwenden.
Ändere mal deine Methode ApplyFilter wie folgt ab:

public IQueryable<T> ApplyFilter<T>(IQueryable<T> source, LambdaExpression columnExpression, String value)
{
	MethodInfo contains = typeof(String).GetMethod("Contains");
	ParameterExpression param = columnExpression.Parameters.First();
	MethodCallExpression methodCall = Expression.Call(columnExpression.Body, contains, Expression.Constant(value, typeof(String)));

	Expression<Func<T, Boolean>> expression = Expression.Lambda<Func<T, Boolean>>(methodCall, false, param);

	return source.Where(expression);
}

Und beim Aufruf übergibst Du dann einfach nur die Expression (nicht den Body):

result = ApplyFilterMethod.Invoke(this, new Object[] { result, loopColumn.Expression, Request.Form[loopColumn.ColumnName] });

Hoffe das klappt. Kann es hier leider schlecht testen.

1.378 Beiträge seit 2006
vor 12 Jahren

Das Konstrukt ergibt sowas in der Art:

model => (model => model.Property).Contains("something")

was natürlich nicht kompilierbar ist. Die Expression welche übergeben wird muss erst ausgewertet werden bevor man eine Contains Method darauf anwenden kann. In meinem Beispiel macht das die GetMethodCallExpression Methode.

Fraglich ist ob der MethodCall "Invoke" beim EF durchgeht.

Lg XXX

S
417 Beiträge seit 2008
vor 12 Jahren

Das Konstrukt ergibt sowas in der Art:

model => (model => model.Property).Contains("something")  

was natürlich nicht kompilierbar ist.

Hast Du das denn getestet oder ist das nur eine Vermutung?
Ich habe das ganze mal schnell runtergeschrieben und bei mir funktioniert das (natürlich erstmal ohne EF):

class Program
{
	[STAThread]
	static void Main(string[] args)
	{
		MethodInfo getDataMethod = typeof(GridModel).GetMethod("GetData");
		object result = getDataMethod.Invoke(null, null);
		Type dataSource = ((IQueryable)result).ElementType;

		MethodInfo applyFilterMethod = typeof(Program).GetMethod("ApplyFilter");
		applyFilterMethod = applyFilterMethod.MakeGenericMethod(new System.Type[] { ((IQueryable)result).ElementType });

		Expression<Func<Series, string>> exp1 = f => f.Title;
		result = applyFilterMethod.Invoke(null, new object[] { result, exp1, "test" });
	}

	public static IQueryable<T> ApplyFilter<T>(IQueryable<T> source, LambdaExpression columnExpression, String value)
	{
		MethodCallExpression methodCall = Expression.Call(columnExpression.Body, "Contains", null, Expression.Constant(value, typeof(String)));
		var param = columnExpression.Parameters.First();
		LambdaExpression lamda = Expression.Lambda(methodCall, param);
		Expression<Func<T, Boolean>> expression = Expression.Lambda<Func<T, Boolean>>(methodCall, false, param);

		return source.Where(expression);
	}
}

public class Series
{
	public string Title { get; set; }
	public DateTime Updated { get; set; }
}

public class GridModel
{
	public static IQueryable<Series> GetData()
	{
		List<Series> list = new List<Series>();
		list.Add(new Series() { Title = "test", Updated = DateTime.Now });

		return list.AsQueryable();
	}
}
1.378 Beiträge seit 2006
vor 12 Jahren

Sagen wirs so: Ich habs getestet und nur auf meine Variante zum Laufen gebracht. 😛

Aber ich schau mir gleich auch mal deine Variante an.

//EDIT: Überzeugt; Funktioniert so auch und ist damit auch viel schlanker. 😃

C
Curse4Life Themenstarter:in
452 Beiträge seit 2005
vor 12 Jahren

WOW, danke euch allen, da habt ihr heute Morgen aber fleißig gepostet und ich glaub es nicht, dass es nur an der einen Zeile lag, das ich den Parameter aus der columnExpression hole. AnDenKopfKlatsch

Also, das funktioniert jetzt einwandfrei!!!

Jetzt habe ich nur noch ein allerletztes Problem was diese Thematik angeht!
Und da bräuchte ich noch eine Lösung/Workaround für, wäre super wenn ihr mir dabei noch helfen könntet, dann verspreche ich auch, mindestens einen Monat keine Fragen mehr im Forum zu stellen! 😉

Und zwar folgendes, ich benutze das neue CodeFirst Framework von Microsoft, dass so weit ich weiß auf EF basiert?!
Meine Klasse Series sieht nun so aus:


class Series
{
	[Key]
	public String Id { get; set; }
	
	
	public String OriginalTitle { get; set; }
	
	
    [NotMapped]
	public String Title 
	{ 
		get
		{
			// Holt den übersetzen Titel z.B. Original Title ist "The X-Files" und hier würde dann bei einem deutschen "Akte X" zurückgegeben.
			TranslationPool.GetTranslation(this.Id);
		}
	}
}

So, jetzt funktioniert das, was wir zusammen programmiert haben perfekt für "OriginalTitle" aber leider nicht für "Title", da wirft ToList dann eine Exception:


"Das angegebene Typelement 'Title' wird in 'LINQ to Entities' nicht unterstützt. Es werden nur Initialisierer, Entitätselemente und Entitätsnavigationseigenschaften unterstützt."

Kann ich dieses Problem irgendwie lösen?

Ich weiß quasie woran es liegt, weil Linq nicht weiß wie er Title in SQL übersetzen soll, aber ich weiß eben leider nicht wie man es lösen kann 😭
Wieder was zu lernen? Oder ist das überhaupt nicht möglich?

Und wenn es nicht möglich ist wie kann ich denn wenigstens vor der ApplyFilter Methode rausfinden ob es sich um so eine nicht unterstützte Property handelt, so das ich dann ApplyFilter nicht aufrufe.

Liebe Grüße

771 Beiträge seit 2009
vor 12 Jahren

Hi,

ich denke, es könnte am fehlenden Setter bei Title liegen. Du kannst ja mal testweise eine leere set-Methode erstellen.

C
Curse4Life Themenstarter:in
452 Beiträge seit 2005
vor 12 Jahren

Nein, daran liegt es nicht, hab es auch noch mal zur Sicherheit getestet.
Aber danke.

S
417 Beiträge seit 2008
vor 12 Jahren

Da Du ja quasi eine Client-seitige Methode in dem Title-Property verwendest, müsstest Du ja eh erst alle Zeilen lesen, damit Du entscheiden kannst ob gefiltert werden muss. Du könntest Dir natürlich eine C# Methode erstellen, welche vom SQL Server verwendet werden kann (ein Beispiel ist hier: http://www.setfocus.com/technicalarticles/articles/clrfunctionforsqlserver.aspx).
Aber damit bindest Du Dich fest an den SQL Server. Außerdem weiss ich nicht, wie Du das dann in deiner ApplyFilter Methode verwenden kannst.

Ansonsten müsstest Du die NotMapped-Properties separat behandeln und später mit den anderen Ergebnissen mergen.
Die NotMapped-Properties kannst Du in deiner ApplyFilter wie folgt abfangen:

public static IQueryable<T> ApplyFilter<T>(IQueryable<T> source, LambdaExpression columnExpression, String value)
{
	var mi = (MemberInfo)((dynamic)columnExpression.Body).Member;
	var notMappedAttribute = mi.GetCustomAttributes(typeof(NotMappedAttribute), true).FirstOrDefault();

	if (notMappedAttribute != null)
		return source;
	
	...
}