Here is a simple project based on a Poco class named Task:
class Program
{
static void Main(string[] args)
{
using (MyDbContext ctx = new MyDbContext())
{
// first query
DateTime compareDate = DateTime.Now + TimeSpan.FromDays(3);
var res = ctx.Tasks.Where(t => t.LastUpdate < compareDate).ToList();
// second query
res = ctx.Tasks.Where(t => t.ShouldUpdate).ToList();
}
}
}
public class MyDbContext : DbContext
{
public DbSet<Task> Tasks { get; set; }
}
public class Task
{
public int ID { get; set; }
public DateTime LastUpdate { get; set; }
public bool ShouldUpdate
{
get
{
return LastUpdate < DateTime.Now + TimeSpan.FromDays(3);
}
}
}
What I want to do is to query the context dbset including in the where clause the ShouldUpdate derived property.
The "first query works fine" (I can't write it in a single line but it doesn't matter).
As you know, we get a NotSupportedException on the "second query", with the following message:
The specified type member 'ShouldUpdate' is not supported in LINQ to Entities. Only initializers, entity members, and entity navigation properties are supported.
That's right and I can understand why it happen, but I need to encapsulate the derived information inside the Task object so I can display the property in a grid or use it in every other place, without duplicating the logic behind it.
Is there a smart technique to do this?
NB: What is the technical name of the ShouldUplate property? derived? calculated? computed?
Finally i found THE solution..
You can store the partial queries (Expressions) in static fileds and use them like this:
class Program
{
static void Main(string[] args)
{
using (MyDbContext ctx = new MyDbContext())
{
res = ctx.Tasks.Where(Task.ShouldUpdateExpression).ToList();
}
}
}
public class MyDbContext : DbContext
{
public DbSet<Task> Tasks { get; set; }
}
public class Task
{
public int ID { get; set; }
public DateTime LastUpdate { get; set; }
public bool ShouldUpdate
{
get
{
return ShouldUpdateExpression.Compile()(this);
}
}
public static Expression<Func<Task, bool>> ShouldUpdateExpression
{
get
{
return t => t.LastUpdate < EntityFunctions.AddDays(DateTime.Now, 3);
}
}
}
Repository pattern would provide a better abstraction in this case. You can centralize the logic as follows. Define a new property TaskSource in your context.
public class MyDbContext : DbContext
{
public DbSet<Task> Tasks { get; set; }
public IQueryable<Task> TaskSource
{
get
{
return Tasks.Where(t => t.LastUpdate < EntityFunctions.AddDays(DateTime.Now, 3));
}
}
}
You need to put the ShouldUpdate logic inside the linq-to-enitites query. You can use EntityFunctions.AddDays to help you out like so
res = ctx.Tasks.Where(t => t.LastUpdate < EntityFunctions.AddDays(DateTime.Now, 3)).ToList();
Related
I ran into one interesting thing in EF. If we get child entity using base entity, loading entities takes more time. My model looks like this:
public abstract class BaseDocument
{
public Guid Id { get; set; }
public string Name { get; set; }
}
public abstract class ComplexDocument : BaseDocument
{
public string AuthorName { get; set; }
}
public abstract class SimpleDocument : BaseDocument
{
public int Level { get; set; }
}
public abstract class OfficeDocument : ComplexDocument
{
public string OfficeName { get; set; }
}
public abstract class ClassDocument : SimpleDocument
{
public string HeadName { get; set; }
}
public class WordDocument : OfficeDocument
{
public int PagesCount { get; set; }
}
public class ExcelDocument : OfficeDocument
{
public int SheetsCount { get; set; }
}
public class TextDocument : ClassDocument
{
public int LinesCount { get; set; }
}
I am using the TPT approach. Here is the inheritance tree
Here is my context class:
public class Context : DbContext
{
public Context() : base(#"Server=(localdb)\MSSQLLocalDB;Database=EFSIX;Trusted_Connection=True;")
{
Database.CreateIfNotExists();
}
public DbSet<BaseDocument> BaseDocuments { get; set; }
public DbSet<ComplexDocument> ComplexDocuments { get; set; }
public DbSet<SimpleDocument> SimpleDocuments { get; set; }
public DbSet<OfficeDocument> OfficeDocuments { get; set; }
public DbSet<ClassDocument> ClassDocuments { get; set; }
public DbSet<ExcelDocument> ExcelDocuments { get; set; }
public DbSet<WordDocument> WordDocuments { get; set; }
public DbSet<TextDocument> TextDocuments { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<BaseDocument>().ToTable("BaseDocuments");
modelBuilder.Entity<ComplexDocument>().ToTable("ComplexDocuments");
modelBuilder.Entity<SimpleDocument>().ToTable("SimpleDocuments");
modelBuilder.Entity<OfficeDocument>().ToTable("OfficeDocuments");
modelBuilder.Entity<ExcelDocument>().ToTable("ExcelDocuments");
modelBuilder.Entity<WordDocument>().ToTable("WordDocuments");
modelBuilder.Entity<ClassDocument>().ToTable("ClassDocuments");
modelBuilder.Entity<TextDocument>().ToTable("TextDocuments");
}
public IQueryable<T> GetEntities<T>() where T : class
{
return Set<T>();
}
}
I'm creating some data:
static void CreateTestData()
{
using (Context context = new Context())
{
for (int i = 0; i < 20; i++)
{
ExcelDocument excel = new ExcelDocument()
{
Id = Guid.NewGuid(),
AuthorName = $"ExcelAuthor{i}",
Name = $"Excel{i}",
OfficeName = $"ExcelOffice{i}",
SheetsCount = (i + 1) * 10
};
context.ExcelDocuments.Add(excel);
WordDocument word = new WordDocument()
{
Id = Guid.NewGuid(),
AuthorName = $"WordAuthor{i}",
Name = $"Word{i}",
OfficeName = $"WordOffice{i}",
PagesCount = (i + 2) * 10
};
context.WordDocuments.Add(word);
TextDocument text = new TextDocument()
{
Id = Guid.NewGuid(),
Name = $"Text{i}",
LinesCount = (i + 3) * 10,
HeadName = $"Head{i}",
Level = i + 5
};
context.TextDocuments.Add(text);
}
context.SaveChanges();
}
}
I made some two methods for getting WordDocument from db. One of them using BaseDocument and another one using WordDocument. Both returns 20 instances of WordDocument:
static long ReadBaseDoc()
{
using (Context context = new Context())
{
var words= context.GetEntities<BaseDocument>().Where(e => e.Name.StartsWith("Word"));
Stopwatch stopwatch = Stopwatch.StartNew();
var instacnes = excel.ToList();
stopwatch.Stop();
return stopwatch.ElapsedMilliseconds;
}
}
static long ReadWordDoc()
{
using (Context context = new Context())
{
var words = context.GetEntities<WordDocument>().Where(e => e.Name.StartsWith("Word"));
Stopwatch stopwatch = Stopwatch.StartNew();
var instacnes = words.ToList();
stopwatch.Stop();
return stopwatch.ElapsedMilliseconds;
}
}
I tested moth method separately, several times, in average method ReadWordDoc takes 25ms and method ReadBaseDoc takes 52ms (instances are the same ).
It's not too big problem now, but when we have complex inheritance it takes more than 1 second. I created 10 classes and inherited from BaseDocument. After that I executed ReadBaseDoc and ReadWordDoc methods. ReadWordDoc took 25ms and ReadBaseDoc took 1023ms. Instances are the same, why ReadBaseDoc takes more time? What is the better way to avoid this kind of problems in EF?
Take a look here. There are ways to make EF faster, but in those complex scenarios ORM just creates more problems than it solves.
One way in your case would be to try to change the inheritance to TablePerType, MAYBE it will be a little bit faster.
Other way would be to locate the slow request and use Dapper for them - it will be much faster.
Last way would be to create a Repository with live cache that loads the full database into memory and keeps it up to date - this should be a singleton in an app. If you have more than one app using the same database, you need to hookup data change triggers.
In general, I would say for slow (and relatively simple) queries like yours, use Dapper + AutoMapper. Keep EF so that your database stays synchronized with your classes, but do not rely on it for queries.
If you really want to stick to ORM, I think you need to switch nHibernate. Haven't try it myself, but form what I read, it is superior in almost every possible way, that includes performance and startup time.
I am having trouble saving children entities via Entity Framework / ASP Identity. It seems to be adding duplicates of everything that is added.
I have tried using a detached graph of the DrivingLicenceModel by TeamMember.DrivingLicence = null in the TeamMemberModel and then working with a detached graph by looking if there is new or old DrivingLicenceCategories but because DrivingLicence links back to TeamMember it causes TeamMember.DrivingLicenceId to be null as it cannot link back to TeamMember.
I have tried Manually adding the EntityState to the DrivingLicence and DrivingLicenceCategories but when I do that it complains that it cannot save two entities with the same primary key.
I assume this is because they way I am copying the entities but I after a lot of looking I am drawing a blank.
If there anyway to copy from TeamMemberRequestModel to TeamMemberModel and then save without the children trying to create clone copies of themselves?
Models
public class TeamMemberModel : IdentityUser
{
public virtual DrivingLicenceModel DrivingLicence { get; set; }
public void ShallowCopy(TeamMemberRequestModel src)
{
this.DateOfBirth = src.DateOfBirth;
if (src.DrivingLicence != null)
{
if (this.DrivingLicence == null)
{
this.DrivingLicence = new DrivingLicenceModel(src.DrivingLicence);
}
else
{
this.DrivingLicence.ShallowCopy(src.DrivingLicence);
}
}
}
public TeamMemberModel() { }
}
public class DrivingLicenceModel
{
[Key]
public int Id { get; set; }
[ForeignKey("TeamMember")]
public string TeamMemberId { get; set; }
[JsonIgnore]
public TeamMemberModel TeamMember { get; set; }
public virtual List<DrivingLicenceCategoryModel> DrivingLicenceCategories { get; set; }
public DrivingLicenceModel() { }
public DrivingLicenceModel(DrivingLicenceModel src)
{
this.ShallowCopy(src);
}
public void ShallowCopy(DrivingLicenceModel src)
{
this.Id = src.Id;
this.IsFullLicence = src.IsFullLicence;
this.IssueDate = src.IssueDate;
this.ExpiryDate = src.ExpiryDate;
this.IssuingAuthority = src.IssuingAuthority;
this.LicenceNumber = src.LicenceNumber;
this.DrivingLicenceCategories = src.DrivingLicenceCategories;
this.DrivingLicencePoints = src.DrivingLicencePoints;
}
}
public class DrivingLicenceCategoryModel
{
[Key]
public int Id { get; set; }
[ForeignKey("DrivingLicence")]
public int DrivingLicenceId { get; set; }
[JsonIgnore]
public DrivingLicenceModel DrivingLicence { get; set; }
}
public class TeamMemberRequestModel
{
public string Id { get; set; }
public virtual DrivingLicenceModel DrivingLicence { get; set; }
}
Context
public class TIERDBContext : IdentityDbContext<TeamMemberModel, RoleModel, string, IdentityUserLogin, IdentityUserRole, IdentityUserClaim>
{
public TIERDBContext() : base("SARDBConnection") { }
public DbSet<DrivingLicenceModel> DrivingLicences { get; set; }
public DbSet<DrivingLicenceCategoryModel> DrivingLicenceCategories { get; set; }
}
Controller
public async Task<IHttpActionResult> Put(string id, TeamMemberRequestModel teamMember)
{
TeamMemberModel CurrentTeamMember = await this.TIERUserManager.FindByIdAsync(id);
CurrentTeamMember.ShallowCopy(teamMember);
await this.TIERUserManager.UpdateAsync(CurrentTeamMember);
}
you have to create clone property into context class
.
In the context clases you could to use clone method that retiran the entity you send by parameters this duplicarse any entity you pass. Sorry for my english
hope you help
After far to many hours working over this. I have come to an answer. The best way to deal with this is to simply deal with it is to add or attach all entities down the tree.
The controller now attaches all children unless they have an ID of 0, therefore new and uses add instead. Then I use this very useful extension I found here http://yassershaikh.com/c-exceptby-extension-method/ to compare lists to see added and deleted entities in the list. While I don't need the added part as the entity will already be marked to an add state as I use add() it does not harm and I want to use it later with add and delete state changing.
Controller
public async Task<IHttpActionResult> Put(string id, TeamMemberRequestModel teamMember)
{
TIERDBContext IdentityContext = (TIERDBContext)this.TIERUserManager.UserStore().Context;
foreach (DrivingLicenceCategoryModel DrivingLicenceCategory in teamMember.DrivingLicence.DrivingLicenceCategories)
{
if (DrivingLicenceCategory.Id == 0)
{
IdentityContext.DrivingLicenceCategories.Add(DrivingLicenceCategory);
}
else
{
IdentityContext.DrivingLicenceCategories.Attach(DrivingLicenceCategory);
}
}
foreach (DrivingLicencePointModel DrivingLicencePoint in teamMember.DrivingLicence.DrivingLicencePoints)
{
if (DrivingLicencePoint.Id == 0)
{
IdentityContext.DrivingLicencePoints.Add(DrivingLicencePoint);
}
else
{
IdentityContext.DrivingLicencePoints.Attach(DrivingLicencePoint);
}
}
this.DetectAddedOrRemoveAndSetEntityState(CurrentTeamMember.DrivingLicence.DrivingLicenceCategories.AsQueryable(),teamMember.DrivingLicence.DrivingLicenceCategories, IdentityContext);
this.DetectAddedOrRemoveAndSetEntityState(CurrentTeamMember.DrivingLicence.DrivingLicencePoints.AsQueryable(),teamMember.DrivingLicence.DrivingLicencePoints, IdentityContext);
CurrentTeamMember.ShallowCopy(teamMember);
await this.TIERUserManager.UpdateAsync(CurrentTeamMember);
}
I then use a generic that uses ExceptBy to work out what is added and delete from the old team member model to the new team member model.
protected void DetectAddedOrRemoveAndSetEntityState<T>(IQueryable<T> old, List<T> current, TIERDBContext context) where T : class, IHasIntID
{
List<T> OldList = old.ToList();
List<T> Added = current.ExceptBy(OldList, x => x.Id).ToList();
List<T> Deleted = OldList.ExceptBy(current, x => x.Id).ToList();
Added.ForEach(x => context.Entry(x).State = EntityState.Added);
Deleted.ForEach(x => context.Entry(x).State = EntityState.Deleted);
}
It works but it is far from great. It takes two DB queries, getting the original and updating. I just cannot think of any better way to do this.
I'm using EntityFramework as a DataLayer and DTO to transfer data between layer. I develop Windows Forms in N-Tier architecture and when I try to mapping from Entity to DTO in BLL:
public IEnumerable<CategoryDTO> GetCategoriesPaged(int skip, int take, string name)
{
var categories = unitOfWork.CategoryRepository.GetCategoriesPaged(skip, take, name);
var categoriesDTO = Mapper.Map<IEnumerable<Category>, List<CategoryDTO>>(categories);
return categoriesDTO;
}
I've got this error:
http://s810.photobucket.com/user/sky3913/media/AutoMapperError.png.html
The error said that I missing type map configuration or unsupported mapping. I have registered mapping using profile in this way at UI Layer:
[STAThread]
static void Main()
{
AutoMapperBusinessConfiguration.Configure();
AutoMapperWindowsConfiguration.Configure();
...
Application.Run(new frmMain());
}
and AutoMapper configuration is in BLL:
public class AutoMapperBusinessConfiguration
{
public static void Configure()
{
Mapper.Initialize(cfg =>
{
cfg.AddProfile<EntityToDTOProfile>();
cfg.AddProfile<DTOToEntityProfile>();
});
}
}
public class EntityToDTOProfile : Profile
{
public override string ProfileName
{
get { return "EntityToDTOMappings"; }
}
protected override void Configure()
{
Mapper.CreateMap<Category, CategoryDTO>();
}
}
public class DTOToEntityProfile : Profile
{
public override string ProfileName
{
get { return "DTOToEntityMappings"; }
}
protected override void Configure()
{
Mapper.CreateMap<CategoryDTO, Category>();
}
}
I've got the same error too when mapping from DTO to Entity.
category = Mapper.Map<Category>(categoryDTO);
How to solve this?
Its because you are using Mapper.Initialize multiple times. If you look at the source code it calls Mapper.Reset() which means only the last mapping defined will work. so instead simply remove the Initialize calls and replace with Mapper.AddProfile< >
Use AutoMapper.AssertConfigurationIsValid() after the Configure() calls. If anything fails it will throw an exception with a descriptive text. It should give you more info to debug further.
Mapping DTOs to Entities using AutoMapper and EntityFramework
here we have an Entity class Country and an CountryDTO
public class Country
{
public int CountryID { get; set; }
public string ContryName { get; set; }
public string CountryCode { get; set; }
}
CountryDto
public class CountryDTO
{
public int CountryID { get; set; }
public string ContryName { get; set; }
public string CountryCode { get; set; }
}
Create Object of CountryDTO
CountryDTO collection=new CountryDTO();
collection.CountryID =1;
collection.ContryName ="India";
collection.CountryCode ="in";
Country model = Convertor.Convert<Country, CountryDTO>(collection);
dbcontext.Countries.Add(model);
dbcontext.SaveChanges();
this will work fine for a new Country, the above code will map CountryDTO to Country Entity Object and add new entities to the dbcontext and save the changes.
using System.Reflection;
public static TOut Convert<TOut, TIn>(TIn fromRecord) where TOut : new()
{
var toRecord = new TOut();
PropertyInfo[] fromFields = null;
PropertyInfo[] toFields = null;
fromFields = typeof(TIn).GetProperties();
toFields = typeof(TOut).GetProperties();
foreach (var fromField in fromFields)
{
foreach (var toField in toFields)
{
if (fromField.Name == toField.Name)
{
toField.SetValue(toRecord, fromField.GetValue(fromRecord, null), null);
break;
}
}
}
return toRecord;
}
public static List<TOut> Convert<TOut, TIn>(List<TIn> fromRecordList) where TOut : new()
{
return fromRecordList.Count == 0 ? null : fromRecordList.Select(Convert<TOut, TIn>).ToList();
}
http://bhupendrasinghsaini.blogspot.in/2014/09/convert-enity-framwork-data-in-entity.html
I have a first list of entities like this :
public partial class Networking :EntityBase
{
public virtual int NetWorkingId
{
get;
set;
}
public virtual string NetWorkingParam
{
get;
set;
}
public virtual System.DateTime NetWorkingDate
{
get;
set;
}
}
And I have a second list of entities like this:
public partial class PrivateNetwork :EntityBase
{
public virtual int PrivateNetworkId
{
get;
set;
}
public virtual int ContaId
{
get { return _contaId; }
set
{
if (_contaId != value)
{
if (Contact != null && Contact.ContaId != value)
{
Contact = null;
}
_contaId = value;
}
}
}
public virtual Nullable<System.DateTime> DateCreation
{
get;
set;
}
}
I want to collect these two lists in one and sort all the elements by date.
Is that possible ?
You can do this, although it's not very pretty, and you end up with an IEnumerable<object> so you have to check each item's type before you can use it:
IEnumerable<object> sorted = myNetworkingList
.Concat<object>(myPrivateNetworkList)
.OrderBy(n => n is Networking
? (DateTime?)((Networking)n).NetWorkingDate
: ((PrivateNetwork)n).DateCreation);
foreach (object either in sorted)
{
if (either is Networking)
// Networking; do something
else
// PrivateNetwork; do something else
}
This problem could easily be solved by using polymorphism; use a common base class or interface for both classes, which has the DateTime property you want to sort on.
Example:
public abstract class NetworkingBase : EntityBase
{
public DateTime DateToSortOn { get; set; }
}
or
public interface INetworking
{
public DateTime DateToSortOn { get; set; }
}
And then make your classes derive from NetworkingBase or implement INetworking:
public partial class Networking : NetworkingBase
{
...
}
public partial class PrivateNetwork : NetworkingBase
{
...
}
or
public partial class Networking : EntityBase, INetworking
{
...
}
public partial class PrivateNetwork : EntityBase, INetworking
{
...
}
Do a LINQ Union or Concat and then an OrderBy on the resulting collection.
What I should have asked earlier is . . .
What do you want to do after you've sorted them?
The answer to this could have a big impact on the potential solution.
If the answer is something like I need to display a list of the dates, where you only need the dates in order. If so then you don't need to merge the two lists, you can get a sequence of just the ordered dates and use that e.g.
var orderedDates = networks.Select(n => n.NetworkingDate)
.Union(privateNetworks.Select(n => n.DateCreation))
.OrderBy(date => date);
If the answer is I need to display a list of links showing the Date that links to the Id of the object, and something to identify the type of object, then you could get away with something very like the above, with an Anonymous object.
var orderedDates = networks.Select(n => new {Date = n.NetworkingDate, Id = n.NetWorkingId, NetworkType = n.GetType().Name})
.Union(privateNetworks.Select(n => new {Date = n.DateCreation, Id = n.PrivateNetWorkingId, NetworkType = n.GetType().Name}))
.OrderBy(n => n.Date);
However if the answer is I need to send a Shutdown() command to the 10 oldest networks then you really do need a polymorphic solution, where you have a single type that you can call a Shutdown() method on, that will resolve to the specific Shutdown() method on the types you're using.
A Polymorphic solution to use only if user khellang's answer doesn't work for you
From a comment on another answer
#BinaryWorrier I chose this answer because I already have records in
the database, so if I choose to add a new interface how will I deal
with the records already stored before adding the interface ?
I find it difficult to believe that your ORM won't allow you to add an interface to an entity class and not - somehow - mark that interface and/or it's member so they're ignored by the ORM.
However, assuming you can't add a new interface or base class, you can still do this polymorphically.
Add the interface, add a class implementing the interface that for each of your Network classes (the Abstractor classes), then transform the network classes into Abstractor classes, adding them to a List<INetwork> and sorting that list.
public interface INetwork
{
DateTime? Date { get; }
}
public class PrivateNetworkAbstractor
:INetwork
{
private PrivateNetwork network;
public PrivateNetworkAbstractor(PrivateNetwork network)
{
this.network = network;
}
public DateTime? Date
{
get { return network.DateCreation; }
}
}
public class NetworkingAbstractor
: INetwork
{
private Networking networking;
public NetworkingAbstractor(Networking networking)
{
this.networking = networking;
}
public DateTime? Date
{
get { return networking.NetWorkingDate; }
}
}
...
public IEnumerable<INetwork> MergenSort(IEnumerable<Networking> generalNetWorks, IEnumerable<PrivateNetwork> privateNetWorks)
{
return generalNetWorks.Select(n => new NetworkingAbstractor(n)).Cast<INetwork>()
.Union(privateNetWorks.Select(n => new PrivateNetworkAbstractor(n)).Cast<INetwork>())
.OrderBy(n=> n.Date);
}
Create an interface that has the date and implement in in both classes. After that sorting is easy.
public interface INetwork
{
DateTime? Date { get; }
}
public partial class Networking :EntityBase, INetwork
{
public DateTime? Date
{
get { return NetWorkingDate; }
}
}
public partial class PrivateNetwork :EntityBase, INetwork
{
public DateTime? Date
{
get { return DateCreation; }
}
}
var commonList = new List<INetwork>();
// Add instances of PrivateNetwork and Networking to the list
var orderedByDate = commonList.OrderBy(n => n.Date);
First solution is using anonymous type
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Example1
{
class Program
{
class Human
{
public string Name { get; set; }
public string Hobby { get; set; }
public DateTime DateOfBirth { get; set; }
}
class Animal
{
public string Name { get; set; }
public string FavouriteFood { get; set; }
public DateTime DateOfBirth { get; set; }
}
static void Main(string[] args)
{
var humans = new List<Human>
{
new Human
{
Name = "Kate",
Hobby = "Fitness",
DateOfBirth = DateTime.Now.AddYears(-27),
},
new Human
{
Name = "John",
Hobby = "Cars",
DateOfBirth = DateTime.Now.AddYears(-32),
},
};
var animals = new List<Animal>
{
new Animal
{
Name = "Fluffy",
FavouriteFood = "Grain",
DateOfBirth = DateTime.Now.AddYears(-2),
},
new Animal
{
Name = "Bongo",
FavouriteFood = "Beef",
DateOfBirth = DateTime.Now.AddYears(-6),
},
};
var customCollection = (from human in humans
select new
{
Name = human.Name,
Date = human.DateOfBirth,
}
).Union(from animal in animals
select new
{
Name = animal.Name,
Date = animal.DateOfBirth,
}).OrderBy(x => x.Date);
foreach (dynamic customItem in customCollection)
Console.WriteLine(String.Format("Date: {0}, Name: {1}", customItem.Date, customItem.Name));
Console.Read();
}
}
}
or without anonymous type (created CustomClass):
...
class CustomClass
{
public string Name { get; set; }
public DateTime Date { get; set; }
}
...
var customCollection = (from human in humans
select new CustomClass
{
Name = human.Name,
Date = human.DateOfBirth,
}
).Union(from animal in animals
select new CustomClass
{
Name = animal.Name,
Date = animal.DateOfBirth,
}).OrderBy(x => x.Date);
foreach (CustomClass customItem in customCollection)
Console.WriteLine(String.Format("Date: {0}, Name: {1}", customItem.Date, customItem.Name));
...
I simply added a base class and assigned it as the parent of both list's classes . and then simple did the union . it made the trick
I have 2 POCO classes:
class Email: Base
{
public int SomeProperty { get; set; }
}
class Photo: Base
{
public int SomeOtherProperty { get; set; }
}
and a base class
abstract class Base
{
public DateTime DateCreated { get; set; }
}
here is my context definition:
public class EntitiesContext : DbContext
{
public DbSet<Email> Emails { get; set; }
public DbSet<Photo> Photos { get; set; }
}
of course these classes here are just for the sake of example, things are quite more complicated.
base class is only intended to have common properties for each table - date modified, state, etc. I believe I use Table-Per-Type approach.
PROBLEM: I have some common business logic which I need to run against each table (for example, count non-processed items of each type). I need a way to iterate through a set of tables with common base class. I was hoping to do something like this:
private void GoThroughAllTables(Action<DbSet<Base>> fnProcess, bool needSave)
{
using (var db = new EntitiesContext())
{
fnProcess(db.Emails);
fnProcess(db.Photos);
if (needSave == true)
{
db.SaveChanges();
}
}
}
public IEnumerable<QueueStatus> GetQueueStatus()
{
var res = new List<QueueStatus>();
GoThroughAllTables((set) =>
{
res.Add(new QueueStatus
{
Count = set.Cast<Base>().Count(x => x.DateCreated > someDate),
});
}, false);
return res;
}
public void DeleteFailedItems()
{
GoThroughAllTables((set) =>
{
set.Cast<Base>().Remove(x => x.DateCreated > someDate);
}, true);
return res;
}
this will not compile:
fnProcess(db.Emails);
Argument 1: cannot convert from 'System.Data.Entity.DbSet|Email|' to
'System.Data.Entity.DbSet|Base|'
passing non-typed DbSet will not work because Cast will fail.
so I'm not sure what else can I try. Any suggestions?