Is there an equivalent to "NotMapped" for Dapper.Net and the Dapper.Net extensions? - dapper

I've started to play with Dapper.Net, and am really loving it so far - however, I have run into one problem.
Say that I have a POCO class like:
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string FullName { get { return FirstName + " " + LastName; } }
}
Now, using Dapper.Net and the Dapper.Net extensions, I want to simply load all instances of that data type from the DB by doing this:
string connectionString = CloudConfigurationManager.GetSetting("DBConnection");
using (SqlConnection cn = new SqlConnection(connectionString))
{
cn.Open();
IEnumerable<Types.Person> entities = cn.GetList<Types.Person>();
var forceMe = entities.ToList();
}
This works fine in the Linq setup, but when it hits the line with the .ToList(), which forces the evaluation, it blows up with "invalid column names" on FullName. Thinking that it might respect the Entity Framework DataAnnotations stuff for NotMapped, I tried adding a NotMapped attribute (after adding EF 5 to the project). This didn't work.
So, the question is, how do I tell Dapper.Net that a column isn't to be expected from the DB? Is this a problem with the extensions, trying to map a DB column for everything it sees in the model POCO? Do I need to revert to writing SQL, and explicitly ask for the columns that I want only, or is there a way to get an equivalent to NotMapped on the column?

I think the only way to to ignore certain properties from being mapped is to implement an auto class mapper, where you can specify your custom field mappings. For example:
public class CustomMapper : ClassMapper<Foo>
{
public CustomMapper()
{
Table("FooTable");
Map(f => f.Id).Column("FooId").Key(KeyType.Identity);
Map(f => f.DateOfBirth).Column("BirthDate");
Map(f => f.FirstName).Column("First");
Map(f => f.LastName).Column("Last");
Map(f => f.FullName).Ignore();
Map(f => f.Calculated).ReadOnly();
}
}
public class Foo
{
public int Id { get; set; }
public DateTime DateOfBirth { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string FullName
{
get { return string.Format("{0} {1}", FirstName, LastName); }
}
}
In the above example, FullName is being ignored.
The auto-mapper also allows you to adjust table names, in case your POCO class names do not match table names.
Also, keep in mind that you must keep your custom maps in the same assembly as your POCO classes. The library uses reflection to find custom maps and it only scans one assembly.
Hope this helps,
Good luck

Related

EF Improve performance of Many-to-Many / .Include .ThenInclude

I've got a relatively basic model - Users and Tags. There is a fixed list of Tags. A User can have multiple Tags and a Tag can be used by multiple users.
I had gone with structure below and finding performance issues when returning results.
public class User
{
public string Id {get; set;}
public virtual List<UserTag> UserTags {get; set}
}
public class UserTag
{
public string UserId { get; set; }
public User User { get; set; }
public int TagId { get; set; }
public Tag Tag{ get; set; }
}
public class Tag
{
[Key]
public int TagId { get; set; }
public string Name { get; set; }
public virtual List<UserTag> UserTags { get; set; }
}
I have the following query which is takings a long time (several seconds):
var x = db.Users.Include(u => u.UserTags).ThenInclude(u => u.Trait).ToList<User>();
I have tried writing it as such, which has improved the time, however it is still taking too long:
db.UserTags.Load();
db.Tags.Load();
var x = db.Users.ToList<User>();
Is there any other way to speed this up? Running a query directly in SQL SMS is almost instant (e.g.
select * from Users u left outer join UserTags t on t.UserId = u.Id)
In terms of data rows, it is apx Tags: 100, UserTags:50,000, Users: 5,000
First you can check how EF translates your request to SQL Server - therefore use the "SQL Server Profiler"
Then you could use the genereated query to check if there might be an missing index which speeds up the query
You also can try to write a Join instead of ThenInclude and see how the query then behaves
best regards
Jimmy

Date type for a certain property with Pure Code First Hot Chocolate GraphQL

I am using pure code first with Hot Chocolate but I have dateTime C# type and I am having problems with the timezone getting converted incorrectly in javascript. So I would like it to output a Date instead of a DateTime object. For now I am using
SchemaBuilder.New()
.BindClrType<DateTime, DateType>()
but this is a very brute force approach as now I can never output a DateTime type in graphQL.
Is there way to put an attribute on the property or set it up somewhere so that a specific property on a class is output as a Date instead of DateTime?
You can also use Pure Code First approach by attributing target field with GraphQLType attribute:
using HotChocolate;
public class User
{
public int Id { get; set; }
[GraphQLType(typeof(NonNullType<DateType>))]
public DateTime BirthDate { get; set; }
}
Yes, there's a pretty straightforward way of reaching that. Suppose you have some User class:
public class User
{
public int Id { get; set; }
public DateTime BirthDate { get; set; }
}
To be able to specify that BirthDate is just a date but not datetime define the User "type metadata" and specify the GraphQL type of BirthDate as "DateType":
public class UserType : ObjectType<User>
{
protected override void Configure(IObjectTypeDescriptor<User> descriptor)
{
descriptor.Field(t => t.BirthDate).Type<DateType>();
}
}
and register that type on schema building:
public void ConfigureServices(IServiceCollection services)
{
services.AddGraphQL(sp =>
SchemaBuilder.New()
.AddQueryType<Query>()
.AddType<UserType>()
.Create());
}

Set options values to an entity property

I come from Django, and with Django's ORM when defining a model I can set options, for example:
class MyModel(Model):
CHOICES = (...) # List of choices here
# it has tu be a 2-tuple list
# but that's is not the point right now.
choice = IntegerField(choices=CHOICES) # Set the choices.
Now, when MyModel is used to create an instance, the attribute(property) choice can hold only values defined in CHOICES.
There is some like this for EntityFrameworkCore-2.0?
It is something like this possible?
class MyEntity
{
[choices=EnumDefiningChoices]
property enum choices { get; set; }
}
A property can have a single defined value, an int, string, etc in order to be stored in a database.
An approach is to use 2 tables, one for your entity and one for the choices.
MyEntity:
public class MyEntity
{
public int Id { get; set; }
// all the other properties
public int ChoiceId { get; set; } // Here is the refference for the choice
public virtual Choice Choice { get; set; }
}
Choice:
public class Choice
{
public int Id { get; set; }
public string Name { get; set; }
}
As you can see the ChoiceId will allow only values from the Choice table.
However if you want to use an Enum there is no "blocking" mechanism using EF, this must be implemented at the application level.
Indeed, there is a way!
With EF > 6 you can do something like this:
public enum Option
{
option_1 = 1,
option_2,
option_3
}
class SomeEntity
{
public Option Option { get; set; }
}
And you can get the enum option name with:
var option_name = Option.GetName(typeof(Option), some_entity.option_2);
Console.WriteLine(option_name);
References
Working with Enumerated Values in Entity Framework
Enum.GetName Method (Type, Object)

Azure Search - Query

So I'm using the C# nuget wrapper around Azure Search. My problem is I have a index of products:
public class ProductDocument
{
[System.ComponentModel.DataAnnotations.Key]
public string Key { get; set; }
[IsSearchable]
public string Sku { get; set; }
[IsSearchable]
public string Name { get; set; }
[IsSearchable]
public string FullDescription { get; set; }
[IsSearchable]
public List<CustomerSkuDocument> CustomerSkus { get; set; }
}
public class CustomerSkuDocument
{
[IsSearchable]
public int AccountId { get; set; }
[IsSearchable]
public string Sku { get; set; }
}
Example data would be:
new Product() { Key= 100,Name="Nail 101",Sku = "CCCCCCCC", CustomerSkus = new List<ProductCustomerSku>()
{
new ProductCustomerSku() {AccountId = 222, CustomerSku = "BBBB"},
new ProductCustomerSku() {AccountId = 333, CustomerSku = "EEEEEEE"}
}
So the problem is around CustomerSkuDocument.
When I Search I need to pass the AccountId in as well as the search term, however the AccountId is only used for when searching the ProductCustomerSkus.
Basically an Account can have different customer skus but it's only associated to that account - I don't want a separate index per account.
So my call would be something like /AccountId=222&term=BBBB which would find the match.
However /AccountId=333&term=BBBB would not find a match.
So I'm calling it like:
SearchParameters sp = new SearchParameters();
sp.SearchMode = SearchMode.Any;
sp.QueryType = QueryType.Full;
DocumentSearchResult<ProductDocument> results =
productIndexClient.Documents.Search<ProductDocument>(term, sp);
Where term is the normal search term, tried it with adding the AccountId but it doesn't work.
Azure Search does not support repeating data structures nested under a property of the outer document. We're working on this (see https://feedback.azure.com/forums/263029-azure-search/suggestions/6670910-modelling-complex-types-in-indexes), but we still have some work to do before we can release that.
Given that, the example you're showing is not probably indexing the nested parts. Can you post the search index definition you're using? While we work in direct support for complex types, you can see your options for approach here: https://learn.microsoft.com/en-us/azure/search/search-howto-complex-data-types
From the above you'll arribe at a index structure that will also guide your query options. If all you need is equality, perhaps you can simply include the accountId and the SKU in the same field and use a collection field so you can have multiple instances. For your query you would issue a search query that requires the accountId and has the rest as optional keywords.

Value is not a convertible object

I have a simple query and Poco that I'm using with Dapper like so:
var jc = this.dbConnection.ExecuteScalar<JcUser>("SELECT loginid as Username,Password,coalesce(CustomerId,0) as CustomerId,TextProfileId,UxProfileId from \"user\" where id = #id", new {id = id});
Poco:
public class JcUser
{
public string UserName { get; set; }
public string Password { get; set; }
public int CustomerId{ get; set; }
public int TextProfileId { get; set; }
public int UxProfileId { get; set; }
}
When this executes it throws an exception with the message
Value is not a convertible object: System.String to JcUser
The stack trace ends up at: at System.Convert.ToType (System.Object value, System.Type conversionType, IFormatProvider provider, Boolean try_target_to_type)
Any ideas why its doing this?
Thanks
UPDATE: Using var jc = this.dbConnection.Query<JcUser>("SELECT loginid as Username,Password,coalesce(CustomerId,0) as CustomerId,TextProfileId,UxProfileId from \"user\" where id = #id", new {id = id}).First(); appears to work. I also realise I'm a moron and ExecuteScalar is only for one value. However, is my update the best way to retrieve only one row?
ExecuteScalar maps to the ADO.NET method of the same name. It returns at most one cell: one grid, one row, one column. As such, it is not intended for use with complex objects, and cannot work correctly in your case as you have multiple columns.
Dapper assumes you would only use that with simple types like int, string etc.
In your case, use:
var jc = this.dbConnection.Query<JcUser>(
sql, args).SingleOrDefault();
If you want to avoid a hidden List<> allocation you could also pass buffered: false.

Resources