I'm new on CSLA.Net and I've some difficult to remove a child object from a list.
I've 3 classes :
Scenario : BusinessBase<Scenario>
ScenarioPermissions : BusinessListBase<ScenarioPermissions, ScenarioPermission>
ScenarioPermission : BusinessBase<ScenarioPermission>
Class Scenario contains one field of type ScenarioPermissions.
When I Try to do Scenario.Permissions.Add(obj) and Scenario.Save(), it works correctly.
But if I want to do Scenario.Permissions.Remove(obj) and Scenario.Save(), it continues without deleting my ScenarioPermission object from the database.
public class Scenario : BusinessBase<Scenario>, IMakeCopy
{
private static readonly PropertyInfo<ScenarioPermissions> m_PermissionsProperty = RegisterProperty<ScenarioPermissions>(c => c.m_Permissions);
public ScenarioPermissions m_Permissions
{
get
{
if (!FieldManager.FieldExists(m_PermissionsProperty))
{
SetProperty(m_PermissionsProperty, ScenarioPermissions.NewPermissions());
}
return GetProperty(m_PermissionsProperty);
}
}
public ReadOnlyCollection<Model.Users.User> Permissions
{
get
{
var collection = new List<Model.Users.User>();
foreach (var item in m_Permissions)
{
collection.Add(Model.Users.User.GetUser(item.UserID));
}
//Adds administrators users...
var admins = Users.Users.GetUsers(true).Where(u => u.Role.InvariantName == "SystemAdministrator" || u.Role.InvariantName == "SuperAdministrator");
foreach (var item in admins)
{
collection.Add(item);
}
return new ReadOnlyCollection<Model.Users.User>(collection.OrderBy(u => u.FullName).ToArray());
}
}
public void AddPermission(Model.Users.User user)
{
if (user == null)
{
throw new ArgumentNullException("user");
}
ScenarioPermission permission = ScenarioPermission.NewPermission(this, user);
if (!this.m_Permissions.Contains(permission))
{
this.m_Permissions.Add(permission);
}
}
public void RemovePermission(Model.Users.User user)
{
if (user == null)
{
throw new ArgumentNullException("user");
}
ScenarioPermission permission = this.m_Permissions.FirstOrDefault(p => p.UserID == user.Id);
if (permission != null)
{
this.m_Permissions.Remove(permission);
}
}
protected override void DataPortal_Update()
{
using (var ctx = DbContextManager<DatabaseContext>.GetManager())
{
var context = ctx.DbContext;
var scenarioId = ReadProperty(m_IdProperty);
var scenario = context.Scenarios.FirstOrDefault(s => s.Id == scenarioId);
if (scenario != null)
{
scenario.Name = ReadProperty(m_NameProperty);
//Some codes....
context.SaveChanges();
}
FieldManager.UpdateChildren(this);
}
}
protected override void DataPortal_DeleteSelf()
{
DataPortal_Delete(ReadProperty(m_IdProperty));
}
private void DataPortal_Delete(Guid id)
{
using (var contextManager = DbContextManager<DatabaseContext>.GetManager())
{
var context = contextManager.DbContext;
var scenario = context.Scenarios.FirstOrDefault(s => s.Id == id);
if (scenario != null)
{
context.Scenarios.Remove(scenario);
context.SaveChanges();
}
}
Dispatcher.CurrentDispatcher.Invoke(new Action(() => ScenarioList.Delete(id)));
}
}
public class ScenarioPermissions : BusinessListBase<ScenarioPermissions, ScenarioPermission>
{
public static ScenarioPermissions NewPermissions()
{
return DataPortal.Create<ScenarioPermissions>();
}
public static ScenarioPermissions GetUsersByScenario(Scenario scenario)
{
return DataPortal.FetchChild<ScenarioPermissions>(scenario);
}
private void Child_Fetch(Scenario obj)
{
using (var ctx = DbContextManager<DatabaseContext>.GetManager())
{
var context = ctx.DbContext;
var scenario = context.Scenarios.Where(s => s.Id == obj.Id).FirstOrDefault();
if (scenario != null)
{
foreach (var item in scenario.Users)
{
this.Add(ScenarioPermission.NewPermission(scenario.Id, item.Id));
}
}
}
}
}
public class ScenarioPermission : BusinessBase<ScenarioPermission>
{
private static readonly PropertyInfo<Guid> m_ScenarioID = RegisterProperty<Guid>(p => p.ScenarioID);
public Guid ScenarioID
{
get { return GetProperty(m_ScenarioID); }
private set { SetProperty(m_ScenarioID, value); }
}
private static readonly PropertyInfo<int> m_UserID = RegisterProperty<int>(p => p.UserID);
public int UserID
{
get { return GetProperty(m_UserID); }
private set { SetProperty(m_UserID, value); }
}
public static ScenarioPermission NewPermission(Scenario scenario, Model.Users.User user)
{
return NewPermission(scenario.Id, user.Id);
}
public static ScenarioPermission NewPermission(Guid scenarioID, int userID)
{
var newObj = DataPortal.CreateChild<ScenarioPermission>();
newObj.ScenarioID = scenarioID;
newObj.UserID = userID;
return newObj;
}
private ScenarioPermission() { /* Used for Factory Methods */}
private void Child_Insert(Scenario scenario)
{
DataPortal_Insert();
}
private void Child_DeleteSelf(Scenario scenario)
{
DataPortal_DeleteSelf();
}
private void Child_DeleteSelf()
{
DataPortal_DeleteSelf();
}
protected override void DataPortal_Insert()
{
using (var ctx = DbContextManager<DatabaseContext>.GetManager())
{
var context = ctx.DbContext;
var scenario = context.Scenarios.FirstOrDefault(s => s.Id == ScenarioID);
var user = context.Users.FirstOrDefault(u => u.Id == UserID);
if (scenario != null && user != null)
{
scenario.Users.Add(user);
context.SaveChanges();
}
}
}
protected override void DataPortal_DeleteSelf()
{
using (var ctx = DbContextManager<DatabaseContext>.GetManager())
{
var context = ctx.DbContext;
var scenario = context.Scenarios.FirstOrDefault(s => s.Id == ScenarioID);
var user = context.Users.FirstOrDefault(u => u.Id == UserID);
if (scenario != null && user != null)
{
if (scenario.Users.Contains(user))
{
scenario.Users.Remove(user);
context.SaveChanges();
}
}
}
}
public override bool Equals(object obj)
{
// If parameter is null return false.
if (obj == null)
{
return false;
}
// If parameter cannot be cast to ScenarioPermission return false.
ScenarioPermission p = obj as ScenarioPermission;
if ((System.Object)p == null)
{
return false;
}
// Return true if the fields match:
return (this.ScenarioID == p.ScenarioID) && (this.UserID == p.UserID);
}
}
I've just find the solution :
When I fetched the children of ScenariosPermissions, I created children instead of fetching existing... So, when I wanted to remove permissions, the existing objects was considered by CSLA as a NewObject, so it does not the Delete() ;-)
Correction :
public class ScenarioPermissions : BusinessListBase<ScenarioPermissions, ScenarioPermission>
{
public static ScenarioPermissions NewPermissions()
{
return DataPortal.Create<ScenarioPermissions>();
}
public static ScenarioPermissions GetUsersByScenario(Scenario scenario)
{
return DataPortal.FetchChild<ScenarioPermissions>(scenario);
}
private void Child_Fetch(Scenario obj)
{
RaiseListChangedEvents = false;
using (var ctx = DbContextManager<DatabaseContext>.GetManager())
{
var context = ctx.DbContext;
var scenario = context.Scenarios.Where(s => s.Id == obj.Id).FirstOrDefault();
if (scenario != null)
{
foreach (var item in scenario.Users)
{
this.Add(ScenarioPermission.GetPermission(scenario.Id, item.Id));
}
}
}
RaiseListChangedEvents = true;
}
}
public class ScenarioPermission : BusinessBase<ScenarioPermission>
{
public static ScenarioPermission GetPermission(Guid scenarioID, int userID)
{
return DataPortal.FetchChild<ScenarioPermission>(scenarioID, userID);
}
private void Child_Fetch(Guid scenarioID, int userID)
{
LoadProperty(m_ScenarioID, scenarioID);
LoadProperty(m_UserID, userID);
}
}
Related
I´m writing an API in .net/C# and have a problem with the following service function:
public async Task<List<Item>> GetAsync(RequestParameters requestParameters) =>
await _itemsCollection.Find(item => (item.item_description.Contains(requestParameters.FilterDescString) ||
item.item_adddesc.Contains(requestParameters.FilterDescString)) &&
item.item_id.Equals(requestParameters.FilterItemIdInt) &&
item.item_amount.Equals(requestParameters.FilterAmountInt) &&
item.item_width.Equals(requestParameters.FilterWidthInt) &&
item.item_height.Equals(requestParameters.FilterHeightInt) &&
item.item_weight.Equals(requestParameters.FilterWeightInt) &&
item.item_aunumber.Contains(requestParameters.FilterAunumberString))
.SortByDescending(i => i.item_id).Skip((requestParameters.PageNumber)* requestParameters.PageSize).Limit(requestParameters.PageSize)
.ToListAsync();
Here are non-null-values neccessary for all filters, but null values should be ignored in query. How can I achieve that? Regards
Edit:
If I have no value for any of that integers I need kind of a wildcard. Here is my model:
public class RequestParameters
{
const int MaxPageSize = 50;
public int PageNumber { get; set; } = 1;
private int _pageSize = 25;
private string _filterDescString = "";
private string _filterAunumberString = "";
private int? _filterItemIdInt = null;
private int? _filterAmountInt = null;
private int? _filterWidthInt = null;
private int? _filterHeightInt = null;
private int? _filterWeightInt = null;
public int PageSize
{
get
{
return _pageSize;
}
set
{
_pageSize = (value > MaxPageSize) ? MaxPageSize : value;
}
}
public string FilterDescString
{
get
{
return _filterDescString;
}
set
{
_filterDescString = (value != null) ? value : _filterDescString;
}
}
public string FilterAunumberString
{
get
{
return _filterAunumberString;
}
set
{
_filterAunumberString = (value != null) ? value : _filterAunumberString;
}
}
public int? FilterItemIdInt
{
get
{
return _filterItemIdInt;
}
set
{
_filterItemIdInt = (value != null) ? value : _filterItemIdInt;
}
}
public int? FilterAmountInt
{
get
{
return _filterAmountInt;
}
set
{
_filterAmountInt = (value != null) ? value : _filterAmountInt;
}
}
public int? FilterWidthInt
{
get
{
return _filterWidthInt;
}
set
{
_filterWidthInt = (value != null) ? value : _filterWidthInt;
}
}
public int? FilterHeightInt
{
get
{
return _filterHeightInt;
}
set
{
_filterHeightInt = (value != null) ? value : _filterHeightInt;
}
}
public int? FilterWeightInt
{
get
{
return _filterWeightInt;
}
set
{
_filterWeightInt = (value != null) ? value : _filterWeightInt;
}
}
}
The initial call looks like this, the user can add various combinations of that string and int filters:
https://localhost:7010/api/Items?PageNumber=0&PageSize=25&FilterDescString=&FilterAunumberString=&FilterAmountInt=null&FilterWidthInt=null&FilterHeightInt=null&FilterWeightInt=null&FilterItemIdInt=null
The initial call and all calls that have at least a value for all of the integers return an empty array.
I proceeded with a solution that uses FilterDefinitions on the mongodb thats connected with my API. Filtering for STRINGS works fine, but on filtering INT I get an empty array. Here is my solution:
public static FilterDefinition<Item> ParamsToFilter(RequestParameters requestParameters)
{
var builder = Builders<Item>.Filter;
var filter = builder.Empty;
if (!string.IsNullOrWhiteSpace(requestParameters.FilterDescString))
{
var descFilter = builder.Regex(i => i.item_description, requestParameters.FilterDescString);
filter &= descFilter;
}
if (!string.IsNullOrWhiteSpace(requestParameters.FilterAddDescString))
{
var addDescFilter = builder.Regex(i => i.item_adddesc, requestParameters.FilterAddDescString);
filter &= addDescFilter;
}
if (!string.IsNullOrWhiteSpace(requestParameters.FilterAunumberString))
{
var aunumberFilter = builder.Regex(i => i.item_aunumber, requestParameters.FilterAunumberString);
filter &= aunumberFilter;
}
if (requestParameters.FilterItemIdInt != null)
{
var itemIdFilter = builder.Eq(i => i.item_id, requestParameters.FilterItemIdInt);
filter &= itemIdFilter;
}
if (requestParameters.FilterAmountInt != null)
{
var itemAmountFilter = builder.Eq(i => i.item_amount, requestParameters.FilterAmountInt);
filter &= itemAmountFilter;
}
if (requestParameters.FilterWidthInt != null)
{
var itemWidthFilter = builder.Eq(i => i.item_width, requestParameters.FilterWidthInt);
filter &= itemWidthFilter;
}
if (requestParameters.FilterHeightInt != null)
{
var itemHeightFilter = builder.Eq(i => i.item_height, requestParameters.FilterHeightInt);
filter &= itemHeightFilter;
}
if (requestParameters.FilterWeightInt != null)
{
var itemWeightFilter = builder.Eq(i => i.item_weight, requestParameters.FilterWeightInt);
filter &= itemWeightFilter;
}
return filter;
}
This is a static method that just adds a filter to the FilterDefinition if there a non-null or non-empty filtervalue for the specific column in the db.
public async Task<List<Item>> GetAsync(RequestParameters requestParameters)
{
var filters = ParamsToFilter(requestParameters);
return await _itemsCollection.Find(filters).SortByDescending(i => i.item_id).Skip((requestParameters.PageNumber) * requestParameters.PageSize).Limit(requestParameters.PageSize).ToListAsync();
}
Later I use the given filters in the GetAsync() method with Find(). Regards
Given these source classes (used in an EFCore application)
class StudentCourse
{
public int StudentId { get; set; }
public int CourseId { get; set; }
}
class Student
{
public virtual ICollection<StudentCourse> Courses { get; set; }
}
and the following Protobuf message
message StudentDto {
repeated int32 courseIds = 1;
}
How am I able to map Student.Courses to StudentDto.CourseIds using AutoMapper?
So far I tried the following:
class StudentProfile : Profile
{
class Converter : IValueConverter<ICollection<StudentCourse>, RepeatedField<int>>
{
public RepeatedField<int> Convert(ICollection<StudentCourse> sourceMember, ResolutionContext context)
{
return new RepeatedField<int> { sourceMember.Select(x => x.CourseId) };
}
}
public StudentProfile()
{
CreateMap<Student, StudentDto>()
.ForMember(dst => dst.CourseIds, o => o.MapFrom(src => src.Courses.Select(x => x.CourseId)));
//.ConvertUsing(student => new StudentDto{CourseIds = { student.Courses.Select(x => x.CourseId)}});
//.ForMember(dst => dst.CourseIds, o => o.ConvertUsing(new Converter(), src => src.Courses));
}
}
class Program
{
static void Main(string[] args)
{
var student = new Student
{
Courses = new List<StudentCourse>
{
new StudentCourse { CourseId = 1, StudentId = 1 },
new StudentCourse { CourseId = 6, StudentId = 1 },
new StudentCourse { CourseId = 11, StudentId = 1 }
}
};
IMapper mapper = new Mapper(new MapperConfiguration(cfg => cfg.AddProfile(new StudentProfile())));
var mapped = mapper.Map<StudentDto>(student);
// Expecting: {{ "courseIds": [ 1, 6, 11 ] }}
// Actually: {{ }}
}
}
The ConvertUsing statement in StudentProfile works and maps the member correctly but as the classes I'm using in my project are far more complex, this is really the last option I would like to use.
Does anyone of you know the cause and a possible fix for this problem?
I'm using Google.Protobuf 3.7.0 and AutoMapper 8.0.0.
Edit:
I already checked the execution plan but couldn't find any errors in there.
For the sake of completeness here I'll attach it here:
(src, dest, ctxt) =>
{
StudentDto typeMapDestination;
return (src == null)
? null
: {
typeMapDestination = dest ?? new StudentDto();
try
{
var resolvedValue =
{
try
{
return ((src == null) || ((src.Courses == null) || false))
? null
: src.Courses.Select(x => x.CourseId);
}
catch (NullReferenceException)
{
return null;
}
catch (ArgumentNullException)
{
return null;
}
};
var propertyValue = (resolvedValue == null)
? {
var collectionDestination = (ICollection<int>)((dest == null) ? null : typeMapDestination.CourseIds);
if ((collectionDestination == null) || collectionDestination.IsReadOnly)
{
}
else
{
collectionDestination.Clear();
}
return new RepeatedField<int>();
}
: {
var passedDestination = (dest == null) ? null : typeMapDestination.CourseIds;
var collectionDestination = ((passedDestination == null) || ((ICollection<int>)passedDestination).IsReadOnly)
? new RepeatedField<int>()
: passedDestination;
collectionDestination.Clear();
var enumerator = resolvedValue.GetEnumerator();
while (true)
{
if (enumerator.MoveNext())
{
var item = enumerator.Current;
collectionDestination.Add(item);
}
else
{
break;
}
}
return collectionDestination;
};
return propertyValue;
}
catch (Exception ex)
{
throw new AutoMapperMappingException(
"Error mapping types.",
ex,
AutoMapper.TypePair,
TypeMap,
PropertyMap);
return null;
}
return typeMapDestination;
};
};
I have the following issue:
I get an IQueryable<T> from LinqToQueryString
This IQueryable<T> is used to query MongoDB
The IQueryable<T> is used to return paged dataset as well as total number of items to determine pages etc
MongoDB adds a group by to the Count on an IQueryable<T>.Where().Count(). This causes Count operations to run very slowly.
Possible solution:
Get the Expression<Func<T,bool>> from the original IQueryable<T> and apply it to the mongoCollection<T>.Count(filter). This bypasses the issue.
I have tried to get the "Where" from the IQueryable<T>.Expression and then manipulate the ExpressionType to a format that can be used in DynamicExpression.ParseLambda(). For the most part, this worked fine until I tested the code with a DateTime expression.
I have attached an LINQPad script that uses a local MongoDB installation to populate data and then Count using the new Expression created from the ExpressionVisitor.
I am hoping there is an easier way to reuse the "Where" from the original Expression in the new MongoDB FilterDefinitionBuilder<T>.Where(originalWhereExpression).
Script dependencies are:
Code:
<Query Kind="Program">
<Reference><RuntimeDirectory>\System.Linq.dll</Reference>
<Reference><RuntimeDirectory>\System.Linq.Expressions.dll</Reference>
<Reference><RuntimeDirectory>\System.Linq.Queryable.dll</Reference>
<NuGetReference>Faker</NuGetReference>
<NuGetReference>LINQKit.Core</NuGetReference>
<NuGetReference>mongocsharpdriver</NuGetReference>
<NuGetReference>MongoDB.Driver</NuGetReference>
<NuGetReference>NBuilder</NuGetReference>
<NuGetReference>Newtonsoft.Json</NuGetReference>
<NuGetReference>System.Linq.Dynamic</NuGetReference>
<Namespace>FizzWare.NBuilder</Namespace>
<Namespace>LinqKit</Namespace>
<Namespace>MongoDB.Bson</Namespace>
<Namespace>MongoDB.Bson.Serialization.Attributes</Namespace>
<Namespace>MongoDB.Driver</Namespace>
<Namespace>MongoDB.Driver.Builders</Namespace>
<Namespace>MongoDB.Driver.Linq</Namespace>
<Namespace>myAlias = System.Linq.Dynamic</Namespace>
<Namespace>Newtonsoft.Json</Namespace>
<Namespace>System.Linq</Namespace>
<Namespace>System.Linq.Expressions</Namespace>
<Namespace>System.Threading.Tasks</Namespace>
<Namespace>System.Threading.Tasks.Dataflow</Namespace>
</Query>
private string _mongoDBConnectionString = "mongodb://localhost";
private string _mongoDBDatabase = "LinqToQ";
private string _mongoDBCollection = "People";
private IMongoClient _mongoClient;
private IMongoDatabase _mongoDb;
private int _demoCount = 100000;
private bool _doPrep = true;
void Main()
{
_connectToMongoDB();
if (_doPrep)
_prepMongo();
var mongoDataQuery = _queryDemoData().Result;
mongoDataQuery.Expression.ToString().Dump("Original Expression");
var whereFinder = new WhereFinder();
whereFinder.SetWhere(mongoDataQuery.Expression);
var tempColl = _getPeopleCollection();
if (!string.IsNullOrEmpty(whereFinder.WhereClause))
{
var filter = new FilterDefinitionBuilder<Person>();
tempColl.Count(filter.Where(_createWherePredicate<Person>(whereFinder.GetLambdaParts<Person>()))).Dump("Dynamic where count");
}
else
tempColl.Count(FilterDefinition<Person>.Empty).Dump("No filter count");
"Done".Dump();
}
// Define other methods and classes here
//
private void _replaceExpressionTypes(ref StringBuilder whereBuilder, Dictionary<ExpressionType,string> expressionTypes)
{
foreach (var expType in expressionTypes.Keys)
{
whereBuilder.Replace($" {expType} ", $" {expressionTypes[expType]} ");
}
var openBracketCount = whereBuilder.ToString().Count(s => s == char.Parse("("));
var closeBracketCount = whereBuilder.ToString().Count(s=> s==char.Parse(")"));
//whereBuilder.Replace("new DateTime(1974, 1, 1)","\"1974-01-01T00:00:00.00Z\"");
whereBuilder.Insert(0,"(",1);
whereBuilder.Append(")");
$"OpenBrackets: {openBracketCount} vs CloseBrackets: {closeBracketCount}".Dump("Found Brackets");
if(openBracketCount==closeBracketCount)
return;
if (openBracketCount > closeBracketCount)
{
var firstopenBracket = whereBuilder.ToString().IndexOf("(");
whereBuilder.Remove(firstopenBracket,1);
}
var lastCloseBracket = whereBuilder.ToString().LastIndexOf(")");
if(lastCloseBracket>-1)
whereBuilder.Remove(lastCloseBracket,1);
}
private Dictionary<ExpressionType, string> _buildExpressionTypePairs()
{
var result = new Dictionary<ExpressionType, string>();
result.Add(ExpressionType.Not, "!");
result.Add(ExpressionType.Add, "+");
result.Add(ExpressionType.AddChecked, "+");
result.Add(ExpressionType.Subtract, "-");
result.Add(ExpressionType.SubtractChecked, "-");
result.Add(ExpressionType.Multiply, "*");
result.Add(ExpressionType.MultiplyChecked, "*");
result.Add(ExpressionType.Divide, "/");
result.Add(ExpressionType.Modulo, "%");
result.Add(ExpressionType.And, "&");
result.Add(ExpressionType.AndAlso, "&&");
result.Add(ExpressionType.Or, "|");
result.Add(ExpressionType.OrElse, "||");
result.Add(ExpressionType.LessThan, "<");
result.Add(ExpressionType.LessThanOrEqual, "<=");
result.Add(ExpressionType.GreaterThan, ">");
result.Add(ExpressionType.GreaterThanOrEqual, ">=");
result.Add(ExpressionType.Equal, "==");
result.Add(ExpressionType.NotEqual, "!=");
return result;
}
private Expression<Func<Person, bool>> _createWherePredicate<T>(LamdaParts<T> lamdaParts)
{
var whereBuilder = new StringBuilder(lamdaParts.ExpressionString);
_replaceExpressionTypes(ref whereBuilder, _buildExpressionTypePairs());
whereBuilder.ToString().Dump("Manipulated where cluase");
var parameter = Expression.Parameter(lamdaParts.ParamterType, lamdaParts.ExpressionParameter);
//lamdaParts.ParamterType.Dump("Parameter");
//var parameter = Expression.Parameter(typeof(Person), "p");
var expression = myAlias.DynamicExpression.ParseLambda(new[] { parameter }, null, whereBuilder.ToString());
//return Expression.Lambda<Func<Person, bool>>(whereExpression, parameter);
return Expression.Lambda<Func<Person, bool>>(expression.Body, expression.Parameters);
}
private async Task<IMongoQueryable<Person>> _queryDemoData()
{
var people = _getPeopleCollection();
return people.AsQueryable().Where(p => p.DateOfBirth <= new DateTime(1974, 1, 1));
//return people.AsQueryable().Where(p => p.LastName == "Anderson" && p.FirstName.Contains("f") && p.DateOfBirth >= new DateTime(1968, 1, 1) && p.DateOfBirth < new DateTime(1974, 1, 1));
//return people.AsQueryable().Where(p => p.LastName == "Anderson" && p.FirstName.Contains("f") && (p.DateOfBirth>=new DateTime(1968,1,1) && p.DateOfBirth<new DateTime(1974,1,1)));
//return people.AsQueryable().Where(p => p.LastName == "Anderson" && p.FirstName.Contains("f"));
//return people.AsQueryable().Where(p => p.FirstName.Contains("f"));
//return people.AsQueryable().Where(p => p.LastName == "Anderson");
}
private void _prepMongo()
{
_mongoDb.DropCollection(_mongoDBCollection, CancellationToken.None);
var testData = _getDemoList(_demoCount);
var people = _getPeopleCollection();
people.Indexes.CreateOne(Builders<Person>.IndexKeys.Ascending(_ => _.LastName));
people.Indexes.CreateOne(Builders<Person>.IndexKeys.Ascending(_ => _.Email));
testData.ForEachOverTpl((person) =>
{
people.InsertOneAsync(person).Wait();
});
$"Inserted {testData.Count} demo records".Dump();
}
private IList<Person> _getDemoList(int demoCount)
{
var result = Builder<Person>.CreateListOfSize(demoCount)
.All()
.With(p => p.FirstName = Faker.NameFaker.FirstName())
.With(p => p.LastName = Faker.NameFaker.LastName())
.With(p => p.Email = Faker.InternetFaker.Email())
.With(p => p.DateOfBirth = Faker.DateTimeFaker.BirthDay(21,50))
.Build();
return result;
}
private IMongoCollection<Person> _getPeopleCollection()
{
return _mongoDb.GetCollection<Person>(_mongoDBCollection);
}
private void _connectToMongoDB()
{
_mongoClient = new MongoClient(_mongoDBConnectionString);
_mongoDb = _mongoClient.GetDatabase(_mongoDBDatabase);
}
public class Person
{
[BsonId]
public string Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public DateTime DateOfBirth { get; set; }
}
public class WhereFinder : MongoDB.Driver.Linq.ExpressionVisitor
{
//private IList<MethodCallExpression> whereExpressions = new List<MethodCallExpression>();
private bool _foundWhere = false;
private bool _setWhere = false;
public string WhereClause { get; set; }
public string Parameter { get; set; }
public LamdaParts<T> GetLambdaParts<T>()
{
return new LamdaParts<T> {
ExpressionParameter=Parameter,
ExpressionString = WhereClause
};
}
public void SetWhere(Expression expression)
{
Visit(expression);
//return whereExpressions;
}
protected override Expression VisitBinary(BinaryExpression node)
{
//$"{node.Left} {_convertNodeType(node.NodeType)} {node.Right}".Dump();
if (_foundWhere && !_setWhere)
{
//node.ToString().Dump("VisitBinary");
$"{node.Left} {_convertNodeType(node.NodeType)} {node.Right}".Dump("Setting Where Clause");
WhereClause= $"{node.Left} {_convertNodeType(node.NodeType)} {node.Right}";
//WhereClause.Dump("WhereClause");
_setWhere=true;
}
return base.VisitBinary(node);
}
private string _convertNodeType(ExpressionType nodeType)
{
switch (nodeType)
{
case ExpressionType.Not:
return "!";
case ExpressionType.Add:
case ExpressionType.AddChecked:
return "+";
case ExpressionType.Subtract:
case ExpressionType.SubtractChecked:
return "-";
case ExpressionType.Multiply:
case ExpressionType.MultiplyChecked:
return "*";
case ExpressionType.Divide:
return "/";
case ExpressionType.Modulo:
return "%";
case ExpressionType.And:
return "&";
case ExpressionType.AndAlso:
return "&&";
case ExpressionType.Or:
return "|";
case ExpressionType.OrElse:
return "||";
case ExpressionType.LessThan:
return "<";
case ExpressionType.LessThanOrEqual:
return "<=";
case ExpressionType.GreaterThan:
return ">";
case ExpressionType.GreaterThanOrEqual:
return ">=";
case ExpressionType.Equal:
return "==";
case ExpressionType.NotEqual:
return "!=";
default:
throw new Exception(string.Format("Unhandled expression type: '{0}'", nodeType));
}
}
protected override Expression VisitParameter(ParameterExpression node)
{
if (_foundWhere)
{
//node.ToString().Dump("VisitParameter");
Parameter=node.ToString();
}
return base.VisitParameter(node);
}
protected override Expression VisitMethodCall(MethodCallExpression expression)
{
if (expression.Method.Name == "Where")
{
//whereExpressions.Add(expression);
_foundWhere = true;
}
if (expression?.Arguments != null)
{
foreach (var arg in expression.Arguments)
{
Visit(arg);
}
}
return expression;
}
}
public class LamdaParts<T>
{
public Type ParamterType
{
get
{
return typeof(T);
}
}
public string ExpressionParameter { get; set; }
public string ExpressionString { get;set;}
}
public static class Extensions
{
public static void ForEachOverTpl<T>(this IEnumerable<T> enumerable, Action<T> call)
{
var cancellationTokenSource = new CancellationTokenSource();
var actionBlock = new ActionBlock<T>(call, new ExecutionDataflowBlockOptions
{
TaskScheduler = TaskScheduler.Current,
MaxDegreeOfParallelism = Environment.ProcessorCount * 2,
CancellationToken = cancellationTokenSource.Token,
});
foreach (T item in enumerable)
{
if (cancellationTokenSource.IsCancellationRequested) return;
actionBlock.Post(item);
}
actionBlock.Complete();
actionBlock.Completion.Wait(cancellationTokenSource.Token);
}
}
The solution is fairly simple once you understand the Expression tree and possible root level expression method names. Thanks to #bolanki for the assistance.
Attached is an updated LINQPad script (to test set _doPrep = true):
<Query Kind="Program">
<Reference><RuntimeDirectory>\System.Linq.dll</Reference>
<Reference><RuntimeDirectory>\System.Linq.Expressions.dll</Reference>
<Reference><RuntimeDirectory>\System.Linq.Queryable.dll</Reference>
<NuGetReference>Faker</NuGetReference>
<NuGetReference>mongocsharpdriver</NuGetReference>
<NuGetReference>MongoDB.Driver</NuGetReference>
<NuGetReference>NBuilder</NuGetReference>
<NuGetReference>Newtonsoft.Json</NuGetReference>
<NuGetReference>System.Linq.Dynamic</NuGetReference>
<Namespace>FizzWare.NBuilder</Namespace>
<Namespace>MongoDB.Bson</Namespace>
<Namespace>MongoDB.Bson.Serialization.Attributes</Namespace>
<Namespace>MongoDB.Driver</Namespace>
<Namespace>MongoDB.Driver.Builders</Namespace>
<Namespace>MongoDB.Driver.Linq</Namespace>
<Namespace>myAlias = System.Linq.Dynamic</Namespace>
<Namespace>Newtonsoft.Json</Namespace>
<Namespace>System.Linq</Namespace>
<Namespace>System.Linq.Expressions</Namespace>
<Namespace>System.Threading.Tasks</Namespace>
<Namespace>System.Threading.Tasks.Dataflow</Namespace>
</Query>
private string _mongoDBConnectionString = "mongodb://localhost";
private string _mongoDBDatabase = "LinqToQ";
private string _mongoDBCollection = "People";
private IMongoClient _mongoClient;
private IMongoDatabase _mongoDb;
private int _demoCount = 2000000;
private bool _doPrep = false;
void Main()
{
_connectToMongoDB();
// Should demo data be generated
if (_doPrep)
_prepMongo();
// Get the queryable to test with
var mongoDataQuery = _getIQueryable();
// Print the original expression
//mongoDataQuery.Expression.ToString().Dump("Original Expression");
// Evaluate the expression and try find the where expression
var whereFinder = new WhereFinder<Person>(mongoDataQuery.Expression);
// Get the MongoCollection to be Filtered and Count
var tempColl = _getPeopleCollection();
if (whereFinder.FoundWhere)
{
//whereFinder.TheWhereExpression.ToString().Dump("Calculated where expression");
var filter = new FilterDefinitionBuilder<Person>();
var stopwatch = new Stopwatch();
stopwatch.Start();
tempColl.Count(filter.Where(whereFinder.TheWhereExpression)).Dump("Dynamic where count");
var afterCalculatedWhere = stopwatch.Elapsed;
mongoDataQuery.Count().Dump("IQueryable<T> where count");
var afterIQuerableWhere = stopwatch.Elapsed;
stopwatch.Stop();
$"Calculated where:{afterCalculatedWhere:c}\nIQueryable where:{afterIQuerableWhere:c}".Dump("Where Durations");
}
else
tempColl.Count(FilterDefinition<Person>.Empty).Dump("No filter count");
"Done".Dump();
}
///////////////////////////////////////////////////////
// END SOLUTION
///////////////////////////////////////////////////////
private IMongoQueryable<Person> _getIQueryable()
{
var people = _getPeopleCollection();
//return people.AsQueryable().Where(p => p.DateOfBirth <= new DateTime(1974, 1, 1));
return people.AsQueryable().Where(p => p.LastName == "Anderson" && p.FirstName.Contains("f") && p.DateOfBirth >= new DateTime(1968, 1, 1) && p.DateOfBirth < new DateTime(1974, 1, 1));
//return people.AsQueryable().Where(p => p.LastName == "Anderson" && p.FirstName.Contains("f") && (p.DateOfBirth>=new DateTime(1968,1,1) && p.DateOfBirth<new DateTime(1974,1,1)));
//return people.AsQueryable().Where(p => p.LastName == "Anderson" && p.FirstName.Contains("f"));
//return people.AsQueryable().Where(p => p.FirstName.Contains("f"));
//return people.AsQueryable().Where(p => p.LastName == "Anderson");
}
public class Person
{
[BsonId]
public string Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public DateTime DateOfBirth { get; set; }
}
public class WhereFinder<T> : MongoDB.Driver.Linq.ExpressionVisitor
{
private bool _processingWhere = false;
private bool _processingLambda = false;
public ParameterExpression _parameterExpression { get; set; }
public WhereFinder(Expression expression)
{
Visit(expression);
}
public Expression<Func<T, bool>> TheWhereExpression { get; set; }
public bool FoundWhere
{
get { return TheWhereExpression != null; }
}
protected override Expression VisitBinary(BinaryExpression node)
{
var result = base.VisitBinary(node);
if(_processingWhere)
TheWhereExpression = (Expression<Func<T, bool>>)Expression.Lambda(node, _parameterExpression);
return result;
}
protected override Expression VisitParameter(ParameterExpression node)
{
if (_processingWhere || _processingLambda || _parameterExpression==null)
_parameterExpression = node;
return base.VisitParameter(node);
}
protected override Expression VisitMethodCall(MethodCallExpression expression)
{
string methodName = expression.Method.Name;
if (TheWhereExpression==null && ( methodName == "Where" || methodName == "Contains"))
{
_processingWhere = true;
if (expression?.Arguments != null)
foreach (var arg in expression.Arguments)
Visit(arg);
_processingWhere = false;
}
return expression;
}
protected override Expression VisitLambda(LambdaExpression exp)
{
if (_parameterExpression == null)
_parameterExpression = exp.Parameters?.FirstOrDefault();
TheWhereExpression = (Expression<Func<T, bool>>)Expression.Lambda(exp.Body, _parameterExpression);
return exp;
}
}
///////////////////////////////////////////////////////
// END SOLUTION
///////////////////////////////////////////////////////
///////////////////////////////////////////////////////
// BEGIN DEMO DATA
///////////////////////////////////////////////////////
private void _prepMongo()
{
_mongoDb.DropCollection(_mongoDBCollection, CancellationToken.None);
var testData = _getDemoList(_demoCount);
var people = _getPeopleCollection();
people.Indexes.CreateOne(Builders<Person>.IndexKeys.Ascending(_ => _.FirstName));
people.Indexes.CreateOne(Builders<Person>.IndexKeys.Ascending(_ => _.LastName));
people.Indexes.CreateOne(Builders<Person>.IndexKeys.Ascending(_ => _.Email));
people.Indexes.CreateOne(Builders<Person>.IndexKeys.Ascending(_ => _.DateOfBirth));
$"Inserting ...{testData.Count}... demo records".Dump();
Extensions.ForEachOverTpl<Person>(testData, (person) =>
{
people.InsertOneAsync(person).Wait();
});
$"Inserted {testData.Count} demo records".Dump();
}
private IList<Person> _getDemoList(int demoCount)
{
var result = Builder<Person>.CreateListOfSize(demoCount)
.All()
.With(p => p.FirstName = Faker.NameFaker.FirstName())
.With(p => p.LastName = Faker.NameFaker.LastName())
.With(p => p.Email = Faker.InternetFaker.Email())
.With(p => p.DateOfBirth = Faker.DateTimeFaker.BirthDay(21, 50))
.Build();
return result;
}
private IMongoCollection<Person> _getPeopleCollection()
{
return _mongoDb.GetCollection<Person>(_mongoDBCollection);
}
private void _connectToMongoDB()
{
_mongoClient = new MongoClient(_mongoDBConnectionString);
_mongoDb = _mongoClient.GetDatabase(_mongoDBDatabase);
}
///////////////////////////////////////////////////////
// END DEMO DATA
///////////////////////////////////////////////////////
public static class Extensions
{
public static void ForEachOverTpl<T>(this IEnumerable<T> enumerable, Action<T> call)
{
var cancellationTokenSource = new CancellationTokenSource();
var actionBlock = new ActionBlock<T>(call, new ExecutionDataflowBlockOptions
{
TaskScheduler = TaskScheduler.Current,
MaxDegreeOfParallelism = Environment.ProcessorCount * 2,
CancellationToken = cancellationTokenSource.Token,
});
foreach (T item in enumerable)
{
if (cancellationTokenSource.IsCancellationRequested) return;
actionBlock.Post(item);
}
actionBlock.Complete();
actionBlock.Completion.Wait(cancellationTokenSource.Token);
}
}
Update: -- Fix for expressions containing Take, OrderBy etc.
public class WhereFinder<T> : MongoDB.Driver.Linq.ExpressionVisitor
{
private bool _processingWhere = false;
private bool _processingLambda = false;
public ParameterExpression _parameterExpression { get; set; }
public WhereFinder(Expression expression)
{
Visit(expression);
}
public Expression<Func<T, bool>> TheWhereExpression { get; set; }
public bool FoundWhere
{
get { return TheWhereExpression != null; }
}
protected override Expression Visit(Expression exp)
{
return base.Visit(exp);
}
protected override Expression VisitBinary(BinaryExpression node)
{
var result = base.VisitBinary(node);
if (_processingWhere)
{
TheWhereExpression = (Expression<Func<T, bool>>) Expression.Lambda(node, _parameterExpression);
}
return result;
}
protected override Expression VisitParameter(ParameterExpression node)
{
if (_processingWhere || _processingLambda || _parameterExpression == null)
_parameterExpression = node;
return base.VisitParameter(node);
}
protected override Expression VisitMethodCall(MethodCallExpression expression)
{
string methodName = expression.Method.Name;
if (methodName == "Where")
_processingWhere = true;
if (expression?.Arguments != null)
foreach (var arg in expression.Arguments)
Visit(arg);
_processingWhere = false;
return expression;
}
protected override Expression VisitLambda(LambdaExpression exp)
{
if (_processingWhere)
{
if (_parameterExpression == null)
_parameterExpression = exp.Parameters?.FirstOrDefault();
TheWhereExpression = (Expression<Func<T, bool>>)Expression.Lambda(exp.Body, _parameterExpression);
}
return exp;
}
}
I am developing my first WPF browser application.
I load invoices in a dataGrid then I filter with textBox or comboBox.
Because it takes few seconds to load, I'am trying to put a loading animation according the following example:
here
It doesn't work the first time I navigate to the page. My dataGrid remains empty. When I debug I have this following error which happens on my query in Get() function.
'System.Data.Entity.Core.EntityCommandExecutionException' occurred in mscorlib.dll but was not handled in user code
But the this query used to work well before I made the changes for the animation. So maybe the problem doesn't come from the query.
Exception:Thrown: "Connection must be valid and open." (System.InvalidOperationException)
A System.InvalidOperationException was thrown: "Connection must be valid and open."
Time: 11/20/2015 12:36:31 PM
Thread:Worker Thread[13324]
public class ConsultInvoiceViewModel : ViewModelBase
{
public Context ctx = new Context();
private ICollectionView _dataGridCollection;
private string _filterString;
private ObservableCollection<Invoice> invoiceCollection;
public ConsultInvoiceViewModel()
{
if (!WPFHelper.IsInDesignMode)
{
var tsk = Task.Factory.StartNew(InitialStart);
tsk.ContinueWith(t => { MessageBox.Show(t.Exception.InnerException.Message); }, CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.FromCurrentSynchronizationContext());
}
}
private void InitialStart()
{
try
{
State = StateEnum.Busy;
DataGridCollection = CollectionViewSource.GetDefaultView(Get());
DataGridCollection.Filter = new Predicate<object>(Filter);
GetShop(); //load one comboBox
GetSupplier(); //load one comboBox
}
finally
{
State = StateEnum.Idle;
}
}
private ObservableCollection<Invoice> Get()
{
DateTime date2 = DateTime.Now.AddMonths(-2);
var query = ctx.Invoices
.GroupBy(x => new { x.suppInvNumber, x.shop1, x.date, x.foodSupplier })
.ToList()
.Select(i => new Invoice
{
suppInvNumber = i.Key.suppInvNumber,
shop1 = i.Key.shop1,
date = i.Key.date,
foodSupplier = i.Key.foodSupplier,
totalPrice = i.Sum(t => t.totalPrice),
})
.Where(d => d.date >= date2)
.OrderByDescending(d => d.date)
.AsQueryable();
invoiceCollection = new ObservableCollection<Invoice>(query);
return invoiceCollection;
}
public ICollectionView DataGridCollection
{
get
{
return _dataGridCollection;
}
set
{
_dataGridCollection = value;
OnPropertyChanged("DataGridCollection"); }
}
public string FilterString
{
get
{
return _filterString;
}
set
{
_filterString = value;
OnPropertyChanged("FilterString");
FilterCollection();
}
}
public static readonly PropertyChangedEventArgs StateArgs = ViewModelBase.CreateArgs<ConsultInvoiceViewModel>(c => c.State);
private StateEnum _State;
public StateEnum State
{
get
{
return _State;
}
set
{
var oldValue = State;
_State = value;
if (oldValue != value)
{
OnStateChanged(oldValue, value);
OnPropertyChanged(StateArgs);
}
}
}
protected virtual void OnStateChanged(StateEnum oldValue, StateEnum newValue)
{
}
private void FilterCollection()
{
if (_dataGridCollection != null)
{
_dataGridCollection.Refresh();
}
}
private bool Filter(object obj)
{
var data = obj as Invoice;
if (data != null)
{
if (!string.IsNullOrEmpty(_filterString))
{
return data.suppInvNumber.Contains(_filterString);
}
return true;
}
return false;
}
private void SearchFilter()
{
IOrderedEnumerable<Invoice> invs;
invs = ctx.Invoices
.Where(s => s.shop == Shop && s.supplier == Supplier && s.date >= From && s.date <= To)
.GroupBy(x => new {x.suppInvNumber, x.shop1, x.date, x.foodSupplier })
.ToList()
.Select(i => new Invoice
{
suppInvNumber = i.Key.suppInvNumber,
shop1 = i.Key.shop1,
date = i.Key.date,
foodSupplier = i.Key.foodSupplier,
totalPrice = i.Sum(t => t.totalPrice),
})
.OrderByDescending(d => d.date);
}
invoiceCollection.Clear();
if (invs != null)
foreach (var inv in invs)
{
invoiceCollection.Add(inv);
}
FilterCollection();
}
#region combobox
private void GetShop()
{
ctx.shops.ToList().ForEach(shop => ctx.shops.Local.Add(shop));
SShop = ctx.shops.Local;
}
private void GetSupplier()
{
ctx.foodSuppliers.ToList().ForEach(supplier => ctx.foodSuppliers.Local.Add(supplier));
FoodSupplier = ctx.foodSuppliers.Local;
}
private IList<foodSupplier> supplier;
public IList<foodSupplier> FoodSupplier
{
get
{
if (supplier == null)
GetSupplier();
return supplier;
}
set
{
supplier = value;
OnPropertyChanged("FoodSupplier");
}
}
private IList<shop> shop;
public IList<shop> SShop
{
get
{
return shop;
}
set
{
shop = value;
OnPropertyChanged("SShop");
}
}
private int _shop;
public int Shop
{
get
{
return _shop;
}
set
{
_shop = value;
OnPropertyChanged("Shop");
SearchFilter();
}
}
private int _supplier;
public int Supplier
{
get
{
return _supplier;
}
set
{
_supplier = value;
OnPropertyChanged("Supplier");
SearchFilter();
}
}
#endregion
#region "Command"
private ICommand searchCommand;
public ICommand SearchCommand
{
get
{
return searchCommand ?? (searchCommand = new RelayCommand(p => this.Search(), p => this.CanSearch()));
}
}
private bool CanSearch()
{
return true;
}
#endregion
}
The exception you are getting indicates an error connecting to the database. It's hard to diagnose this because of the way you keep a single Context reference for the life of the application. That connection could be failing at any point.
Try wrapping your data access in a new Context for each logical operation like this. Keeping one Context around for the life of an application is an Anti-pattern that can lead to all kinds of errors, especially when trying to do things in the background.
private ObservableCollection<Invoice> Get()
{
using (var ctx = new Context())
{
DateTime date2 = DateTime.Now.AddMonths(-2);
var query = ctx.Invoices
.GroupBy(x => new { x.suppInvNumber, x.shop1, x.date, x.foodSupplier })
.ToList()
.Select(i => new Invoice
{
suppInvNumber = i.Key.suppInvNumber,
shop1 = i.Key.shop1,
date = i.Key.date,
foodSupplier = i.Key.foodSupplier,
totalPrice = i.Sum(t => t.totalPrice),
})
.Where(d => d.date >= date2)
.OrderByDescending(d => d.date)
.AsQueryable();
invoiceCollection = new ObservableCollection<Invoice>(query);
}
return invoiceCollection;
}
I'd like to write a generic extension method to link two classes/objects
Classes:
public class Model1
{
public Guid Model2ID { get; set; }
public Model2 Model2 { get; set; }
// .. other insignificant properties to this question
}
public class Model2
{
public Guid ID { get; set; }
}
I'm trying to write a method that looks something like:
public static class ObjectExtensions
{
public static Void LinkTo<T,U>(
this T m1,
IEnumerable<U> m2,
m1p // expression to select what property on m1 is populated
Action<bool,T,IEnumerable<U>> expression)
{
// not sure about this part at all
m1p = u2.FirstOrDefault(expression);
}
}
Usage:
var listOfModel2 = //....
Model1.LinkTo(listOfModel2, m => m.Model2, (m1,m2) m1.Model2Id == m2.ID);
As we had discussed in Chat, I'd recommend a light-weight version of EF Context (with reflection). This is completely custom and dynamic, you simply need to use KeyAttribute and ForeignKeyAttribute on your models and add the models to your this custom Context. I only wired up the Add, as the rest you should be able to figure out on your own.
Classes:
public class Staff
{
[Key]
public Guid Id { get; set; }
[ForeignKey("Contact")]
public Guid ContactId { get; set; }
public Contact Contact { get; set; }
}
public class Contact
{
[Key]
public Guid Id { get; set; }
public string Name { get; set; }
[ForeignKey("Dog")]
public Guid DogId { get; set; }
public Dog Dog { get; set; }
}
public class Dog
{
[Key]
public Guid Id { get; set; }
}
Context:
public class Context
{
//Add as many of these as you want. Don't forget to make public properties for them!
private ObservableCollection<Staff> _staffs = new ObservableCollection<Staff>();
private ObservableCollection<Contact> _contacts = new ObservableCollection<Contact>();
private ObservableCollection<Dog> _dogs = new ObservableCollection<Dog>();
private List<IForeignKeyRelation> _relations = new List<IForeignKeyRelation>();
public Context()
{
var observables = this.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance)
.ToList();
foreach(var observable in observables)
{
var notifyCollection = observable.GetValue(this) as INotifyCollectionChanged;
if (notifyCollection != null)
{
notifyCollection.CollectionChanged += CollectionChanged;
Type principalType = observable.FieldType.GetGenericArguments()[0];
var relations = principalType.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.ToList()
.Where(p => p.GetCustomAttribute(typeof(ForeignKeyAttribute)) as ForeignKeyAttribute != null)
.Select(p => new { PrincipalForeignKeyInfo = p, ForeignKey = p.GetCustomAttribute(typeof(ForeignKeyAttribute)) as ForeignKeyAttribute })
.Where(p => principalType.GetProperty(p.ForeignKey.Name) != null)
.Select(p => {
var principalForeignKeyInfo = p.PrincipalForeignKeyInfo;
var principalRelationInfo = principalType.GetProperty(p.ForeignKey.Name);
var dependantType = principalRelationInfo.PropertyType;
var dependantKeyProperties = dependantType.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.ToList()
.Where(dp => dp.GetCustomAttribute(typeof(KeyAttribute)) as KeyAttribute != null)
.ToList();
var dependantKeyInfo = dependantKeyProperties.FirstOrDefault();
var isValid = (dependantKeyInfo != null)
// Don't allow INT to GUID comparisons
// Keys need to be of same type;
&& (principalForeignKeyInfo.PropertyType == dependantKeyInfo.PropertyType);
return new {
IsValid = isValid,
PrincipalRelationInfo = principalRelationInfo,
DependantType = dependantType,
PrincipalCollection = observable.GetValue(this),
PrincipalForeignKeyInfo = principalForeignKeyInfo,
DependantKeyInfo = dependantKeyInfo
};
})
.Where(r => r.IsValid)
.Select(r =>
{
var relationType = typeof(ForeignKeyRelation<,>).MakeGenericType(principalType, r.DependantType);
var relation = Activator.CreateInstance(relationType) as IForeignKeyRelation;
relation.GetType().GetProperty("PrincipalCollection").SetValue(relation, observable.GetValue(this));
relation.DependantKeyInfo = r.DependantKeyInfo;
relation.PrincipalForeignKeyInfo = r.PrincipalForeignKeyInfo;
relation.PrincipalRelationInfo = r.PrincipalRelationInfo;
return relation;
})
.ToList();
_relations.AddRange(relations);
}
}
}
// Makes storing Generic types easy when the
// Generic type doesn't exist;
private interface IForeignKeyRelation
{
PropertyInfo PrincipalForeignKeyInfo { get; set; }
PropertyInfo PrincipalRelationInfo { get; set; }
PropertyInfo DependantKeyInfo { get; set; }
void Add<T>(T value);
}
// Class to hold reflected values
// Reflection
private class ForeignKeyRelation<P,D> : IForeignKeyRelation
{
// Staff.ContactId
public PropertyInfo PrincipalForeignKeyInfo { get; set; }
public Collection<P> PrincipalCollection { get; set; }
// Staff.Contact
public PropertyInfo PrincipalRelationInfo { get; set; }
// Contact.Id
public PropertyInfo DependantKeyInfo { get; set; }
public void Add<T>(T value)
{
if (value.GetType() == typeof(D))
{
var dependantKey = DependantKeyInfo.GetValue(value);
var principals = PrincipalCollection.Where(p => this.PrincipalForeignKeyInfo.GetValue(p).Equals(dependantKey))
.ToList();
foreach(var principal in principals)
{
PrincipalRelationInfo.SetValue(principal, value);
}
}
}
}
private void CollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
{
switch (args.Action)
{
case NotifyCollectionChangedAction.Add:
foreach(var relation in this._relations)
{
foreach(var item in args.NewItems)
{
relation.Add(item);
}
}
break;
case NotifyCollectionChangedAction.Move:
break;
case NotifyCollectionChangedAction.Remove:
break;
case NotifyCollectionChangedAction.Replace:
break;
case NotifyCollectionChangedAction.Reset:
break;
default:
throw new NotImplementedException(args.Action.ToString());
}
}
public IList<Staff> Staffs
{
get
{
return _staffs;
}
}
public IList<Contact> Contacts
{
get
{
return _contacts;
}
}
public IList<Dog> Dogs
{
get
{
return _dogs;
}
}
}
Simple example program:
public static void Main()
{
var context = new Context();
var staff = new Staff() { Id = Guid.NewGuid() };
var contact = new Contact();
contact.Id = Guid.NewGuid();
contact.Name = "Hello DotFiddle!";
staff.ContactId = contact.Id;
context.Staffs.Add(staff);
Console.WriteLine("staff contact is null: " + (staff.Contact == null).ToString());
context.Contacts.Add(contact);
Console.WriteLine("staff contact is null: " + (staff.Contact == null).ToString());
Console.WriteLine("Staff.Contact.Name: "+ staff.Contact.Name);
}
result:
staff contact is null: True
staff contact is null: False
Staff.Contact.Name: Hello DotFiddle!
This Entire Example on DotNetFiddle.net
I don't know exactly what you're going for, but one of these accomplishes it: The bottom two are 'iffy' as you can see by all the if statements. Simply put there's no way for the compiler to be able to be sure that they'll work, since you can easily pass a bad propertyExpression.
class Program
{
static void Main(string[] args)
{
var guids = Enumerable.Range(0, 10).Select(i => Guid.NewGuid()).ToList();
var m2s = guids.Select(g => new Model2 { ID = g }).ToList();
var model1 = new Model1 { Model2ID = m2s[4].ID };
model1.LinkTo(m2s, (m1, m2) => m1.Model2 = m2, (m1, m2) => m2.ID == m1.Model2ID);
var model1a = new Model1 { Model2ID = m2s[4].ID };
model1a.LinkTo(m2s, m1 => m1.Model2, m1 => m1.Model2ID, m2 => m2.ID);
var model1b = new Model1 { Model2ID = m2s[4].ID };
model1b.LinkTo(m2s, m1 => m1.Model2, (m1, m2) => m1.Model2ID == m2.ID);
}
}
public static class ObjectExtensions
{
public static void LinkTo<T, U>(this T m1, IEnumerable<U> m2s, Action<T, U> action, Func<T, U, bool> filter)
{
if (m2s.Any(m2 => filter(m1, m2)))
{
var x = m2s.First(m2 => filter(m1, m2));
action(m1, x);
}
}
public static void LinkTo<T, U>(this T m1, IEnumerable<U> m2s, Expression<Func<T, U>> propertyExpression, Func<T, U, bool> filter)
{
var results = m2s.Where(m2 => filter(m1, m2));
if (!results.Any())
return;
var x = results.FirstOrDefault();
if (x != null)
{
var me = (propertyExpression.Body as MemberExpression);
if (me != null)
{
var pi = me.Member as PropertyInfo;
if (pi != null)
{
var setter = pi.GetSetMethod();
if (setter != null)
{
setter.Invoke(m1, new object[] { x });
}
}
}
}
}
public static void LinkTo<T, U, Key>(this T m1, IEnumerable<U> m2s, Expression<Func<T, U>> propertyExpression, Func<T, Key> tKey, Func<U, Key> uKey)
{
var results = Enumerable.Repeat(m1, 1)
.Join(m2s, tKey, uKey, (t, u) => u);
if(!results.Any())
return;
var x = results
.FirstOrDefault();
if (x != null)
{
var me = (propertyExpression.Body as MemberExpression);
if (me != null)
{
var pi = me.Member as PropertyInfo;
if (pi != null)
{
var setter = pi.GetSetMethod();
if (setter != null)
{
setter.Invoke(m1, new object[] { x });
}
}
}
}
}
}
You should read about extension methods. They are called "from" object.
For example you can write generic extension method as below
class Program
{
static void Main(string[] args)
{
var listOfModel2 = new Model1();
//You can call it from "object"
listOfModel2.MyExtensionMethod();
//Or directly as it is declared
ObjectExtensions.MyExtensionMethod(listOfModel2);
}
}
public static class ObjectExtensions
{
public static void MyExtensionMethod<T>(this T t)
{
//Do somthing
}
}