Entity Framework throwing exception with Extension Method - c#

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();

Related

An unhandled exception of type 'System.StackOverflowException' occurred in EntityFramework dll

I get this exception when I try to process 270k records. It fails at 12k. Can someone explain to me what I am missing?
The database is SQL and I am using EF 6. I am using predicate builder to build my where clause.
The idea being
select * from table where ((a = 'v1' and b = 'v2') or (a = 'v11' and b = 'v21') or (a = 'v12' and b = 'v22') ..)
I don't see anywhere that I still hold reference to my object that represents EF class. I am creating a POCO for the result I want to send back to view.
Any ideas?
Also I am using CommandTimeout of 10000 and the point where it fails, when I run the query with same paramters in sql management studio, it returns 400 rows.
When I ran profiler, I noticed a few seconds before I got the error, memory usage shot up to 1GB+
Thanks
public List<SearchResult> SearchDocuments(List<SearchCriteria> searchCriterias)
{
List<SearchResult> results = new List<SearchResult>();
var fieldSettings = GetData() ;// make a call to database to get this data
using (var context = CreateContext())
{
var theQuery = PredicateBuilder.False<ViewInSqlDatabase>();
int skipCount = 0;
const int recordsToProcessInOneBatch = 100;
while (searchCriterias.Skip(skipCount).Any())
{
var searchCriteriasBatched = searchCriterias.Skip(skipCount).Take(recordsToProcessInOneBatch);
foreach (var searchCriteria in searchCriteriasBatched)
{
var queryBuilder = PredicateBuilder.True<ViewInSqlDatabase>();
// theQuery
if (searchCriteria.State.HasValue)
queryBuilder = queryBuilder.And(a => a.State == searchCriteria.State.Value);
if (!string.IsNullOrWhiteSpace(searchCriteria.StateFullName))
queryBuilder = queryBuilder.And(a => a.StateName.Equals(searchCriteria.StateFullName, StringComparison.CurrentCultureIgnoreCase));
if (searchCriteria.County.HasValue)
queryBuilder = queryBuilder.And(a => a.County == searchCriteria.County.Value);
if (!string.IsNullOrWhiteSpace(searchCriteria.CountyFullName))
queryBuilder = queryBuilder.And(a => a.CountyName.Equals(searchCriteria.CountyFullName, StringComparison.CurrentCultureIgnoreCase));
if (!string.IsNullOrWhiteSpace(searchCriteria.Township))
queryBuilder = queryBuilder.And(a => a.Township == searchCriteria.Township);
// and so on...for another 10 parameters
theQuery = theQuery.Or(queryBuilder.Expand());
}
// this is where I get error after 12k to 15k criterias have been processed
var searchQuery = context.ViewInSqlDatabase.AsExpandable().Where(theQuery).Distinct().ToList();
foreach (var query in searchQuery)
{
var newResultItem = SearchResult.Create(query, fieldSettings); // POCO object with no relation to database
if (!results.Contains(newResultItem))
results.Add(newResultItem);
}
skipCount += recordsToProcessInOneBatch;
}
}
return results.Distinct().OrderBy(a => a.State).ThenBy(a => a.County).ThenBy(a => a.Township).ToList();
}
Fourat is correct that you can modify your query to context.SearchResults.Where(x => ((x.a == 'v1' &&x.b == 'v2') || (x.a = 'v11' &&x.b = 'v21') || (x.a = 'v12' && x.b = 'v22')).Distinct().OrderBy(a => a.State).ThenBy(a => a.County).ThenBy(a => a.Township).ToList(); What this do with make the database do the heavy lifting for you and you
I would also suggest that you use lazy evaluation instead of forcing it into a list if you can.

Optimizing LINQ routines

I run a build system. Datawise the simplified description would be that I have Configurations and each config has 0..n Builds.
Now builds produce artifacts and some of these are stored on server. What I am doing is writing kind of a rule, that sums all the bytes produced per configuration builds and checks if these are too much.
The code for the routine at the moment is following:
private void CalculateExtendedDiskUsage(IEnumerable<Configuration> allConfigurations)
{
var sw = new Stopwatch();
sw.Start();
// Lets take only confs that have been updated within last 7 days
var items = allConfigurations.AsParallel().Where(x =>
x.artifact_cleanup_type != null && x.build_cleanup_type != null &&
x.updated_date > DateTime.UtcNow.AddDays(-7)
).ToList();
using (var ctx = new LocalEntities())
{
Debug.WriteLine("Context: " + sw.Elapsed);
var allBuilds = ctx.Builds;
var ruleResult = new List<Notification>();
foreach (var configuration in items)
{
// all builds for current configuration
var configurationBuilds = allBuilds.Where(x => x.configuration_id == configuration.configuration_id)
.OrderByDescending(z => z.build_date);
Debug.WriteLine("Filter conf builds: " + sw.Elapsed);
// Since I don't know which builds/artifacts have been cleaned up, calculate it manually
if (configuration.build_cleanup_count != null)
{
var buildCleanupCount = "30"; // default
if (configuration.build_cleanup_type.Equals("ReserveBuildsByDays"))
{
var buildLastCleanupDate = DateTime.UtcNow.AddDays(-int.Parse(buildCleanupCount));
configurationBuilds = configurationBuilds.Where(x => x.build_date > buildLastCleanupDate)
.OrderByDescending(z => z.build_date);
}
if (configuration.build_cleanup_type.Equals("ReserveBuildsByCount"))
{
var buildLastCleanupCount = int.Parse(buildCleanupCount);
configurationBuilds =
configurationBuilds.Take(buildLastCleanupCount).OrderByDescending(z => z.build_date);
}
}
if (configuration.artifact_cleanup_count != null)
{
// skipped, similar to previous block
}
Debug.WriteLine("Done cleanup: " + sw.Elapsed);
const int maxDiscAllocationPerConfiguration = 1000000000; // 1GB
// Sum all disc usage per configuration
var confDiscSizePerConfiguration = configurationBuilds
.GroupBy(c => new {c.configuration_id})
.Where(c => (c.Sum(z => z.artifact_dir_size) > maxDiscAllocationPerConfiguration))
.Select(groupedBuilds =>
new
{
configurationId = groupedBuilds.FirstOrDefault().configuration_id,
configurationPath = groupedBuilds.FirstOrDefault().configuration_path,
Total = groupedBuilds.Sum(c => c.artifact_dir_size),
Average = groupedBuilds.Average(c => c.artifact_dir_size)
}).ToList();
Debug.WriteLine("Done db query: " + sw.Elapsed);
ruleResult.AddRange(confDiscSizePerConfiguration.Select(iter => new Notification
{
ConfigurationId = iter.configurationId,
CreatedDate = DateTime.UtcNow,
RuleType = (int) RulesEnum.TooMuchDisc,
ConfigrationPath = iter.configurationPath
}));
Debug.WriteLine("Finished loop: " + sw.Elapsed);
}
// find owners and insert...
}
}
This does exactly what I want, but I am thinking if I could make it any faster. Currenly I see:
Context: 00:00:00.0609067
// first round
Filter conf builds: 00:00:00.0636291
Done cleanup: 00:00:00.0644505
Done db query: 00:00:00.3050122
Finished loop: 00:00:00.3062711
// avg round
Filter conf builds: 00:00:00.0001707
Done cleanup: 00:00:00.0006343
Done db query: 00:00:00.0760567
Finished loop: 00:00:00.0773370
The SQL generated by .ToList() looks very messy. (Everything that is used in WHERE is covered with an index in DB)
I am testing with 200 configurations, so this adds up to 00:00:18.6326722. I have a total of ~8k items that need to get processed daily (so the whole routine takes more than 10 minutes to complete).
I have been randomly googling around this internet and it seems to me that Entitiy Framework is not very good with parallel processing. Knowing that I still decided to give this async/await approch a try (First time a tried it, so sorry for any nonsense).
Basically if I move all the processing out of scope like:
foreach (var configuration in items)
{
var confDiscSizePerConfiguration = await GetData(configuration, allBuilds);
ruleResult.AddRange(confDiscSizePerConfiguration.Select(iter => new Notification
{
... skiped
}
And:
private async Task<List<Tmp>> GetData(Configuration configuration, IQueryable<Build> allBuilds)
{
var configurationBuilds = allBuilds.Where(x => x.configuration_id == configuration.configuration_id)
.OrderByDescending(z => z.build_date);
//..skipped
var confDiscSizePerConfiguration = configurationBuilds
.GroupBy(c => new {c.configuration_id})
.Where(c => (c.Sum(z => z.artifact_dir_size) > maxDiscAllocationPerConfiguration))
.Select(groupedBuilds =>
new Tmp
{
ConfigurationId = groupedBuilds.FirstOrDefault().configuration_id,
ConfigurationPath = groupedBuilds.FirstOrDefault().configuration_path,
Total = groupedBuilds.Sum(c => c.artifact_dir_size),
Average = groupedBuilds.Average(c => c.artifact_dir_size)
}).ToListAsync();
return await confDiscSizePerConfiguration;
}
This, for some reason, drops the execution time for 200 items from 18 -> 13 sec. Anyway, from what I understand, since I am awaiting each .ToListAsync(), it is still processed in sequence, is that correct?
So the "can't process in parallel" claim starts coming out when I replace the foreach (var configuration in items) with Parallel.ForEach(items, async configuration =>. Doing this change results in:
A second operation started on this context before a previous
asynchronous operation completed. Use 'await' to ensure that any
asynchronous operations have completed before calling another method
on this context. Any instance members are not guaranteed to be thread
safe.
It was a bit confusing to me at first as I await practically in every place where the compiler allows it, but possibly the data gets seeded to fast.
I tried to overcome this by being less greedy and added the new ParallelOptions {MaxDegreeOfParallelism = 4} to that parallel loop, peasant assumption was that default connection pool size is 100, all I want to use is 4, should be plenty. But it still fails.
I have also tried to create new DbContexts inside the GetData method, but it still fails. If I remember correctly (can't test now), I got
Underlying connection failed to open
What possibilities there are to make this routine go faster?
Before going in parallel, it is worth to optimize query itself. Here are some suggestions that might improve your times:
1) Use Key when working with GroupBy. This might solve issue of complex & nested SQL query as in that way you instruct Linq to use the same keys defined in GROUP BY and not to create sub-select.
var confDiscSizePerConfiguration = configurationBuilds
.GroupBy(c => new { ConfigurationId = c.configuration_id, ConfigurationPath = c.configuration_path})
.Where(c => (c.Sum(z => z.artifact_dir_size) > maxDiscAllocationPerConfiguration))
.Select(groupedBuilds =>
new
{
configurationId = groupedBuilds.Key.ConfigurationId,
configurationPath = groupedBuilds.Key.ConfigurationPath,
Total = groupedBuilds.Sum(c => c.artifact_dir_size),
Average = groupedBuilds.Average(c => c.artifact_dir_size)
})
.ToList();
2) It seems that you are bitten by N+1 problem. In simple words - you execute one SQL query to get all configurations and N another ones to get build information. In total that would be ~8k small queries where 2 bigger queries would suffice. If used memory is not a constraint, fetch all build data in memory and optimize for fast lookup using ToLookup.
var allBuilds = ctx.Builds.ToLookup(x=>x.configuration_id);
Later you can lookup builds by:
var configurationBuilds = allBuilds[configuration.configuration_id].OrderByDescending(z => z.build_date);
3) You are doing OrderBy on configurationBuilds multiple times. Filtering does not affect record order, so you can safely remove extra calls to OrderBy:
...
configurationBuilds = configurationBuilds.Where(x => x.build_date > buildLastCleanupDate);
...
configurationBuilds = configurationBuilds.Take(buildLastCleanupCount);
...
4) There is no point to do GroupBy as builds are already filtered for a single configuration.
UPDATE:
I took it one step further and created code that would retrieve same results as your provided code with a single request. It should be more performant and use less memory.
private void CalculateExtendedDiskUsage()
{
using (var ctx = new LocalEntities())
{
var ruleResult = ctx.Configurations
.Where(x => x.build_cleanup_count != null &&
(
(x.build_cleanup_type == "ReserveBuildsByDays" && ctx.Builds.Where(y => y.configuration_id == x.configuration_id).Where(y => y.build_date > buildLastCleanupDate).Sum(y => y.artifact_dir_size) > maxDiscAllocationPerConfiguration) ||
(x.build_cleanup_type == "ReserveBuildsByCount" && ctx.Builds.Where(y => y.configuration_id == x.configuration_id).OrderByDescending(y => y.build_date).Take(buildCleanupCount).Sum(y => y.artifact_dir_size) > maxDiscAllocationPerConfiguration)
)
)
.Select(x => new Notification
{
ConfigurationId = x.configuration_id,
ConfigrationPath = x.configuration_path
CreatedDate = DateTime.UtcNow,
RuleType = (int)RulesEnum.TooMuchDisc,
})
.ToList();
}
}
First make a new context every parallel.foreach of you going to go that route. But u need to write a query that gets all the needed data in one trip. To speed up ef u can also disable change tracking or proxies on the context when ur reading data.
There are a lot of places for optimizations...
There are places where you should put .ToArray() to avoid asking multiple time to server...
I did a lot of refactor, but I'm unable to check, due lack of more information.
Maybe this can lead you to a better solution...
private void CalculateExtendedDiskUsage(IEnumerable allConfigurations)
{
var sw = new Stopwatch();
sw.Start();
using (var ctx = new LocalEntities())
{
Debug.WriteLine("Context: " + sw.Elapsed);
var allBuilds = ctx.Builds;
var ruleResult = GetRulesResult(sw, allConfigurations, allBuilds); // Clean Code!!!
// find owners and insert...
}
}
private static IEnumerable<Notification> GetRulesResult(Stopwatch sw, IEnumerable<Configuration> allConfigurations, ICollection<Configuration> allBuilds)
{
// Lets take only confs that have been updated within last 7 days
var ruleResult = allConfigurations
.AsParallel() // Check if you really need this right here...
.Where(IsConfigElegible) // Clean Code!!!
.SelectMany(x => CreateNotifications(sw, allBuilds, x))
.ToArray();
Debug.WriteLine("Finished loop: " + sw.Elapsed);
return ruleResult;
}
private static bool IsConfigElegible(Configuration x)
{
return x.artifact_cleanup_type != null &&
x.build_cleanup_type != null &&
x.updated_date > DateTime.UtcNow.AddDays(-7);
}
private static IEnumerable<Notification> CreateNotifications(Stopwatch sw, IEnumerable<Configuration> allBuilds, Configuration configuration)
{
// all builds for current configuration
var configurationBuilds = allBuilds
.Where(x => x.configuration_id == configuration.configuration_id);
// .OrderByDescending(z => z.build_date); <<< You should order only when needed (most at the end)
Debug.WriteLine("Filter conf builds: " + sw.Elapsed);
configurationBuilds = BuildCleanup(configuration, configurationBuilds); // Clean Code!!!
configurationBuilds = ArtifactCleanup(configuration, configurationBuilds); // Clean Code!!!
Debug.WriteLine("Done cleanup: " + sw.Elapsed);
const int maxDiscAllocationPerConfiguration = 1000000000; // 1GB
// Sum all disc usage per configuration
var confDiscSizePerConfiguration = configurationBuilds
.OrderByDescending(z => z.build_date) // I think that you can put this even later (or not to have anyway)
.GroupBy(c => c.configuration_id) // No need to create a new object, just use the property
.Where(c => (c.Sum(z => z.artifact_dir_size) > maxDiscAllocationPerConfiguration))
.Select(CreateSumPerConfiguration);
Debug.WriteLine("Done db query: " + sw.Elapsed);
// Extracting to variable to be able to return it as function result
var notifications = confDiscSizePerConfiguration
.Select(CreateNotification);
return notifications;
}
private static IEnumerable<Configuration> BuildCleanup(Configuration configuration, IEnumerable<Configuration> builds)
{
// Since I don't know which builds/artifacts have been cleaned up, calculate it manually
if (configuration.build_cleanup_count == null) return builds;
const int buildCleanupCount = 30; // Why 'string' if you always need as integer?
builds = GetDiscartBelow(configuration, buildCleanupCount, builds); // Clean Code (almost)
builds = GetDiscartAbove(configuration, buildCleanupCount, builds); // Clean Code (almost)
return builds;
}
private static IEnumerable<Configuration> ArtifactCleanup(Configuration configuration, IEnumerable<Configuration> configurationBuilds)
{
if (configuration.artifact_cleanup_count != null)
{
// skipped, similar to previous block
}
return configurationBuilds;
}
private static SumPerConfiguration CreateSumPerConfiguration(IGrouping<object, Configuration> groupedBuilds)
{
var configuration = groupedBuilds.First();
return new SumPerConfiguration
{
configurationId = configuration.configuration_id,
configurationPath = configuration.configuration_path,
Total = groupedBuilds.Sum(c => c.artifact_dir_size),
Average = groupedBuilds.Average(c => c.artifact_dir_size)
};
}
private static IEnumerable<Configuration> GetDiscartBelow(Configuration configuration,
int buildCleanupCount,
IEnumerable<Configuration> configurationBuilds)
{
if (!configuration.build_cleanup_type.Equals("ReserveBuildsByDays"))
return configurationBuilds;
var buildLastCleanupDate = DateTime.UtcNow.AddDays(-buildCleanupCount);
var result = configurationBuilds
.Where(x => x.build_date > buildLastCleanupDate);
return result;
}
private static IEnumerable<Configuration> GetDiscartAbove(Configuration configuration,
int buildLastCleanupCount,
IEnumerable<Configuration> configurationBuilds)
{
if (!configuration.build_cleanup_type.Equals("ReserveBuildsByCount"))
return configurationBuilds;
var result = configurationBuilds
.Take(buildLastCleanupCount);
return result;
}
private static Notification CreateNotification(SumPerConfiguration iter)
{
return new Notification
{
ConfigurationId = iter.configurationId,
CreatedDate = DateTime.UtcNow,
RuleType = (int)RulesEnum.TooMuchDisc,
ConfigrationPath = iter.configurationPath
};
}
}
internal class SumPerConfiguration {
public object configurationId { get; set; } //
public object configurationPath { get; set; } // I did use 'object' cause I don't know your type data
public int Total { get; set; }
public double Average { get; set; }
}

Select single object from a query

I have the following query that pulls the data I need perfectly fine.
var subFuncName = from a in m_dcSQL_ConnectionProdTest.DC3_SubFunctions
where a.VersionIndex == versionIndex && stepDistinct.Select(b => b.Step).Contains(a.FunctionNumber) && stepDistinct.Select(c => c.LogID).Contains(a.SubFunctionNumber)
select new
{
a.FunctionNumber,
a.SubFunctionNumber,
a.SubFunctionName,
};
Then I want to add some data to a list.
foreach (var item in stepDistinct)
{
lstPareto.Add(new clsPareto(Convert.ToInt32(item.Step), Convert.ToInt32(item.LogID),
stepLogID.Where(p => p.Step.Equals(item.Step) && p.LogID.Equals(item.LogID)).Count(),
subFuncName.Where(x => x.FunctionNumber.Equals(item.Step) && x.SubFunctionNumber.Equals(item.LogID)).Select(x => x.SubFunctionName).ToString())); --THIS LINE IS THE PROBLEM--
}
My clsPareto class:
public class clsPareto
{
public int intStep { get; set; }
public int intLogID { get; set; }
public int iCount { get; set; }
public string strFuncName { get; set; }
public clsPareto(int ParetoStep, int ParetoLogID, int Count, string FuncName)
{
intStep = ParetoStep;
intLogID = ParetoLogID;
iCount = Count;
strFuncName = FuncName;
}
}
What I am trying to do, is to pull each SubFunctionName from subFuncName where FunctionNumber = Step and SubFunctionNumber = LogID. However, when I bind it to my datagrid, the column meant to show the names just shows the SQL Query String instead and doesn't actually take the elements I want. I thought my .Select(x => x.SubFunctionName) would do the trick, but apparently it doesn't. Still pretty new to using LINQ and C#, so how would I go about doing this?
The linq for the problem line is still an expression - Select() returns an IEnumerable not the value - and then you are doing a ToString() on it. This is why you just get SQL back.
You need to resolve the expression and get an actual object out of it. Adding Single() to get the FuncName should do it. You may also not need to convert to string if FuncName already is:
subFuncName.Where(x => x.FunctionNumber.Equals(item.Step) && x.SubFunctionNumber.Equals(item.LogID))
.Select(x => x.SubFunctionName).Single().ToString()));
This solution will throw an exception if there is no matching element in the collection.
subFuncName.Where(x => x.FunctionNumber.Equals(item.Step) && x.SubFunctionNumber.Equals(item.LogID))
.Select(x => x.SubFunctionName).Single().ToString()));
Could use;
var subFuncName = subFuncName.Where(x => x.FunctionNumber.Equals(item.Step) && x.SubFunctionNumber.Equals(item.LogID))
.Select(x => x.SubFunctionName).FirstOrDefault());
if(subFuncName != null)
// Add it
else
subFuncName == "UNDEFINED";
and handle the case if subFuncName is null yourself.

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