Get Entity Table Name - EF7 - c#

As the question states, anyone know how to get the entity table name in entity framework 7?
I have the code to do this in entity framework 6.1 (from another site, cant find link), but none of the interfaces/objects seem to be referenced in entity framework 7.
Code for EF6.1
public string GetTableName(Type type)
{
var metadata = ((IObjectContextAdapter)this).ObjectContext.MetadataWorkspace;
var objectItemCollection = ((ObjectItemCollection)metadata.GetItemCollection(DataSpace.OSpace));
var entityType = metadata.GetItems<EntityType>(DataSpace.OSpace).Single(e => objectItemCollection.GetClrType(e) == type);
var entitySet = metadata.GetItems(DataSpace.CSpace).Where(x => x.BuiltInTypeKind == BuiltInTypeKind.EntityType).Cast<EntityType>().Single(x => x.Name == entityType.Name);
var entitySetMappings = metadata.GetItems<EntityContainerMapping>(DataSpace.CSSpace).Single().EntitySetMappings.ToList();
EntitySet table = null;
var mapping = entitySetMappings.SingleOrDefault(x => x.EntitySet.Name == entitySet.Name);
if (mapping != null)
{
table = mapping.EntityTypeMappings.Single().Fragments.Single().StoreEntitySet;
}
else
{
mapping = entitySetMappings.SingleOrDefault(x => x.EntityTypeMappings.Where(y => y.EntityType != null).Any(y => y.EntityType.Name == entitySet.Name));
if (mapping != null)
{
table = mapping.EntityTypeMappings.Where(x => x.EntityType != null).Single(x => x.EntityType.Name == entityType.Name).Fragments.Single().StoreEntitySet;
}
else
{
var entitySetMapping = entitySetMappings.Single(x => x.EntityTypeMappings.Any(y => y.IsOfEntityTypes.Any(z => z.Name == entitySet.Name)));
table = entitySetMapping.EntityTypeMappings.First(x => x.IsOfEntityTypes.Any(y => y.Name == entitySet.Name)).Fragments.Single().StoreEntitySet;
}
}
return (string)table.MetadataProperties["Table"].Value ?? table.Name;
}

The only thing you need to do now to achieve the same is this:
public string GetTableName(Type type)
{
return this.Model.GetEntityType(type).SqlServer().TableName;
}
PS: I'm assuming this method is declared in your DbContext class, otherwise change the this keyword for your DbContext instance.
Update
GetEntityType was renamed to FindEntityType. You can find more info in this link
return this.Model.FindEntityType(type).SqlServer().TableName;

Related

Efficiently select a set of objects that have multi value keys

I have a ProductVersions model which has a multi part key, a int ProductId and a int VersionNum field.
The function below takes a list of simple Dto classes that are just these 2 fields with goal of returning a set full ProductVersion objects from the database that match.
Below is a less than efficient solution. I am expecting the incoming list to only between 2 to 4 items so its not too bad but I'd like to do better.
private async Task<List<ProductVersion>?> GetProductVersionsFromDto(IList<ProductVersionDto>? productVersionDtos)
{
List<ProductVersion>? productVersions = null;
if (productVersionDtos != null)
{
foreach (ProductVersionDto dto in productVersionDtos)
{
ProductVersion? productVersion = await myPortalDBContext.ProductVersions
.Where(pv => pv.ProductId == dto.ProductId && pv.VersionNum == dto.VersionNum)
.FirstOrDefaultAsync();
if (productVersion != null)
{
if (productVersions == null) productVersions = new List<ProductVersion>();
productVersions.Add(productVersion);
}
}
}
return productVersions;
}
I had consider something like this:
private async Task<List<ProductVersion>?> GetProductVersionsFromDto(IList<ProductVersionDto>? productVersionDtos)
{
List<ProductVersion>? productVersions = null;
if (productVersionDtos != null)
{
productVersions = await myPortalDBContext.ProductVersions
.Join(productVersionDtos,
pv => new { pv.ProductId, pv.VersionNum },
pvd => new { pvd.ProductId, pvd.VersionNum },
(pv, pvd) => pv)
.ToListAsync();
}
return productVersions;
}
but at runtime fails because the Join doesn't make sense. Anyone know of way to do this more efficiently with just a single round-trip into the dbContext?
Use FilterByItems extension and then you can generate desired query:
private async Task<List<ProductVersion>?> GetProductVersionsFromDto(IList<ProductVersionDto>? productVersionDtos)
{
if (productVersionDtos == null)
return null;
var productVersions = await myPortalDBContext.ProductVersions
.FilterByItems(productVersionDtos,
(pv, dto) => pv.ProductId == dto.ProductId && pv.VersionNum == dto.VersionNum, true)
.ToListAsync();
return productVersions;
}

'Expression of type 'System.String' cannot be used for parameter of type 'System.Reflection.PropertyInfo'

I have two classes - ContactCompany and inside List of ContactPeople.
The result must be - list of all contact people or a specific contact person that matches a certain criteria.
The criteria is a string and it will search all the string fields in both classes. If a ContactCompany is found , all the list of contact people will be displayed.
So Far I came up with this:
public List<ContactPersonDto> FilterContragentAndClients(string filter)
{
var contactCompanyStringProperties = typeof(ContactCompany).GetProperties().Where(prop => prop.PropertyType == filter.GetType() && prop.DeclaringType.Name != "AuditEntity`1");
var contactPersonStringProperties = typeof(ContactPerson).GetProperties().Where(prop => prop.PropertyType == filter.GetType());
var together = contactCompanyStringProperties.Concat(contactPersonStringProperties);
var allContactPersonFoundInCompany = this.contactCompanyRepository.GetAll(cc => contactCompanyStringProperties.Any
(prop => ((prop.GetValue(cc, null) == null) ? "" : prop.GetValue(cc, null).ToString().ToLower()) == filter)).SelectMany(acc => acc.ContactPeople).ToList();
var contactPersonOnItsOwn = contactPersonRepository.GetAll(cp => contactPersonStringProperties.Any
(prop => ((prop.GetValue(cp, null) == null) ? "" : prop.GetValue(cp, null).ToString().ToLower()) == filter));
var totalList = allContactPersonFoundInCompany.Concat(contactPersonOnItsOwn).Distinct().ToList().Take(100);
List<ContactPersonDto> result = new List<ContactPersonDto>();
foreach (var item in totalList)
{
result.Add(mapper.Map<ContactPersonDto>(item));
}
return result;
}
My idea was to check the property and its value, ToString() it and compare it with the criteria the user has inputted.
Just another note - I wrote the prop.Declarintype.Name in order to exclude AuditEntity properties.(Created By, Created At, etc.)
When I hit allContactPersonFoundInCompany the ToString() cannot be translated.
This is the full error I receive:
Expression of type 'System.String' cannot be used for parameter of type 'System.Reflection.PropertyInfo' of method 'Boolean Contains[PropertyInfo](System.Collections.Generic.IEnumerable`1[System.Reflection.PropertyInfo], System.Reflection.PropertyInfo)' (Parameter 'arg1')
I understood the implications of reflection , it is very slow, therefore here is my solution.
public List<ContactPersonDto> FilterContragentAndClients(string filter)
{
var contactPeopleInCompany = this.contactCompanyRepository.GetAll
(c => c.AccountablePerson.Contains(filter) |
c.Address.Contains(filter) ||
c.City.Contains(filter) ||
c.Name.Contains(filter) ||
c.Prefix.Contains(filter) ||
c.RegistrationNumber.Contains(filter)).SelectMany(c => c.ContactPeople);
var contactPerson = this.contactPersonRepository.GetAll
(cp => cp.Address.Contains(filter) ||
cp.Email.Contains(filter) ||
cp.Name.Contains(filter) ||
cp.PhoneNumber.Contains(filter) ||
cp.Prefix.Contains(filter));
var together = contactPeopleInCompany.Concat(contactPerson).Distinct().Take(100).ToList();
List<ContactPersonDto> result = new List<ContactPersonDto>();
foreach (var item in together)
{
result.Add(mapper.Map<ContactPersonDto>(item));
}
return result;
}

Entity Framework throwing exception with Extension Method

I have the following code in my Controller:
public List<DockDoorViewModel> GetDoorViewModel()
{
List<DockDoorViewModel> doors = new List<DockDoorViewModel>();
for (int i = 1; i < 11; i++)
{
// This is where the Stack Trace is pointing to.
DockDoorViewModel door = db.vwDockDoorDatas
.Where(x => x.DockNo == i)
.Select(x => x.ToDockDoorViewModel())
.FirstOrDefault();
if (door == null)
{
door = new DockDoorViewModel(i);
}
else
{
door.Items = db.vwDockDoorDatas
.Where(x => x.DockNo == i)
.Select(x => x.ToDockDoorItem())
.ToList();
}
doors.Add(door);
}
return doors;
}
I am getting this exception when I try and Run the Web App:
Exception Details: System.NotSupportedException: LINQ to Entities does not recognize the method 'DockDoorMonitor.Models.DockDoorViewModel ToDockDoorViewModel(DockDoorMonitor.Models.vwDockDoorData)' method, and this method cannot be translated into a store expression.
Here is the extension method:
public static class vwDockDoorDataExtensions
{
public static DockDoorViewModel ToDockDoorViewModel(this vwDockDoorData x)
{
DockDoorViewModel vm = null;
if (x != null)
{
vm = new DockDoorViewModel()
{
ID = x.ID,
DockNo = x.DockNo,
loadType = x.loadType,
LoadDescription = x.LoadDescription,
Name = x.Name,
LocationCode = x.LocationCode,
SACode = x.SACode
};
}
return vm;
}
public static DockDoorItem ToDockDoorItem(this vwDockDoorData x)
{
DockDoorItem vm = null;
if (x != null)
{
vm = new DockDoorItem()
{
ID = x.ItemNo,
Description = x.Description,
Quantity = x.Quantity,
UnitOfMeasure = x.UnitOfMeasure
};
}
return vm;
}
}
I've done this kind of thing before so I'm not seeing what I am doing wrong? This is my first time with a MVC5 and EF6 application.
The error message tells you everything you need to know really - EF can't translate your extension methods to SQL therefore throws an exception. You need to convert your query from LINQ to Entities to LINQ to Objects, this can be done with a simple call to AsEnumerable() e.g.
DockDoorViewModel door = db.vwDockDoorDatas.Where(x => x.DockNo == i)
.AsEnumerable()
.Select(x => x.ToDockDoorViewModel())
.FirstOrDefault();
Effectively, what this does is create a hybrid query where everything before the AsEnumerable is translated and executed as SQL and the remainder being executed client-side and in memory.
As per your performance issues, looking at your query again you are unnecessarily pulling across a lot of records, you are only after the first one so why not just pull that one over i.e.
vwDockDoorData entity = db.vwDockDoorDatas.Where(x => x.DockNo == i)
.FirstOrDefault();
DockDoorViewModel door = entity != null ? entity.ToDockDoorViewModel() : null;
A further improvement on that would be to simply filter the records before you iterate them (give you have a start/end range) e.g.
var doorDatas = db.vwDockDoorDatas.Where(x => x.DockNo >= 1 && x.DockNo <= 11)
.ToList();
for (int i = 0; i < doorDatas.Count; i++)
{
// This is where the Stack Trace is pointing to.
DockDoorViewModel door = data.ToDockDoorViewModel();
if (door == null)
{
door = new DockDoorViewModel(i+1);
}
else
{
door.Items = data.ToDockDoorItem();
}
doors.Add(door);
}
The above would only require a single trip to the DB.
You will have to load the data from the SQL Server before using your to method. You can do this (for example) with the following command:
door.Items = db.vwDockDoorDatas
.Where(x => x.DockNo == i)
.ToList() //Possibly use AsEnumerable() here instead as James says
.Select(x => x.ToDockDoorItem())
.ToList();

LINQ to Entities does not recognize the method and this method cannot be translated into a store expression

I call some date in my database using entity frame work. but My below code giving this error
LINQ to Entities does not recognize the method 'SchoolBreifcase.Compliance get_Item(Int32)' method, and this method cannot be translated into a store expression.
Here is my full code
FinancialCompliance financialCompliance = new FinancialCompliance();
List<Compliance> compliance = null;
if (HttpContext.Current.User.IsInRole("SchoolAdmin"))
{
compliance = datamodel.Compliances.Where(u => u.UserId == userId).OrderBy(c => c.AddedDate).ToList();
}
if (HttpContext.Current.User.IsInRole("User"))
{
compliance = datamodel.Compliances.Where(u => u.VerifierId == userId || u.OwnerId == userId).OrderBy(c => c.AddedDate).ToList();
}
if (compliance != null)
{
for (int i = 1; i < compliance.Count; i++)
{
financialCompliance = datamodel.FinancialCompliances.Where(f => f.ComplianceId == compliance[i].ComplianceId).SingleOrDefault();
if (compliance.Count == i)
{
return financialCompliance;
}
}
}
return financialCompliance;
}
This line give that error:
financialCompliance = datamodel.FinancialCompliances.Where(f => f.ComplianceId == compliance[i].ComplianceId).SingleOrDefault();
Does not help stack over flow answer
I have find some answers in this stack overflow site for
LINQ to Entities does not recognize the method
etc..But does not help to me .So I asked this question . Please don't any one close this question for reason of already asked
You need to create a variable to refer to compliance[i].ComplianceId then use it later.
for (int i = 1; i < compliance.Count; i++)
{
var complianceId = compliance[i].ComplianceId;
financialCompliance = datamodel.FinancialCompliances.Where(f => f.ComplianceId == complianceId ).SingleOrDefault();
if (compliance.Count == i)
{
return financialCompliance;
}
}
It's about the compliance[i].ComplianceId. Create a variable first:
var id = compliance[i].ComplianceId;
financialCompliance = datamodel.FinancialCompliances
.Where(f => f.ComplianceId == id).SingleOrDefault();

entity framework 5 MaxLength

I was using EF4 and a piece of code I found to get the MaxLength value from an entity like this:
public static int? GetMaxLength(string entityTypeName, string columnName)
{
int? result = null;
using (fooEntities context = new fooEntities())
{
Type entType = Type.GetType(entityTypeName);
var q = from meta in context.MetadataWorkspace.GetItems(DataSpace.CSpace)
.Where(m => m.BuiltInTypeKind == BuiltInTypeKind.EntityType)
from p in (meta as EntityType).Properties
.Where(p => p.Name == columnName
&& p.TypeUsage.EdmType.Name == "String")
select p;
var queryResult = q.Where(p =>
{
bool match = p.DeclaringType.Name == entityTypeName;
if (!match && entType != null)
{
match = entType.Name == p.DeclaringType.Name;
}
return match;
}).Select(sel => sel.TypeUsage.Facets["MaxLength"].Value);
if (queryResult.Any())
{
result = Convert.ToInt32(queryResult.First());
}
return result;
}
}
However, I upgraded to EF5 and I know get this error message:
...fooEntities' does not contain a definition for 'MetadataWorkspace' and no
extension method 'MetadataWorkspace' accepting a first argument of type
'...fooEntities' could be found (are you missing a using directive or an assembly
reference?)
What's the best way to get that meta data from EF5?
This is a very handy piece of code. I refactored it a bit and it's so useful I thought I would post it here.
public static int? GetMaxLength<T>(Expression<Func<T, string>> column)
{
int? result = null;
using (var context = new EfContext())
{
var entType = typeof(T);
var columnName = ((MemberExpression) column.Body).Member.Name;
var objectContext = ((IObjectContextAdapter) context).ObjectContext;
var test = objectContext.MetadataWorkspace.GetItems(DataSpace.CSpace);
if(test == null)
return null;
var q = test
.Where(m => m.BuiltInTypeKind == BuiltInTypeKind.EntityType)
.SelectMany(meta => ((EntityType) meta).Properties
.Where(p => p.Name == columnName && p.TypeUsage.EdmType.Name == "String"));
var queryResult = q.Where(p =>
{
var match = p.DeclaringType.Name == entType.Name;
if (!match)
match = entType.Name == p.DeclaringType.Name;
return match;
})
.Select(sel => sel.TypeUsage.Facets["MaxLength"].Value)
.ToList();
if (queryResult.Any())
result = Convert.ToInt32(queryResult.First());
return result;
}
}
And you can call it like:
GetMaxLength<Customer>(x => x.CustomerName);
This is assuming you've got a DbSet defined in your DbContext of type Customer, which has a property of CustomerName with a defined MaxLength.
This is very helpful for things like creating Model attributes that set a textbox's maxlength to the max length of the field in the database, always ensuring the two are the same.
I refactored mccow002's example into a copy-paste-ready Extension method class:
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Data.Metadata.Edm;
public static class DbContextExtensions
{
// get MaxLength as an extension method to the DbContext
public static int? GetMaxLength<T>(this DbContext context, Expression<Func<T, string>> column)
{
return (int?)context.GetFacets<T>(column)["MaxLength"].Value;
}
// get MaxLength as an extension method to the Facets (I think the extension belongs here)
public static int? GetMaxLength(this ReadOnlyMetadataCollection<Facet> facets)
{
return (int?)facets["MaxLength"].Value;
}
// just for fun: get all the facet values as a Dictionary
public static Dictionary<string,object> AsDictionary(this ReadOnlyMetadataCollection<Facet> facets) {
return facets.ToDictionary(o=>o.Name,o=>o.Value);
}
public static ReadOnlyMetadataCollection<Facet> GetFacets<T>(this DbContext context, Expression<Func<T, string>> column)
{
ReadOnlyMetadataCollection<Facet> result = null;
var entType = typeof(T);
var columnName = ((MemberExpression)column.Body).Member.Name;
var objectContext = ((IObjectContextAdapter)context).ObjectContext;
var test = objectContext.MetadataWorkspace.GetItems(DataSpace.CSpace);
if (test == null)
return null;
var q = test
.Where(m => m.BuiltInTypeKind == BuiltInTypeKind.EntityType)
.SelectMany(meta => ((EntityType)meta).Properties
.Where(p => p.Name == columnName && p.TypeUsage.EdmType.Name == "String"));
var queryResult = q.Where(p =>
{
var match = p.DeclaringType.Name == entType.Name;
if (!match)
match = entType.Name == p.DeclaringType.Name;
return match;
})
.Select(sel => sel)
.FirstOrDefault();
result = queryResult.TypeUsage.Facets;
return result;
}
}
It means that you have not only upgraded EF but you have also changes the API. There are two APIs - the core ObjectContext API and simplified DbContext API. Your code is dependent on ObjectContext API (the only API available in EF4) but EF5 uses DbContext API (added in separate EntityFramework.dll assembly since EF4.1). If you want to use new EF features and your previous code you should just upgrade to .NET 4.5.
If you also want to use a new API you will have to update a lot of your existing code but it is still possible to get ObjectContext from DbContext and make your method work again. You just need to use this snippet:
var objectContext = ((IObjectContextAdapter)context).ObjectContext;
and use objectContext instead of context in your code.
I had similar issue and solution is here;
MyDBEntities ctx = new MyDBEntities();
var objectContext = ((IObjectContextAdapter)ctx).ObjectContext;
var cols = from meta in objectContext.MetadataWorkspace.GetItems(DataSpace.CSpace)
.Where(m => m.BuiltInTypeKind == BuiltInTypeKind.EntityType)
from p in (meta as EntityType).Properties
.Where(p => p.DeclaringType.Name == "TableName")
select new
{
PropertyName = p.Name
};

Categories