How can I cast to subclass when using LINQ and TPT EF? - entity-framework

I have the following class hierarchy:
public class RealPeople { }
public class Users : RealPeople { }
public class People : RealPeople { }
In my dbContext, I defined a dbSet for RealPeople and on the OnModelCreating procedure, I specified separated tables for People and Users:
modelBuilder.Entity<Users>().ToTable("Users");
modelBuilder.Entity<People>().ToTable("People");
This creates the corresponding full hierarchy in my DB, with the 3 corresponding tables.
The problem comes when I want to retrieve the list of Users in my DB.
This:
List = (from Reg in PersistentMgr.RealPeople select (Users)Reg)
.ToList();
or this:
List = (from Reg in PersistentMgr.RealPeople select (Users)((RealPeople)Reg))
.ToList();
Throws an exception:
LINQ only being able to cast primitive model types.
So the thing is, I can't cast RealPeople to the corresponding subclass Users.
Any ideas on this one?

The way to get a collection of subclasses is using OfType:
var users = (from p in PersistentMgr.RealPeople select p).OfType<User>();

Try this instead:
var list = PersistentMgr.RealPeople.Select(reg => reg as Users).ToList();
better:
var list = PersistentMgr.RealPeople.Select(reg => (reg is Users) ? reg as Users : null).ToList();
You will get the same error if you try this:
var realperson = new RealPeople();
var user = (Users) realperson;
The reason is because the compiler doesn't know how to convert complex types into their subtypes by simple casting - so you need to use the as keyword instead. This will either return null, or the supertype casted into the subtype.
var realperson = new RealPeople();
var user = realperson as Users; // user is realperson converted into a Users object
var aString = "this is a string";
var otheruser = aString as Users; // otheruser is null, because aString was not a valid supertype for Users

Related

Filehelpers and Entity Framework

I'm using Filehelpers to parse a very wide, fixed format file and want to be able to take the resulting object and load it into a DB using EF. I'm getting a missing key error when I try to load the object into the DB and when I try and add an Id I get a Filehelpers error. So it seems like either fix breaks the other. I know I can map a Filehelpers object to a POCO object and load that but I'm dealing with dozens (sometimes hundreds of columns) so I would rather not have to go through that hassle.
I'm also open to other suggestions for parsing a fixed width file and loading the results into a DB. One option of course is to use an ETL tool but I'd rather do this in code.
Thanks!
This is the FileHelpers class:
public class AccountBalanceDetail
{
[FieldHidden]
public int Id; // Added to try and get EF to work
[FieldFixedLength(1)]
public string RecordNuber;
[FieldFixedLength(3)]
public string Branch;
// Additional fields below
}
And this is the method that's processing the file:
public static bool ProcessFile()
{
var dir = Properties.Settings.Default.DataDirectory;
var engine = new MultiRecordEngine(typeof(AccountBalanceHeader), typeof(AccountBalanceDetail), typeof(AccountBalanceTrailer));
engine.RecordSelector = new RecordTypeSelector(CustomSelector);
var fileName = dir + "\\MOCK_ACCTBAL_L1500.txt";
var res = engine.ReadFile(fileName);
foreach (var rec in res)
{
var type = rec.GetType();
if (type.Name == "AccountBalanceHeader") continue;
if (type.Name == "AccountBalanceTrailer") continue;
var data = rec as AccountBalanceDetail; // Throws an error if AccountBalanceDetail.Id has a getter and setter
using (var ctx = new ApplicationDbContext())
{
// Throws an error if there is no valid Id on AccountBalanceDetail
// EntityType 'AccountBalanceDetail' has no key defined. Define the key for this EntityType.
ctx.AccountBalanceDetails.Add(data);
ctx.SaveChanges();
}
//Console.WriteLine(rec.ToString());
}
return true;
}
Entity Framework needs the key to be a property, not a field, so you could try declaring it instead as:
public int Id {get; set;}
I suspect FileHelpers might well be confused by the autogenerated backing field, so you might need to do it long form in order to be able to mark the backing field with the [FieldHidden] attribute, i.e.,
[FieldHidden]
private int _Id;
public int Id
{
get { return _Id; }
set { _Id = value; }
}
However, you are trying to use the same class for two unrelated purposes and this is generally bad design. On the one hand AccountBalanceDetail is the spec for the import format. On the other you are also trying to use it to describe the Entity. Instead you should create separate classes and map between the two with a LINQ function or a library like AutoMapper.

Linq to select top 1 related entity

How can I include a related entity, but only select the top 1?
public EntityFramework.Member Get(string userName)
{
var query = from member in context.Members
.Include(member => member.Renewals)
where member.UserName == userName
select member;
return query.SingleOrDefault();
}
According to MSDN:
"Note that it is not currently possible to filter which related entities are loaded. Include will always bring in all related entities."
http://msdn.microsoft.com/en-us/data/jj574232
There is also a uservoice item for this functionality:
http://data.uservoice.com/forums/72025-entity-framework-feature-suggestions/suggestions/1015345-allow-filtering-for-include-extension-method
The approach to use an anonymous object works, even though it's not clean as you wish it would be:
public Member GetMember(string username)
{
var result = (from m in db.Members
where m.Username == username
select new
{
Member = m,
FirstRenewal = m.Renewals.FirstOrDefault()
}).AsEnumerable().Select(r => r.Member).FirstOrDefault();
return result;
}
The FirstRenewal property is used just to make EF6 load the first renewal into the Member object. As a result the Member returned from the GetMember() method contains only the first renewal.
This code generates a single Query to the DB, so maybe it's good enough for You.

How to Check If Entity Exists Before Attaching?

When a new Post is input into the system, a number of Tags will need to be instantiated and associated with the Post instance. Some of these Tags will already exist in the db, others will not and will need inserting.
An example:
var post = new Post {
Slug = "hello-world",
Title = "Hello, World!",
Content = "this is my first post.",
Tags = new List<Tag>()
};
var tag = new Tag { Name = "introduction" };
post.Tags.Add(tag);
When an associated Tag does not exist in the db, I can rely on a simple call to DbSet<T>.Add to insert both the post and the associated tags into the db.
However, attempting to insert a post with associated tags that already exist in the db causes a primary key violation on the tags table.
In an attempt to solve this problem, I tried to Attach each tag to the database context which works super when the tags already exist in the database but otherwise, an exception is thrown with the following inner exception:
The INSERT statement conflicted with the FOREIGN KEY constraint "FK_dbo.TagPosts_dbo.Tags_Tag_Name". The conflict occurred in
database "EF.Domain.BlogDb", table "dbo.Tags", column
'Name'. The statement has been terminated."}
I want to insert tags associated with the post into the database only when it is required. How can I achieve this?
Your predicament is quite interesting. If you wouldn't use a generic repository but a specific repository that knows the primary key of the entity you want to attach is you could just use a code like this:
var tagExists = Tags.Any(t => t.Name == tag.Name);
or
var tag = Tags.Find(tag.Name);
however in your case we need a more general approach such as getting the primary key of the entity the Repository class uses regardless the type of the entity. To achieve this I've created two extension methods on the DbContext class:
public static IList<string> GetPrimaryKeyNames<TEntity>(this DbContext context)
where TEntity : class
{
var objectContext = ((IObjectContextAdapter)context).ObjectContext;
var set = objectContext.CreateObjectSet<TEntity>();
return set.EntitySet.ElementType
.KeyMembers
.Select(k => k.Name)
.ToList();
}
public static IList<object> GetPrimaryKeyValues<TEntity>(this DbContext context, TEntity entity)
where TEntity : class
{
var valueList = new List<object>();
var primaryKeyNames = context.GetPrimaryKeyNames<TEntity>();
foreach(var propertyInfo in entity.GetType().GetProperties())
{
if (primaryKeyNames.Contains(propertyInfo.Name))
{
valueList.Add(propertyInfo.GetValue(entity));
}
}
return valueList;
}
Utilizing these methods you can change the Attach method on the Repository class like the following:
public void Attach(TEntity entity)
{
var storeEntity = _context.Set<TEntity>().Find(
_context.GetPrimaryKeyValues(entity).ToArray());
if (storeEntity != null)
{
_context.Entry(storeEntity).State = EntityState.Detached;
_context.Set<TEntity>().Attach(entity);
}
}

Can I use CLIMutable records with EntityFramework in an update scenario?

I am trying to use F# record types with the CLIMutable attribute as entities with Entity Framework. Selecting entities, and adding new entities works fine, but there is a problem with updating entities. Apparently I cannot use the assignment operator (<-) to assign a value to the field of a mutable record type.
Is there any way to make an assignment to the field of an existing record?
[<CLIMutable>]
[<Table("UserProfile")>]
type UserProfile = {
[<Key>]
Id : int
Username : string }
type Context() =
inherit DbContext(connectionString)
[<DefaultValue>]
val mutable private _users : DbSet<UserProfile>
member this.Users
with get() = this._users and set(v) = this._users <- v
let updateUsername id username =
use context = new Context()
let user = context.Users.Find(id : int)
user.Username <- username //This line does not compile
context.SaveChanges()
From the documentation:
type Car = {
Make : string
Model : string
mutable Odometer : int
}
let myCar = { Make = "Fabrikam"; Model = "Coupe"; Odometer = 108112 }
myCar.Odometer <- myCar.Odometer + 21
The CLIMutable attribute is there so that frameworks etc can populate records. If you need to modify the record yourself, you simply need to inform the F# compiler that you want to be able to update that field.

Execution of non-generic DbContext.Set?

I need to execute a dynamic set type, here's what I'm trying to do (pseudo):
var type = GetSetType(); //System.Type
var set = context.Set(type);
var results = set.ToArray();
I know this can't work for sure, Enumerable ex. methods are only for generic IEnumerables, but I tried set.AsQueryable().Cast<object>().ToArray(), and a NotSupportedException was thrown: "LINQ to Entities only supports casting EDM primitive or enumeration types.".
Any way to execute a non-generic DbSet?
I should have deleted my question since the answer is too simple, but for the record, let's just keep it up.
namespace System.Linq
{
public static class EntityFrameworkExtensions
{
public static IEnumerable<object> AsEnumerable(this DbSet set)
{
foreach (var entity in set)
yield return entity;
}
}
}
Usage:
var collection = context.Set(entityType).AsEnumerable();
Beware that any filtering performed on the retuned collection will not happen on the SQL server, but on the collection enumerator, the entire table will be returned. Use only when loading all rows anyway.