How to query against non-mapped property in Entity Framework? - c#

I am converting a mapped property's type via encapsulation using the process found here (Convert value when mapping). Like #Mykroft said, this prevents you from writing LINQ queries against the DB.
Using the example properties below, How can I tell Linq to Entities to use the IsActiveBool when writing the statement db.Employee.Where(b => b.IsActive); ?
[Column("IsActive")]
protected string IsActiveBool { get; set; }
[System.ComponentModel.DataAnnotations.Schema.NotMapped]
public bool IsActive
{
get { return IsActiveBool == "Y"; }
set { IsActiveBool = value ? "Y" : "N"; }
}

Using your simplistic example of db.Employee.Where(b => b.IsActive); You could write it like this:
db.Employee.ToList().Where(b => b.IsActive);
Keep in mind that this is going to pull back all rows in the Employee table and then filter on IsActive. If you want SQL server to do the filtering for you (which you should because this isn't a good solution for a table of any real size) then you need to write it as:
db.Employee.Where(b => b.IsActiveBool == "Y");

Related

How do I use a boolean on my model that wraps a string value from the database?

Weird question, and there may not be an answer... I inherited an old mysql database where unfortunately many of the "boolean" fields were set up as VARCHAR instead of the more appropriate TINYINT(1)...
I'm now running Entity Framework Core on top of the database. Since I don't know everywhere the boolean values are used, I can't update the database quite yet, but I would like to be able to actually work with the string values as booleans in EF... Is there a way to change the model types so the model wraps up everything neatly as bool but it still treats the fields as strings when I push it to the database?
Essentially, in my code, I want to be able to do this:
object.IsGood = true;
Instead of this: object.IsGood = "TRUE"
And my model would just discreetly handle the value conversion, something like this for writing to the database (and I'd need another converter for reading the boolean values back from the database):
string databaseValue = "";
if (object.IsGood)
{
databaseValue = "TRUE";
}
else
{
databaseValue = "FALSE";
}
Any way I can see of doing it, I'd be actually changing the database when I change the model... I know I could wrap the model itself into another class, but that seems confusing. I'd love to just update the database, but that could be a huge pain to unravel all the possible places that code touches these values... so I was hoping there was an intermediate solution.
I always search for this type of thing under the terms "mapping" or "wrapper" and those were not bringing up anything from the documentation that seemed useful... Christopher's comment got me on the right track and I got what I was searching for.
Apparently "Value Conversions" are what I was looking for:
Value Conversions EF Core
Edit: I've removed the docs example since anyone can look that up. I'm adding my real example below.
Since I wanted to convert back and forth between a string and a boolean, I created this converter as a static value so I could reuse it in a couple of models:
public static class EntityFrameworkCoreValueConverters
{
/// <summary>
/// converts values stored in the database as VARCHAR to a nullable bool
/// handles "TRUE", "FALSE", or DBNULL
/// </summary>
public static ValueConverter<bool?, string> dbVarCharNullableToBoolNullableConverter = new ValueConverter<bool?, string>(
v => v == true ? "TRUE" : v == false ? "FALSE" : null,
v => v.ToUpper() == "TRUE" ? true : false
);
}
Note that there already exists a BoolToStringConverter as part of the Microsoft.EntityFrameworkCore.Storage.ValueConversion namespace, but this doesn't appear to handle null values, which I needed.
Then I can change my model values to bool? instead of string, but leave alone the actual database value types.
The dbVarCharNullableToBoolNullableConverter can then be applied in my OnModelCreating method (or in my case Configure on the model itself which gets applied in the OnModelCreating method:
public void Configure(EntityTypeBuilder<MachineHelpRequests> builder)
{
//... model builder code above
builder.Property(e => e.IsAcknowledged)
.HasColumnName("acknowledged_mhr")
.HasColumnType("varchar(45)")
.HasConversion(EntityFrameworkCoreValueConverters.dbVarCharNullableToBoolNullableConverter);
//... model builder code below
}
This is probably not the best solution but it might get you by until you can refactor the DB.
Add a partial class to the Model and implement the appropriate getters and setters.
partial class Model
{
[NotMapped]
public bool FieldABool
{
get
{
return FieldA == "TRUE";
}
set
{
if (value == true)
{
FieldA = "TRUE";
}
else
{
FieldA = "FALSE";
}
}
}
}

It is possible to query a NotMapped property?

i'm working with EF6 code first, and i used this answer to map a List<stirng> in my entitie.
This is my class
[Key]
public string SubRubro { get; set; }
[Column]
private string SubrubrosAbarcados
{
get
{
return ListaEspecifica == null || !ListaEspecifica.Any() ? null : JsonConvert.SerializeObject(ListaEspecifica);
}
set
{
if (string.IsNullOrWhiteSpace(value))
ListaEspecifica.Clear();
else
ListaEspecifica = JsonConvert.DeserializeObject<List<string>>(value);
}
}
[NotMapped]
public List<string> ListaEspecifica { get; set; } = new List<string>();
It works perfectly to storage my list as Json, but now i need to perform a linq query, and i'm trying this
var c = db.CategoriaAccesorios.Where(c => c.ListaEspecifica.Contains("Buc")).First();
And it's throwing
System.NotSupportedException: The specified type member
'ListaEspecifica' is not supported in LINQ to Entities. Only
initializers, entity members, and entity navigation properties are
supported.
what is logical.
Is there any way to perform a query like this?
The problem here is that LINQ to Entities does not understand how to convert your query to the back-end (SQL) language. Because you're not materializing (i.e. converting to .NET) the results of the query until you filter it, LINQ tries to convert your query to SQL itself. Since it's not sure how to do that, you get a NotSupportedException.
If you materialize the query first (I.e. call a .ToList()) then filter, things will work fine. I suspect this isn't what you want, though. (I.e. db.CategoriaAccesorios.ToList().Where(c => c.ListaEspecifica.Contains("Buc")).First();)
As this answer explains, your issue is the EF to SQL Conversion. Obviously you want some way to workaround it, though.
Because you are JSON serializing, there are actually a couple options here, most particularly using a LIKE:
var c =
(from category
in db.CategoriaAccessorios
where SqlMethods.Like(c.SubrubrosAbarcados, "%\"Buc\"%")
select category).First()
If EF Core, allegedly Microsoft.EntityFrameworkCore.EF.Functions.Like should replace SqlMethods.Like.
If you have SQL Server 2016+, and force the SubrubrosAbarcados to be a JSON type, it should be possible to use a raw query to directly query the JSON column in particular.
If you're curious about said aspect, here's a sample of what it could look like in SQL Server 2016:
CREATE TABLE Test (JsonData NVARCHAR(MAX))
INSERT INTO Test (JsonData) VALUES ('["Test"]'), ('["Something"]')
SELECT * FROM Test CROSS APPLY OPENJSON(JsonData, '$') WITH (Value VARCHAR(100) '$') AS n WHERE n.Value = 'Test'
DROP TABLE Test
I was able to do something like this via CompiledExpression.
using Microsoft.Linq.Translations;
// (...) namespace, class, etc
private static readonly CompiledExpression<MyClass, List<string>> _myExpression = DefaultTranslationOf<MyClass>
.Property(x => x.MyProperty)
.Is(x => new List<string>());
[NotMapped]
public List<string> MyProperty
{
get { return _myExpression.Evaluate(this); }
}
I hope there are better / prettier solutions though ;)

The specified type member is not supported in LINQ to Entities. Using 0 to many relationship for an entity property

I'm essentially trying to create an extension property on an entity named "Report", that can also be used in LINQ expressions.
Each Report entity has a 0 to many relationship with a table called ReportStatus, which will contain a history of all status updates for each Report. New Reports will not yet contain an entry in this table (so I will just return empty string). I want to be able to grab the current status code with ease (most recent entry in ReportStatus) for any given Report, as well as query Reports that match whatever status I'm wanting to filter on. Due to the 0 to many relationship involved, it's getting over my head for a clean solution. If anyone can provide some guidance, it would be much appreciated.
The extension property as it stands right now:
public partial class Report
{
public string CurrentStatus
{
get
{
return
this.ReportStatus.Count == 0 ?
"" :
this.ReportStatus.OrderByDescending(r => r.ReportStatusDate).First()
.StatusCode;
}
}
It gives the "The specified type member is not supported in LINQ to Entities. Only initializers, entity members, and entity navigation properties are supported." on expressions such as _repository.Reports.Where(r => r.CurrentStatus == StatusCodes.Pending).ToList()
As I mentioned in the comment above, you can't actually do that query in LINQ-to-Entities since CurrentStatus is not actually a column in the database. As such, you'll need to do the following as your query to get what you're asking for:
var pendingReports = _repository.Reports.Where(r => r.ReportStatus.Any() &&
r.ReportStatus.OrderByDescending(s =>
s.ReportStatusDate).First().StatusCode == StatusCodes.Pending);
Took a bit from IronMan84 Here's what I have now.
Made an extension class:
public static class QueryExtensions
{
public static IQueryable<Report> GetByStatus(
this IQueryable<Report> query, string statusCode)
{
if (statusCode == "")
{
return query.Where(r => r.ReportStatus.Count == 0); // new reports, no status history
}
else
return query.Where(r => r.ReportStatus.OrderByDescending(s =>
s.ReportStatusDate).FirstOrDefault().StatusCode.StatusCode1 == statusCode);
}
}
And use it like this:
_repository.Reports.GetByStatus(StatusCode.Pending)ToList();
I still can use the CurrentStatus extension property shown in my question when just needing to peek at the status for a lone entity. Thanks!

Genericising one class to handle multiple types

I have a series of about 30 lookup tables in my database schema, all with the same layout (and I would prefer to keep them as separate tables rather than one lookup table), and thus my Linq2SQL context has 30 entities for these lookup tables.
I have a standard class that I would use for CRUD operations on each of these 30 entites, for example:
public class ExampleAttributes : IAttributeList
{
#region IAttributeList Members
public bool AddItem(string Item, int SortOrder)
{
MyDataContext context = ContextHelper.GetContext();
ExampleAttribute a = new ExampleAttribute();
a.Name = Item;
a.SortOrder = SortOrder;
context.ExampleAttributes.InsertOnSubmit(a);
try
{
context.SubmitChanges();
return true;
}
catch
{
return false;
}
}
public bool DeleteItem(int Id)
{
MyDataContext context = ContextHelper.GetContext();
ExampleAttribute a = (from m in context.ExampleAttributes
where m.Id == Id
select m).FirstOrDefault();
if (a == null)
return true;
// Make sure nothing is using it
int Count = (from m in context.Businesses
where m.ExampleAttributeId == a.Id
select m).Count();
if (Count > 0)
return false;
// Delete the item
context.ExampleAttributes.DeleteOnSubmit(a);
try
{
context.SubmitChanges();
return true;
}
catch
{
return false;
}
}
public bool UpdateItem(int Id, string Item, int SortOrder)
{
MyDataContext context = ContextHelper.GetContext();
ExampleAttribute a = (from m in context.ExampleAttributes
where m.Id == Id
select m).FirstOrDefault();
a.Name = Item;
a.SortOrder = SortOrder;
try
{
context.SubmitChanges();
return true;
}
catch
{
return false;
}
}
public String GetItem(int Id)
{
MyDataContext context = ContextHelper.GetContext();
var Attribute = (from a in context.ExampleAttributes
where a.Id == Id
select a).FirstOrDefault();
return Attribute.Name;
}
public Dictionary<int, string> GetItems()
{
Dictionary<int, string> Attributes = new Dictionary<int, string>();
MyDataContext context = ContextHelper.GetContext();
context.ObjectTrackingEnabled = false;
Attributes = (from o in context.ExampleAttributes orderby o.Name select new { o.Id, o.Name }).AsEnumerable().ToDictionary(k => k.Id, v => v.Name);
return Attributes;
}
#endregion
}
I could replicate this class 30 times with very minor changes for each lookup entity, but that seems messy somehow - so can this class be genericised so I can also pass it the type I want, and have it handle internally the type differences in the linq queries? That way, I have one class to make additions to, one class to bug fix et al - seems the way that it should be done.
UPDATE:
Andrews answer below gave me the option that I was really looking at while thinking about the question (passing the type in) but I need more clarification on how to genericise the linq queries. Can anyone clarify this?
Cheers
Moo
There are a couple things you can try.
One is to define an interface that has all the relevant fields that the thirty entity classes share. Then, you would be able to have each entity class implement this interface (let's call it IMyEntity) by doing something like
public partial class EntityNumber1 : IMyEntity
{
}
for each entity (where EntityNumber1 is the name of one of the entity classes). Granted, this is still thirty different definitions, but your CRUD operation class could then operate on IMyEntity instead of having to write a new class each time.
A second way to do this is simply to genericize the CRUD operation class, as you suggest:
public class ExampleAttributes<T> : IAttributeList
{
...
which allows you to use T as the type on which to operate. Granted, this might be easier in combination with the first method, since you would still have to check for the presence of the attributes and cast the entity to the appropriate type or interface.
Edit:
To check for the presence of the appropriate properties on the entity, you might need to use reflection methods. One way to check whether the given type T has a particular property might be to check for
typeof(T).GetProperties().OfType<PropertyInfo>().Count<PropertyInfo>(pi => pi.Name == "MyPropertyName" && pi.GetGetMethod().ReturnType == typeof(TypeIWant)) > 0
Of course, replace TypeIWant with the type you are expecting the property to be, and replace MyPropertyName with the name of the property for which you are checking.
Add a parameter to the constructors which specifies the type. Then you can work with it internally. One class, with perhaps a switch statement in the constructor.
For genericising a LINQ query, the biggest problem is that your DataContext has the collections based on type. There are a few ways this can be circumvented. You could try to access it using reflection, but that will require quite a bit of hacking and would pretty much destroy all efficiency that LINQ to SQL would provide.
The easiest way seems to be to use Dynamic LINQ. I have not used it personally, but it seems like it should support it. You can find more information in this thread: Generic LINQ query predicate?
and on http://aspalliance.com/1569_Dynamic_LINQ_Part_1_Using_the_LINQ_Dynamic_Query_Library.1
Maybe someone else can provide more information about this?
This isn't necessarily an answer to the question, but may be a solution to your problem. Have you considered generating all the classes that you need? T4 is built into Visual Studio, and can generate code for you. The link below describes it fairly broadly, but contains heaps of links for further information.
http://www.hanselman.com/blog/T4TextTemplateTransformationToolkitCodeGenerationBestKeptVisualStudioSecret.aspx
That way, you can define all the methods in one place, and generate the class files for your 30-odd lookup models. One place to make changes etc.
Maybe worth considering, and if not, still worth knowing about.

Db4o StartsWith and ignore case

The following query takes a while to return:
db.Query<Person>(x => x.StartsWith("Chr", StringComparison.CurrentCultureIgnoreCase))
is there a way to get this working correctly? ie faster?
Maybe you ran into a limitation of db4o’s query-optimization. Normally Native Queries and LINQ-Queries are translated into a low level SODA-query. When this optimization fails, db4o instantiates the objects in the database in order to execute the query. As you can imagine this can be quite slow.
The best current solution is to use a SODA directly for this case. For example a class with one property:
public class SimpleObject
{
private string name;
public string Name
{
get { return name; }
set { name = value; }
}
}
The native query like this:
var result = db.Query<SimpleObject>(x => x.Name.StartsWith ("Chr",StringComparison.CurrentCultureIgnoreCase));
Can be represented by this SODA-Query:
IQuery query = db.Query();
query.Constrain(typeof (SimpleObject)); // restrict to a certain class
query.Descend("name").Constrain("Chr").StartsWith(false); // the field 'name' starts with 'chr', case-insensitive
foreach (var s in query.Execute())
{
//
}
I hope future versions of the Query-Optimizer support this case directly.
adding and index on the column you're comparing would probably help.
http://developer.db4o.com/Documentation/Reference/db4o-7.4/net35/tutorial/docs/Indexes.html#outline219

Categories