just wondering how I can get rid of this warning:
Microsoft.EntityFrameworkCore.Model.Validation[10620]
The property 'FI' on entity type 'Routes' is a collection or enumeration type with a value converter but with no
value comparer. Set a value comparer to ensure the
collection/enumeration elements are compared correctly.
Code:
modelBuilder.Entity<Routes>().Property(l => l.FI).HasConversion(
FI=> String.Join(",", FI),
dbVal => dbVal.Split(',', StringSplitOptions.RemoveEmptyEntries).ToList()
);
where FI is defined in the model as:
public List<string> FI { get; set; }
I understand I have to do a SetValueComparer call somehow but I am not sure how. The examples on line are a bit different and don't have the String.Join and dbVal lines.
Thank you
From the Microsoft docs - this is a example of how to set a value comparer (inside of HasConversion()):
modelBuilder
.Entity<EntityType>()
.Property(e => e.MyListProperty)
.HasConversion(
v => JsonSerializer.Serialize(v, (JsonSerializerOptions)null),
v => JsonSerializer.Deserialize<List<int>>(v, (JsonSerializerOptions)null),
new ValueComparer<List<int>>(
(c1, c2) => c1.SequenceEqual(c2),
c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())),
c => c.ToList()
)
);
You need to choose the appropriate value comparison, of course (List<string> instead of List<int>, for example).
UPDATE: adapted to your code snippet, it should look like this (don't know, if it works, though):
modelBuilder
.Entity<Routes>()
.Property(l => l.FI)
.HasConversion(
fi => String.Join(",", fi),
dbVal => dbVal.Split(',', StringSplitOptions.RemoveEmptyEntries).ToList(),
new ValueComparer<List<string>>(
(c1, c2) => c1.SequenceEqual(c2),
c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())),
c => c.ToList()
)
);
Related
The code below is part of the search functionality code that grabs the query string from URL and searches the database.
Everything works great except for one thing which is BatchID. Which throws the below error message. The BatchIDis DataType Int in the database. Due to this reason it's causing an error. I tried casting as (string) however no luck
Error Message
Severity Code Description Project File Line Error CS1061 'int' does
not contain a definition for 'Contains' and no extension method
'Contains' accepting a first argument of type 'int' could be found
(are you missing a using directive or an assembly
reference?) CardDistro E:\wwwroot\CardDistro \CardDistro
\Controllers\CardsController.cs 32
QueriedTransactionList = db.Transactions
.Where(x => x.Card.InCome.InComePayerName.Contains(QueryString) ||
x.Card.InCome.InComePayerEmail.Contains(QueryString) ||
x.Card.InCome.InComePayerTIN.Contains(QueryString) ||
x.BatchID.Contains( (string) QueryString) //Error over here ||
x.Card.Port.PortName.Contains(QueryString)
)
.OrderByDescending(x => x.TTransactionID)
.GroupBy(x => x.BatchID)
.Select(x => x.FirstOrDefault())
.ToList();
If BatchID property is int type , you cannot use Contains as it works on an array. You may check the value directly using == operator.
Since you are using the same string parameter(QueryString) to get the id value as well, you should use the TryParse method to safely parse the string to an int variable value (if it is a valid string representation of an int value) and use that. If your BatchID value is always greater than 0, you can initialize your local variable to 0, and if the querystring has a valid numeric value, parse it and update the local variable and use that in your LINQ expression.
int id = 0;
if (Int32.TryParse(QueryString, out id))
{
}
var results = db.Transactions
.Where(x => x.Card.InCome.InComePayerName.Contains(QueryString) ||
x.Card.InCome.InComePayerEmail.Contains(QueryString) ||
x.Card.InCome.InComePayerTIN.Contains(QueryString) ||
x.Card.Port.PortName.Contains(QueryString) ||
x.BatchID == id
)
.OrderByDescending(x => x.TTransactionID)
.GroupBy(x => x.BatchID)
.Select(x => x.FirstOrDefault())
.ToList();
NOTE : This is NOT a duplicate of this useful SO question, my problem is all about the TruncateTime inside the GroupBy clause. See explanations below :
I'd like to use DbFunctions.TruncateTime in a multiple GroupBy clause, but it doesn't seem to work in my ASP.NET MVC5 project.
Here is a first lambda I wrote, it gives me the total number of views per day for a set of data.
It gives me the expected result :
var qViews = dbContext.TABLE_C.Where(c => c.IdUser == 1234)
.Join(dbContext.TABLE_V.Where(v => v.Date > DbFunctions.AddMonths(DateTime.Now, -1)), c => c.Id, v => v.Id, (c, v) => new { c, v })
.GroupBy(x => DbFunctions.TruncateTime(x.v.MyDateTimeColumn))
.Select(g => new
{
Date = (DateTime)g.Key,
NbViews = g.Count(),
}).ToDictionary(p => p.Date, p => p.NbViews);
Result is something like that :
...
Date | Views
03/07/2018 | 15
03/08/2018 | 8
03/09/2018 | 23
Now, I'd like a more detailled result, with the number of views per day AND PER ITEM on the same set of data.
Here is what I'd like to write :
var qViews = dbContext.TABLE_C.Where(c => c.IdUser == 1234)
.Join(dbContext.TABLE_V.Where(v => v.Date > DbFunctions.AddMonths(DateTime.Now, -1)), c => c.Id, v => v.Id, (c, v) => new { c, v })
.GroupBy(x => new { DbFunctions.TruncateTime(x.v.MyDateTimeColumn), x.c.Id}) // Issue #1
.Select(g => new
{
Date = g.Key.Date, //Issue #2
NbViews = g.Count(),
}).ToDictionary(p => p.Date, p => p.NbViews);
And I expected something like that :
...
Date | Views | ID Item
03/07/2018 | 4 | 456789
03/07/2018 | 11 | 845674
03/08/2018 | 6 | 325987
03/08/2018 | 1 | 548965
03/08/2018 | 1 | 222695
03/09/2018 | 23 | 157896
So, this request have two issues (see comments above)
Issue #1 : It seems I can't GroupBy multiple columns, which one of them use DbFunctions. If I use .GroupBy(x => new { x.v.MyDateTimeColumn, x.c.Id }), code compiles, but doesn't give me the expected result, as I want to group by date, not date + time
Issue #2 : Date = g.Key.Date, seems wrong for the compiler. When I wrote g.Key, autocompletion only suggests me the Id column, but it doesn't see the truncated date.
Why can't I GroupBy multiple columns, with one of them is a truncated Date ?
Is there any workaround ?
You need to give your anonymous type's properties names if you want to use them later on:
.GroupBy(x => new
{ Date = DbFunctions.TruncateTime(x.v.MyDateTimeColumn),
Id = x.c.Id
})
Then you can project on that:
.Select(g => new
{
Date = g.Date,
NbViews = g.Count(),
})
And finally you cannot do this:
.ToDictionary(p => p.Date, p => p.NbViews);
because you will get this error:
An item with the same key has already been added.
Why? Because the Date is not unique since you just grouped by Date and Id so Date(s) will be duplicated. It is the same as this but this is a list of string:
var nums = new List<string> { "1", "1", "1", "2" };
nums.ToDictionary(x => x, x => x);
But, perhaps, you may want to do this:
var lu = nums.ToLookup(x => x, x => x);
And now you can look them up:
// Returns 3 items since there are 3 "1"s
IEnumerable<string> ones = lu["1"];
Dart allows for chaining futures to invoke more than one async method in sequence without nesting callbacks, which is awesome.
Let's say we want to first connect to a data store like Redis, and then run a bunch of sequential reads:
Future<String> FirstValue(String indexKey)
{
return RedisClient.connect(Config.connectionStringRedis)
.then((RedisClient redisClient) => redisClient.exists(indexKey))
.then((bool exists) => !exists ? null : redisClient.smembers(indexKey))
.then((Set<String> keys) => redisClient.get(keys.first))
.then((String value) => "result: $value");
}
Four async methods and yet the code is fairly easy to read and understand. It almost looks like the steps are executed synchronously and in sequence. Beautiful! (Imagine having to write the same code using nested JavaScript callbacks...)
Unfortunately, this won't quite work: the RedisClient we get from the .connect method is only assigned to a local variable which is not in scope for the subsequent .thens. So, redisClient.smembers and redisClient.get will actually throw a null pointer exception.
The obvious fix is to save the return value in another variable with function scope:
Future<String> FirstValue(String indexKey)
{
RedisClient redisClient = null;
return RedisClient.connect(Config.connectionStringRedis)
.then((RedisClient theRedisClient)
{
redisClient = theRedisClient;
return redisClient.exists(indexKey);
})
.then((bool exists) => !exists ? null : redisClient.smembers(indexKey))
.then((Set<String> keys) => redisClient.get(keys.first))
.then((String value) => "result: $value");
}
Unfortunately, this makes the code more verbose and less beautiful: there's now an additional helper variable (theRedisClient), and we had to replace one of the Lambda expressions with an anonymous function, adding a pair of curly braces and a return statement and another semicolon.
Since this appears to be a common pattern, is there a more elegant way of doing this? Any way to access those earlier intermediate further down the chain?
You can use a nested assignment to avoid curly braces and return :
.then((RedisClient rc) => (redisClient = rc).exists(indexKey))
You can do scopes with futures too, by not putting all the 'then' calls at the same level.
I'd do something like:
Future<String> FirstValue(String indexKey) =>
RedisClient.connect(Config.connectionStringRedis)
.then((RedisClient redisClient) =>
redisClient.exists(indexKey)
.then((bool exists) => !exists ? null : redisClient.smembers(indexKey))
.then((Set<String> keys) => redisClient.get(keys.first))
.then((String value) => "result: $value");
);
Indentation is always difficult with code like this. This example follows the Dart style guide, but I think it could be more readable with less indentation of the then calls:
Future<String> FirstValue(String indexKey) =>
RedisClient.connect(Config.connectionStringRedis)
.then((RedisClient redisClient) =>
redisClient.exists(indexKey)
.then((bool exists) => !exists ? null : redisClient.smembers(indexKey))
.then((Set<String> keys) => redisClient.get(keys.first))
.then((String value) => "result: $value");
);
I have the following DB structure.
I want to make a select using Entity Framework. I want all my Categories that MAY have a CategoryText. If They have a CategoryText I also need to get the Language.
I searched and I couldn't find anything useful.
Here is my query that doesn't work:
var categoriesSQL = db.Categories
.Include(i => i.CategoryTexts.Select(s => s.Language)
.Where(w => w.Format == (string)Session["chosen_language"]));
var categories = categoriesSQL.ToList();
It throws:
The Include path expression must refer to a navigation property defined on the type. Use dotted paths for reference navigation properties and the Select operator for collection navigation properties.
Parameter name: path
I tried to solve this in a lot of ways and recomandations but I couldn't find a solution.
I want the select made in only one query.
Thank you!
Try this
var language = (string)Session["chosen_language"];
var categoriesSQL = db.Categories
.Include(i => i.CategoryTexts.Select(s => s.Language))
.Where(c =>
(from ct in c.CategoryTexts
from l in ct.Languages
select l.Format).Contains(language)
);
var categories = categoriesSQL.ToList();
OR
var language = (string)Session["chosen_language"];
var categoriesSQL = db.Categories
.Include(i => i.CategoryTexts.Select(s => s.Language))
.Where(c => c.CategoryText
.Any(ct => ct.Languages
.Any(l => l.Format == language)
)
);
var categories = categoriesSQL.ToList();
One of the search functions to our website returns far too many results for one page to handle, so I am trying to add the paging function as provided by here: https://github.com/TroyGoode/PagedList
The solution builds properly and the page will load as well, however when I try to conduct a search a "NotSupportedException" is thrown on the page's controller/Index() method:
The method 'Skip' is only supported for sorted input in LINQ to Entities. The method 'OrderBy' must be called before the method 'Skip'.
Visual Studio 2010 points to the return statement when this exception is thrown. This is only my second day working in ASP MVC so any and all suggestion are welcome. Thank you!
case "name":
//if no comma, do a combined search by last name and by corporate name.
searchString = searchString.ToUpper();
var lastAgents =
db.Agent.OrderBy(s => s.LastName).Where(s => s.LastName.ToUpper().StartsWith(searchString)).Include(
a => a.AgentIdentification).Include(a => a.SymetraNumberToAgentId);
//end new code
var corp2Agents =
db.Agent.OrderBy(s => s.CorporateName).Where(s => s.CorporateName.ToUpper().StartsWith(searchString)).Include(
a => a.AgentIdentification);
if ((corp2Agents.Count() == 0) & (lastAgents.Count() == 0)) ViewBag.ErrorMessage = "None found in search for Last Names and Companies beginning with " + search1;
else ViewBag.Message = "Results of Last Name and Company Name search. Found " + (corp2Agents.Count() + lastAgents.Count()).ToString();
pageNumber = (page ?? 1);
return View(lastAgents.Union(corp2Agents).ToPagedList(pageNumber, pageSize));
Took forever but I found the answer. Both these statements
var lastAgents =
db.Agent.OrderBy(s => s.LastName).Where(s => s.LastName.ToUpper().StartsWith(searchString)).Include(
a => a.AgentIdentification).Include(a => a.SymetraNumberToAgentId);
//end new code
var corp2Agents =
db.Agent.OrderBy(s => s.CorporateName).Where(s => s.CorporateName.ToUpper().StartsWith(searchString)).Include(
a => a.AgentIdentification);
contain an OrderBy, however this is necessary in the Union statement as well. The final "return" statement is as follows:
return View((lastAgents.Union(corp2Agents)).OrderBy(s => s.sNumber).ToPagedList(pageNumber, pageSize));
Try adding the .OrderBy(s => s.sNumber) in the controller like this:
var lastAgents =
db.Agent.Where(s => s.LastName.ToUpper().StartsWith(searchString)).Include(
a => a.AgentIdentification).Include(a => a.SymetraNumberToAgentId).OrderBy(s => s.sNumber);
//end new code
var corp2Agents =
db.Agent.Where(s => s.CorporateName.ToUpper().StartsWith(searchString)).Include(
a => a.AgentIdentification).OrderBy(s => s.CorporateName);