I am converting an Expression<T, bool> to an Expression<Y, bool> where T and Y are different entities not related in any way other than through an Automapper mapping. Essentially, I have a Model object that my code uses:
public class Store
{
public string StoreId { get; set; }
public string Name { get; set; }
public List<Phone> Phones { get; set; }
public Address Address { get; set; }
public Account Account { get; set; }
public Status Status { get; set; }
}
that I am mapping to an entity object to store in my mongo database:
public class Store : MongoEntity
{
public string AccountId { get; set; }
public string Name { get; set; }
public List<string> UserIds { get; set; }
public List<Phone> PhoneNumbers { get; set; }
public Address Address { get; set; }
}
public abstract class MongoEntity : IMongoEntity
{
[BsonId]
public ObjectId Id { get; set; }
public Status Status { get; set; }
}
I used the answer in this question to work out how to convert between expressions (Question), and that got me 90% there. I was able to modify the code to grab the AutoMapper mappings between my Model store and my entity store and grab the destination property from from the source property:
private Expression<Func<TNewTarget, bool>> TransformPredicateLambda<TOldTarget, TNewTarget>(
Expression<Func<TOldTarget, bool>> predicate)
{
var lambda = (LambdaExpression)predicate;
if (lambda == null)
{
throw new NotSupportedException();
}
//Modified here to get automapper mappings
var maps = Mapper.FindTypeMapFor<TOldTarget, TNewTarget>();
var mutator = new ExpressionTargetTypeMutator(t => typeof(TNewTarget), maps);
var explorer = new ExpressionTreeExplorer();
var converted = mutator.Visit(predicate.Body);
return Expression.Lambda<Func<TNewTarget, bool>>(
converted,
lambda.Name,
lambda.TailCall,
explorer.Explore(converted).OfType<ParameterExpression>());
}
protected override Expression VisitMember(MemberExpression node)
{
var dataContractType = node.Member.ReflectedType;
var activeRecordType = _typeConverter(dataContractType);
PropertyMap prop = null;
foreach (var propertyMap in _maps)
{
var source = propertyMap.SourceMember;
var dest = propertyMap.DestinationProperty;
if (source != null && source.Name == node.Member.Name)
{
prop = propertyMap;
}
}
if (prop == null)
{
return base.VisitMember(node);
}
var propertyName = prop.DestinationProperty.Name;
var property = activeRecordType.GetProperty(propertyName);
var converted = Expression.MakeMemberAccess(
base.Visit(node.Expression),
property
);
return converted;
}
The problem is, my entity object doesn't have all of the same properties as my Model object (Account object versus AccountId for example). When the Transformer gets to the Account property on the Model object, I get an Exception (because there is no matching property on my Entity object). I cannot return null from VisitMember, and new Expression() is not allowed either. How can I handle ignoring properties on my Model object that do not exist on my Entity object?
Updating with info from Comments
So, to be a little more clear, I am using Automapper to map from a Models.Store to an Entity.Store. My entity.Store only has an AccountId (because I don't want to duplicate all of the account data), but my Models.Store needs the whole account object (which I would get by querying the Accounts collection).
Automapper is bascially converting my Account object to just an AccountId on my entity. Therefore, when I search for x => x.Account.AccountId == abcd1234 (where x is a models.Store), I need my expression to convert to x => x.AccountId == abcd1234 (where x is an Entity.Store).
I have that part working (changing mS => mS.Account.AccountId == 1234 to mE => mE.AccountId == 1234). The problem I am having now is that after doing the AccountId property, VisitMember is called with Account as the node. Since there is no Account in my Entity.Store object, I get the exception.
It's rather hard to test a solution without testable/runnable code. But here's a guess
Given the following expression mS => mS.Account.AccountId == 1234 and looking to transform MemberExpressions, you'll get the following calls:
VisitMember(mS.Account.AccountId
VisitMember(mS.Account)
You want to transform the second one into mE.AccountId. This involves two transformations: One, changing the property access from (EntityType).(AccountType).AccountId to (MongoStoreType).AccountId, and also changing the underlying object. If you're already handling the parameter transformations in other methods of your ExpressionVisitor, probably VisitParameter and VisitLambda, you'll be fine there. You then just need to skip looking at the parent MemberAccess, and jump straight to the grandparent:
var converted = Expression.MakeMemberAccess(
base.Visit(node.Expression),
property
);
return converted;
becomes something like this:
var parentMember = node.Expression as MemberExpression;
if (parentMember != null)
{
var grandparent = parentMember.Expression;
var converted = Expression.MakeMemberAccess(
base.Visit(grandparent),
property
);
return converted;
}
else
{
var converted = Expression.MakeMemberAccess(
base.Visit(node.Expression),
property
);
return converted;
}
Related
Following this documentation, I am trying to create some entities with properties of an owned type:
[Owned]
public class StreetAddress
{
public string Street { get; set; }
public string City { get; set; }
}
public class Order
{
public int Id { get; set; }
public StreetAddress BillingAddress { get; set; }
public StreetAddress ShippingAddress { get; set; }
}
By convention, EF will create the following properties in the entity Order:
BillingAddress_Street
BillingAddress_City
ShippingAddress_Street
ShippingAddress_City
I know that there is a way to rename the columns:
modelBuilder.Entity<Order>().OwnsOne(
o => o.ShippingAddress,
sa =>
{
sa.Property(p => p.Street).HasColumnName("ShipsToStreet");
sa.Property(p => p.City).HasColumnName("ShipsToCity");
});
however, this means that I would need to use this for every property that will be generated. In my opinion, this is exactly what I wanted to avoid by using the owned types in the first place.
What I would like to do, is to create a custom convention, however, maybe using the model builder and explicitly telling him how to name those properties getting rid of the underscore and so on.
I have tried dvelving into the modelBuilder properties, trying to get all of all entities of that owned type, but that does not seem to work.
Is there a way to solve this?
So I could not sleep and went back to trying, and was able to solve it:
var ownedTypes = modelBuilder.Model.GetEntityTypes().Where(e => e.IsOwned());
foreach (var type in ownedTypes)
{
foreach (var prop in type.GetProperties())
{
var entityName = type.DefiningEntityType.Name;
var ownedType = type.ClrType;
var navigationName = type.DefiningNavigationName;
var propertyName = prop.Name;
var newColumnName = $"{navigationName}{propertyName}";
var primaryKeyName = modelBuilder.Model.GetEntityTypes().SingleOrDefault(e => e.Name == entityName)?.GetProperties().SingleOrDefault(p => p.IsPrimaryKey())?.Name;
if (string.IsNullOrWhiteSpace(primaryKeyName) || propertyName != primaryKeyName)
{
modelBuilder.Entity(entityName).OwnsOne(ownedType, navigationName, sa =>
{
sa.Property(propertyName).HasColumnName(newColumnName);
});
}
}
}
I would still be glad if you could point me to a better, cleaner solution, should there be one. Or maybe there are some issues here in my code.
I am building a system of filters to manipulate a simple collection that looks like:
public class ProcessedData
{
public byte ProcessId { get; set; }
public List<EventsData> EventsData { get; set; } = new List<EventsData>();
}
And EventsData object has its id property inside something like:
public class EventData
{
public byte EventDataId { get; set; }
}
Now the idea is simple, I have a collection of selected ProcessId and EventId in form of the two list like:
public List<byte> SelectedProcessFilter { get; set; }
public List<byte> SelectedEventFilter { get; set; }
So I have prepared aggregate where conditions function what looks like:
public IEnumerable<ProcessedData> DataFilter(IEnumerable<ProcessedData> preprocessedData, Expression<Func<ProcessedData, bool>>[] filters)
{
if (filters != null)
{
preprocessedEvents = filters.Aggregate(preprocessedData, (current, filter) => current.Where(filter.Compile()));
}
return preprocessedEvents.ToList();
}
And now i can declare where conditions something like:
private Expression<Func<ProcessedData, bool>>[] BuildPredicate()
{
var predicates = new List<Expression<Func<ProcessedData, bool>>>();
if (SelectedProcessFilter != null && SelectedProcessFilter .Any())
{
predicates.Add(data => SelectedProcessFilter.Contains(data.ProcessId));
}
if (SelectedEventFilter != null && SelectedEventFilter.Any())
{
predicates.Add(data => SelectedEventFilter.Any(value => data.EventsData.Any(target => target.EventDataId == (byte)value.Value)));
}
return predicates.ToArray();
}
Usage is simple just:
IEnumerable<ProcessedData> filteredPreprocessedData = service.DataFilter(preprocessedEvents, BuildPredicate());
And basically works, but there is one thins when i filter on EventsDataId, it checks if such value is in that property only, Is it possible to fit somehow possibility to rebuild such property so it will contain only selected EventId ?
For now as what i did, I fallowed NetMage advise and just made a copy of object with new properties values.
I have a problem when I update a line with a foreign key. The principle idea is to update a row in the database with a generic method but I have an exception when I save modification in the data base so I try to make the state of entity os modified but not the worker.
else if (ModeButtonVMCaracteristiquesType == ModeButtonVMCaracteristiqueType.EDITIONCaracteristiqueTypeItem)
{
int idCaracteristiqueSelected = Convert.ToInt32(CaracteristiqueSelected.idCharacteristicItem);
var LineModified = (from x in ImItemsModel.imtypeitems select x.imcharacteristicsitems).ToList();
LineModified.ForEach(p => ImItemsModel.Entry(p).State = System.Data.Entity.EntityState.Modified);
var UpdateCaracteristicTypeItem = StaticGenericUpdate.UpadateRowInModel<imcharacteristicsitem>(ImItemsModel, "idCharacteristicItem", idCaracteristiqueSelected, "fk_idTypeItemIMCaracteristicsItems", fk_value, propertiesForModel, propertiesForView, this);
ImItemsModel.SaveChanges();
So I have two models:
public partial class imcharacteristicsitem
{
public imcharacteristicsitem()
{
this.imvaluesofitemscaracteristics = new HashSet<imvaluesofitemscaracteristic>();
}
public int idCharacteristicItem { get; set; }
public string characteristicItem { get; set; }
public string unitCaracteristicItem { get; set; }
public int fk_idTypeItemIMCaracteristicsItems { get; set; }
public byte[] typeValueCaracteristicItem { get; set; }
public virtual ICollection<imvaluesofitemscaracteristic> imvaluesofitemscaracteristics { get; set; }
public virtual imtypeitems imtypeitem { get; set; }
}
and:
public partial class imtypeitems
{
public imtypeitems()
{
this.imcharacteristicsitems = new HashSet<imcharacteristicsitem>();
this.imitems = new HashSet<imitem>();
}
public int idTypeItem { get; set; }
public string DesignationTypeItem { get; set; }
public byte[] SymbolTypeItem { get; set; }
public Nullable<int> MaxNumberConnectionsTypeItem { get; set; }
public virtual ICollection<imcharacteristicsitem> imcharacteristicsitems { get; set; }
public virtual ICollection<imitem> imitems { get; set; }
}
and the generic method is:
public static T UpadateRowInModel<T>(DbContext Model, string NameiD, int IdSelected, string NameFk_key ,int fk_key, IList<PropertyInfo> propertiesModel, IList<PropertyInfo> propertiesView, VMCaracteristicType vm) where T : class
{
T item = Model.Set<T>().Find(IdSelected);
foreach (var property in propertiesModel)
{
if (propertiesView.Count != 0)
{
property.SetValue(item, propertiesView.FirstOrDefault(elem => elem.Name.Equals(property.Name)) == null ? null :
propertiesView.FirstOrDefault(elem => elem.Name.Equals(property.Name)).GetValue(vm));
}
if (property.Name == NameiD)
{
property.SetValue(item, IdSelected);
}
if (property.Name == NameFk_key)
{
property.SetValue(item, null);
Model.Entry(item).Property(NameFk_key).IsModified = false;
}
}
return item;
}
EDIT :
so i realise that the probleme is the entity framework can't save because my table imtypeitems have a collection of caractéristique
public virtual ICollection<imcharacteristicsitem> imcharacteristicsitems { get; set; }
so i must delet the row that i wish update it from this table and after that i will save so i can't delet the row of collection i try like this :
var RowBeforupdate = ImItemsModel.imcharacteristicsitems.Include("imtypeitem").Single(row => row.idCharacteristicItem == idCaracteristiqueSelected);
var UpdateCaracteristicTypeItem = StaticGenericUpdate.UpadateRowInModel<imcharacteristicsitem>(ImItemsModel, "idCharacteristicItem", idCaracteristiqueSelected, "fk_idTypeItemIMCaracteristicsItems", fk_value, propertiesForModel, propertiesForView, this);
ImItemsModel.Entry(RowBeforupdate).CurrentValues.SetValues(UpdateCaracteristicTypeItem);
just a ps : i am not a expert in entity framework :(
The most likely cause for the FK null exception is that the FK entity property is processed/set in the foreach loop after the FK_ID property. So even if you change the FK_ID IsModified value to false, you're most likely later setting the imtypeitem property to null.
Also you are changing all model properties based on view properties; so if there's no particular view property you'll be setting the model property to null. This is seldom the thing you want to be doing in this scenario. Usually you want to change just the corresponding view properties and leave the other model properties unchanged.
So the foreach part of your generic method should look like this:
if (propertiesView.FirstOrDefault(elem => elem.Name.Equals(property.Name)) != null)
{
property.SetValue(item, propertiesView.First(elem => elem.Name.Equals(property.Name)).GetValue(vm));
}
If you have certain properties that are part of view properties but you don't want them to be updated you can just skip them (using your Nameid and NameFk_key):
if(property.Name == NameiD || property.Name == NameFk_key)
continue;
So the final form of the generic method might look something like this:
public static T UpadateRowInModel<T>(DbContext Model, string NameiD, int IdSelected, string NameFk_key ,int fk_key, IList<PropertyInfo> propertiesModel, IList<PropertyInfo> propertiesView, VMCaracteristicType vm) where T : class
{
T item = Model.Set<T>().Find(IdSelected);
foreach (var property in propertiesModel)
{
if(property.Name == NameiD || property.Name == NameFk_key)
continue;
var viewP = propertiesView.FirstOrDefault(elem => elem.Name.Equals(property.Name));
if (viewP != null)
{
property.SetValue(item, viewP.GetValue(vm));
}
}
return item;
}
There's also a variation possible where you do a foreach on the propertiesView; also the parameters for the method can be adjusted.
On a sidenote: I'm not sure if
var LineModified = (from x in ImItemsModel.imtypeitems select x.imcharacteristicsitems).ToList();
LineModified.ForEach(p => ImItemsModel.Entry(p).State = System.Data.Entity.EntityState.Modified);
does something useful, or if it does anything. The p in lambda is an ICollection of enity types and not a separate DB context entity. Not to mention that you want to set Modified state for ALL entities of type imcharacteristicsitem. Maybe you can explain the reason for this.
I have entities in the DB which each contain a list of key value pairs as metadata. I want to return a list of object by matching on specified items in the metadata.
Ie if objects can have metadata of KeyOne, KeyTwo and KeyThree, I want to be able to say "Bring me back all objects where KeyOne contains "abc" and KeyThree contains "de"
This is my C# query
var objects = repository.GetObjects().Where(t =>
request.SearchFilters.All(f =>
t.ObjectChild.Any(tt =>
tt.MetaDataPairs.Any(md =>
md.Key.ToLower() == f.Key.ToLower() && md.Value.ToLower().Contains(f.Value.ToLower())
)
)
)
).ToList();
and this is my request class
[DataContract]
public class FindObjectRequest
{
[DataMember]
public IDictionary<string, string> SearchFilters { get; set; }
}
And lastly my Metadata POCO
[Table("MetaDataPair")]
public class DbMetaDataPair : IEntityComparable<DbMetaDataPair>
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long Id { get; set; }
[Required]
public string Key { get; set; }
public string Value { get; set; }
}
The error I get is
Error was Unable to create a constant value of type
'System.Collections.Generic.KeyValuePair`2[[System.String, mscorlib,
Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089],[System.String, mscorlib,
Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]'.
Only primitive types or enumeration types are supported in this
context.
So it looks like the f variable in your query is a KeyValuePair<string, string>. What you need to do is save them as local variables before you use them as a KVP cannot be converted to SQL.
var filters = request.SearchFilters.Select(kvp => new[] { kvp.Key, kvp.Value }).ToArray();
var objects = repository.GetObjects().Where(t =>
filters.All(f =>
t.ObjectChild.Any(tt =>
tt.MetaDataPairs.Any(md =>
md.Key.ToLower() == f[0] && md.Value.ToLower().Contains(f[1])
)
)
)
).ToList();
What you have to remember is that anything you do in LINQ in EF when it is still of the type IQueryable<T> - which is before you call ToList(), ToArray(), ToDictionary() and maybe even AsEnumerable() (I have honestly never tried AsEnumerable()) - must be able to be represented as SQL. That means that only SQL types (string, int, long, byte, date, etc..) and entity types defined in your DbContext can be used in the query. Everything else needs to be broken apart into one of those forms.
EDIT:
You could also try coming from the other side of the query, but you are going to need a few things first..
The Model ...
[Table("MetaDataPair")]
public class DbMetaDataPair : IEntityComparable<DbMetaDataPair>
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long Id { get; set; }
[Required]
public string Key { get; set; }
public string Value { get; set; }
// this is the navigation property back up to the ObjectChild
public virtual ObjectChild ObjectChild { get;set; }
}
public ObjectChild
{
...
public ICollection<MetaDataPair> MetaDataPairs { get; set; }
// this is the navigation property back up to the "Object"
public virtual Object Object { get; set; }
...
}
Now for the query...
public IEnumerable<Object> GetObjectsFromRequest(FindObjectRequest request)
{
foreach(var kvp in request.SearchFilters)
{
var key = kvp.Key;
var value = kvp.Value;
yield return metaDataRepository.MetaDataPairs
.Where(md => md.Key.ToLower() == key && md.Value.ToLower().Contains(value))
.Select(md => md.ObjectChild.Object)
}
}
This should execute 'n' number of SQL queries for the number of meta pairs you need to match. The better option would be trying to union them somehow but without some code to play with that might be a mission.
As a side note: I don't really know the names of your classes so I have used > > what I can work out. Obviously Object is not the name of the model..
I'm extremely new to ASP .NET and LINQ so please forgive me for my ignorance.
I've a Region class:
public class Region
{
[Key]
public int Region_ID { get; set; }
public string Region_Name { get; set; }
}
And a Service class:
public class Service
{
[Key]
public int Service_ID { get; set; }
public string Service_Name { get; set; }
}
And a mapping class which stores the many-many mapping of service_IDs with region_IDs:
public class Mapping_ServiceToRegion
{
[Key]
public int Service_ID { get; set; }
public int Region_ID { get; set; }
}
Now I want to create an API function which outputs Region_Name based on given Service_ID. This is what I have so far in my RegionsController:
// GET api/Regions/Service_ID
[ResponseType(typeof(Region))]
public async Task<IHttpActionResult> GetRegion(int id)
{
var region_id = from sr in db.Mapping_ServiceToRegions
where sr.Service_ID == id
select sr.Region_ID;
var region = await db.Regions.Select(r =>
new Region()
{
Region_ID = r.Region_ID,
Region_Name = r.Region_Name
}).SingleOrDefaultAsync(r => r.Region_ID == region_id); //ERROR
if (region == null)
{
return NotFound();
}
return Ok(region);
}
The error I'm getting is:
Cannot convert lambda expression because it is not a delegate type.
I realize that my region_id variable will have multiple region_ids based on a service_id. How can I modify the code to account for this? Is there an IN operator that I can use to say r.Region_ID IN region_id?
And does the above code look correct otherwise?
Thanks.
You should change the SingleOrDefaultAsync() call using Contains() method like below since your region_id is of IEnumerable<T> and not a single value and so you can't perform direct equality comparison.
SingleOrDefaultAsync(r => region_id.Contains(r.Region_ID))
Ahh!!! here Region is one of EF mapped entity and you are trying to construct that and thus the error. You should either chose to select an Anonymous type (or) use a custom viewmodel/DTO object like
var region = await db.Regions.Select(r =>
new
{
Region_ID = r.Region_ID,
Region_Name = r.Region_Name
}).SingleOrDefaultAsync(r => region_id.Contains(r.Region_ID));