Map One to One Relationship at Run-Time - c#

i'm trying to upgrade an old CMS to use NHibernate and can't deter from the original database structure much. Here is the bit which is causing an issue. Say i have the following 2 tables:
Articles:
- Id (PK, Identity)
- Title
- Content
Meta:
- ArticleId (PK, FK to Articles)
- Description
- Keywords
I have created the following classes:
public class Article {
public virtual int Id { get; set; }
public virtual string Title { get; set; }
public virtual string Content { get; set; }
}
public class Meta : IComponent {
public virtual string Description { get; set; }
public virtual string Keywords { get; set; }
}
public interface IComponent {
}
Usually the Meta would normally be mapped as a component (or a one to one relationship) property on the Article class. However in the application i'm building an admin can enable/disable the components that apply to articles. Also i'd like them to extend the application to add their own components without touching the Article class.
For them reasons i can't add a property against the Article class. Now ideally in my code i'd like to be able to say:
var articles = session.Query<Article>()
.Fetch(a = a.Component<Meta>())
.Where(a => a.Component<Meta>().Keywords.Contains("Some Word"))
.ToList();
// This wouldn't generate an extra SQL statement
var keywords = articles[0].Component<Meta>().Keywords;
Which would generate the following SQL (or similar):
SELECT * FROM Articles INNER JOIN Meta ON Articles.Id = Meta.ArticleId WHERE Meta.Keywords LIKE '%Some Word%'
Is it possible to map the Component method so that it does an inner join to get the Meta. The concept seems pretty simple but i don't have a clue where begin. I'd really appreciate the help.
Thanks

Given this:
public class Article
{
public virtual int ArticleId { get; set; }
public virtual string Title { get; set; }
public virtual string Content { get; set; }
}
public class Meta : IComponent
{
public virtual Article Article { get; set; }
public virtual int MetaId { get; set; }
public virtual string Description { get; set; }
public virtual string Keywords { get; set; }
}
AFAIK, you cannot Fetch something that isn't part of an entity. So from your example, it's not possible to fetch Meta from the Article entity.
So if you want to fetch the other info of an Article, you just have to join Article to them, then project the complete data in your Linq, example:
var articles =
from a in s.Query<Article>()
join m in s.Query<Meta>() on a equals m.Article
where m.Keywords.Contains("Some Word")
select new { a, m };
foreach(var x in articles)
Console.WriteLine("{0} {1}", x.a.Title, x.m.Description);
Resulting query:
select *
from [Article] article0_, [Meta] meta1_
where meta1_.ArticleId = article0_.ArticleId
and meta1_.Keywords like '%Some Word%'
Another approach, start from Meta, then fetch the Article; on query, this will join the Article immediately, i.e. no lazy loading:
var artB =
from m in s.Query<Meta>().Fetch(x => x.Article)
where m.Keywords.Contains("Some Word")
select m;
foreach (var x in artB)
Console.WriteLine("{0} {1}", x.Article.Title, x.Description);
Resulting query:
select *
from [Meta] meta0_
left outer join [Article] article1_ on meta0_.ArticleId = article1_.ArticleId
where meta0_.Keywords like '%Some Word%'
To keep an Article having only one Meta, put a Unique on Meta's reference:
create table Article
(
ArticleId int identity(1,1) not null primary key,
Title varchar(100) not null,
Content varchar(100) not null
);
create table Meta
(
-- this prevents an Article having two Meta
ArticleId int not null references Article(ArticleId) unique,
MetaId int identity(1,1) not null primary key,
Description varchar(100) not null,
Keywords varchar(100) not null
);
insert into Article(Title,Content) values('Great','Yeah')
insert into Meta(ArticleId, Description, Keywords) values(1,'Oh','Some Word');

In your NHibernate mapping file, you can specify the fetch type. The spec for the one-to-one xml is located in the NHibernate Documentation. Notice that number 5 has an option of Join and Select and it defaults to select.

Would the following solution be of interest?
You can make a protected mapping to your components and access these from this public generic method.
This way you can choose to eager/lazy load your components.
public class Article
{
protected virtual ICollection<IComponent> Components { get; set; }
public virtual T Component<T>() where T : IComponent
{
return Components.FirstOrDefault(c=>c.Type==typeof(T));
}
}

Related

How to model a collection of items where one of them is the active item?

I have a situation where an entity has a list of children that are inactive. Additionally, they have another "current/active" sub entity of the same type.
The following would be the ideal modeling, but I cannot figure out how to this with Entity Framework Core:
public class Customer
{
public Application CurrentApplication { get; set; }
public List<Application> PastApplications { get; set; }
}
public class Application
{
public bool IsCurrent { get; set; }
public Customer Customer { get; set; }
}
In the past, I've typically modeled it as so:
public class Customer
{
public Application CurrentApplication => AllApplications.Single(a => a.IsCurrent);
public List<Application> PastApplications => AllApplications.Where(a => !a.IsCurrent);
public List<Application> AllApplications { get; set; }
}
public class Application
{
public bool IsCurrent { get; set; }
public Customer Customer { get; set; }
}
However, I feel that this could lead to the possibility of another Application incorrectly being set as IsCurrent, thus breaking the .Single().
What's the suggested way to accomplish this from a DDD perspective? If that doesn't match up with what EF Core can do, what is a good practical suggestion?
I don't think that this is a DDD problem, rather a how to design a relational DB model and how to use EF Core question.
First you need to decide what is the relationship between Customers and Applications:
One-to-Many, that is, a Customer may have zero or more Applications, but an Application belongs to exactly one Customer. This scenerio is also called a master-detail relationship. The entity on the Many side (Application in this case) stores a reference (called a foreign key) to its owner (Customer).
Many-to-Many, that is, a Customer may have zero or more Applications, but an Application may belong to zero or more Customers, as well. This scenario is modelled using an extra "join" table (usually named something like CustomerApplication) so the problem is resolved to two One-to-May relationships in the end.
If there is only one active Application at a given time (per customer), the active application can be modelled using a One-to-One (Zero-to-One, to be precise) relationship between Customer and Application (with a foreign key on Customer's side). It can also be modelled using a flag field on Application as you tried but that's not as error-proof as a foreign key (but may have better performance, though).
The code you posted resembles rather a One-to-Many scenario, so I show an example for that case. Understanding the following, you can easily change it Many-to-Many if desired.
First let's define the entities:
public class Customer
{
public int Id { get; set; }
public int? CurrentApplicationId { get; set; }
public Application CurrentApplication { get; set; }
public ICollection<Application> Applications { get; set; }
}
public class Application
{
public int Id { get; set; }
public Customer Customer { get; set; }
}
The single interesting part is int? CurrentApplicationId. We need to explicitly define the foreign key for our Zero-to-Many relationship (more on this later). The nullable int (int?) tells EF that this field can be NULL.
EF is usually able to figure out the relationship mappings but in this case we need to explain them to it explicitly:
class DataContext : DbContext
{
// ctor omitted for brevity
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Customer>(customer =>
{
customer.HasMany(entity => entity.Applications)
.WithOne(relatedEntity => relatedEntity.Customer)
.OnDelete(DeleteBehavior.Cascade);
customer.HasOne(entity => entity.CurrentApplication)
.WithOne()
.HasForeignKey<Customer>(entity => entity.CurrentApplicationId);
});
}
public DbSet<Application> Applications { get; set; }
public DbSet<Customer> Customers { get; set; }
}
What's going on in the OnModelCreating method is called fluent API configuration. This topic and conventions is a must to understand and control how EF maps the entities to DB tables.
Based on the mapping EF is able to generate (see code-first migrations) the following DB schema:
CREATE TABLE [Customers] (
[Id] INTEGER NOT NULL
, [CurrentApplicationId] bigint NULL
, CONSTRAINT [sqlite_master_PK_Customers] PRIMARY KEY ([Id])
, FOREIGN KEY ([CurrentApplicationId]) REFERENCES [Applications] ([Id]) ON DELETE RESTRICT ON UPDATE NO ACTION
);
CREATE UNIQUE INDEX [IX_Customers_CurrentApplicationId] ON [Customers] ([CurrentApplicationId] ASC);
CREATE TABLE [Applications] (
[Id] INTEGER NOT NULL
, [CustomerId] bigint NULL
, CONSTRAINT [sqlite_master_PK_Applications] PRIMARY KEY ([Id])
, FOREIGN KEY ([CustomerId]) REFERENCES [Customers] ([Id]) ON DELETE CASCADE ON UPDATE NO ACTION
);
CREATE INDEX [IX_Applications_CustomerId] ON [Applications] ([CustomerId] ASC);
Exactly what we wanted.
Now, how you query the active and inactive applications in this configuration? Something like this:
var customerId = 1;
using (var ctx = new DataContext())
{
var currentApplication = (
from customer in ctx.Customers
where customer.Id == customerId
select customer.CurrentApplication
).FirstOrDefault();
var pastApplications =
(
from customer in ctx.Customers
from application in customer.Applications
where customer.Id == customerId && customer.CurrentApplication != application
select application
).ToArray();
}
I suggest you to read through the acticles to be found here to get familiar with EF Core.
As for relational DB modelling this site seems a useful resource.
Using EFCore, the following would work:
public class Customer
{
[Key]
public int ID {get; set;}
//other properties
//Navigation Property
public virtual ICollection<Application> Applications{ get; set; }
}
public class Application
{
[Key]
public int ID {get; set;}
[ForeignKey("Customer")]
public int CustomerID{get; set;}
public DateTime ApplicationDate{get; set}
//other properties
public bool IsCurrent { get; set; }
}
I am assuming your are using Code First, so this will be the correct way to do your mappings.
After migrating and updating the context, you could use some backend code to always ensure that your most recent application returns as IsCurrent.
You would can then select your current application as follows:
private yourContext _context;
var yourContext = _context.Customers
.Include(m=>m.Application)
.Where(m=> m.isCurrent==false)
.Single(a=>a.);
return yourContext;
You would of course have to set up your constructor with the context etc

many to many linq query with like expression

What is the best way to do a many to many join ant the entity framework.
I have a tag class
favorite
I have a Tag class
[Table("tblTags")]
public class Tag
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
[Column("fld_int_id")]
public virtual int Id { get; set; }
[Column("fld_str_name")]
public virtual string Name { get; set; }
public virtual ICollection<DocumentUploadEntity> Documents { get; set; }
}
I have a documents class
[Table("tblUploadDocument")]
public class DocumentUploadEntity
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Column("fld_int_ID")]
public int ID { get; set; }
public virtual ICollection<Tag> Tags { get; set; }
}
I map them like this:
modelBuilder.Entity<DocumentUploadEntity>()
.HasMany(x => x.Tags)
.WithMany(x => x.Documents)
.Map(x =>
{
x.ToTable("tblUploadDocumentsTags");
x.MapLeftKey("fld_int_document_id");
x.MapRightKey("fld_int_tag_id");
});
I want to search for any documents contain a tag name in a like expression. If I was to do this in sql I would do this:
SELECT * FROM tblUploadDocument d
INNER JOIN tblUploadDocumentsTags ud
ON fld_int_document_id = d.fld_int_id
INNER JOIN tbltags t
ON ud.fld_int_tag_id = t.fld_int_id
WHERE t.fld_str_name like 'foo%';
Please excuse the table names and field names, this was not my doing.
How can I do this with linq and entity framework.
var documents = DbContext.Tags.Where(x => x.Name.StartsWith("foo"))
.SelectMany(y => y.Documents).ToList()
The beauty of the EF is that you can start from either side and use the navigation property to get to the other side of the many-to-many relationship. Behind the scenes EF will use the link table and necessary joins.
For instance, you can start from DocumentUploadEntity:
var documents =
from document in db.DocumentUploadEntities
where document.Tags.Any(tag => tag.Name.Contains("foo"))
select document;
or you can start from Tag:
var documents =
from tags in db.Tags
where tag.Name.Contains("foo")
from document in tag.Documents
select document;
UPDATE:: As #James Dev correctly stated in the comments, the equivalent of SQL LIKE 'foo% is Name.StartsWith("foo").

Entity Framework - Retrieve different values according to foreign keys

I'm new working with EF and a have this scenario:
Classes:
public class Step
{
public int ID { get; set; }
public string name { get; set; }
}
public class Workflow
{
public int ID { get; set; }
public int stepID { get; set; }
public int nextStepID { get; set; }
public virtual Step Step
}
What I want to know is if have a way to get "name" from class Step based on stepID and nextStepID.
I know that I can do that
var result = (from Workflow in db.Workflow
join Step in db.Step on Workflow.stepID equals Step.ID
join nextStep in db.Step on Workflow.nextStepID equals nextStep.ID
select new
{
nameStep = Step.name,
nameNextStep = nextStep.name
}
).ToList();
but this way i'm not retrieving a Workflow entity.
I'm wondering if is possible to do something like that "automatically" using EF to retrieve a Workflow entity with Step and Next Step name.
I hope that's clear.
Thanks in advance
You can include the WorkFlow entity in the anonymous type like this:
select new
{
Workflow = Workflow,
nameStep = Step.name,
nameNextStep = nextStep.name
}
You might want to consider modeling the Workflow entity in such a way that it has two navigational properties to Step. One for the stepID foreign key and one for the nextStepID foreign key. This will make your queries simpler. Take a look at this question.

Linq create typed result list

I have made some hard work, I think may be there is library that can make this work. For example, I create a model classes for .NET EF 6 mapped to Sql Server tables:
class Author {
public long Id { get; set; }
public string Name { get; set; }
}
class Book {
public long Id { get; set; }
public string Name { get; set; }
public long AuthorId { get; set;}
}
there is iherited class:
class BookEx : Book {
public string AuthorName { get;set; }
}
and LINQ query:
var query = from t in context.Books
join t1 in context.Authors on t.Id equals t1.AuthorId
select new BookEx {
Id = t.Id,
Name = t.Name,
AuthorId = t.AuthorId,
AuthorName = t1.Name
}
every thinks works fine, but this is simple class, when I need class like Book with more properies, I need fill every properties and it is too hard some times:
select new BookEx {
Id = t.Id,
Name = t.Name,
AuthorId = t.AuthorId,
AuthorName = t1.Name
}
can I make a short version of filling like:
select new BookEx {
AuthorName = t1.Name
}
where properties of parent class Book will filled by LINQ or some other methods? Is there libraries that can make this hard work (some times we forget fill some properties)?
EDITED: Project don't use associations. There is no need to use association, take full table (in table can be 10-40 properties) data of Author but get only Name property. Need solve problem like in exmple.
I think there is no need to create a new entity named BookEx, you can access Author entity details like AuthorName using navigation properties. You don't need to use select statement. For example:
var books = from t in context.Books
join t1 in context.Authors on t.Id equals t1.AuthorId;
this will return Book details with Author detials too so you can get the AuthorName like this-
foreach(var item in books)
{
string authorName=item.Author.Name;
}
To achieve this you need to add one more property (navigation property to Author) in your book class:
public class Book {
public long Id { get; set; }
public string Name { get; set; }
public long AuthorId { get; set;}
public virtual Author Author { get; set; }
}
If performance is a paramount concern you may have to lose some flexibility, but you shouldn't sacrifice re-use. It's unclear where your break-even is here, but if your approach of creating a BookEx projection is working for you, you can certainly wrap it in a method for re-use. This way you avoid "needing to fill every property" and "sometimes...forgetting to fill some properties":
static class DaoExtensions
{
public static IQueryable<BookEx> BookPlusAuthorNames(this DbContext context)
{
var query = from t in context.Books
join t1 in context.Authors on t.Id equals t1.AuthorId
select new BookEx {
Id = t.Id,
Name = t.Name,
AuthorId = t.AuthorId,
AuthorName = t1.Name
}
return query;
}
}
Again, because of your performance concern, I wouldn't go to an external library for this problem as stated.
For posterity: if development flexibility were prioritized over performance, it would be better to map this natural association by adding Author as a navigation property on Book:
class Book {
...
[ForeignKey("AuthorId")]
public virtual Author Author { get; set; }
}
This will allow usage like book.Author.Name to navigate directly to Name. EF will handle all the join-logic.

How to deal with Many to Many database in Asp.Net Web API

I am working on a ASP.Net Web API and I have been bangin my head to the wall but I had no luck so far with the many to many relationship.
I am working on a MVC project
I have 3 tables classes as you can see below:
[Table("Emails")]
public class Email
{
public int Id { get; set; }
public string EmailAddress { get; set; }
}
[Table("Reports")]
public class Report
{
public int Id { get; set; }
public string Name { get; set; }
}
[Table("ReportsAndEmails")]
public class ReportAndEmail
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Id { get; set; }
public int EmailID { get; set; }
public int ReportID { get; set; }
}
On table ReporsAndEmails, I have two foreign keys, Id in Email table is the foreign key for EmailID and Id in Report is the foreign Key for ReportID. Everything works as I expect. But I want to be able is, I want to show all the records in ReportAndEmail table to the user, but instead of showing EmailID, match the id in Email table and show the EmailAddress and the same way for the ReportID. I want o match the reportID with he Report table's ID and show the Name instead.
I know I can hack the code to show that in the client part by loading all the tables and matching data, but I know that approach is so silly. but I am making an API web project, so I want to deal with that in the server side rather than the client side. My question is, what is the best way of dealing with this issue? What should I really do ? Any comment on this is highly appreciated.
I think you are looking for Linq join.
http://code.msdn.microsoft.com/101-LINQ-Samples-3fb9811b (search for "join" on the page) has the following example, but you need to join 3 tables in a similar way.
public void Linq102()
{
string[] categories = new string[]{
"Beverages",
"Condiments",
"Vegetables",
"Dairy Products",
"Seafood" };
List<Product> products = GetProductList();
var q =
from c in categories
join p in products on c equals p.Category
select new { Category = c, p.ProductName };
foreach (var v in q)
{
Console.WriteLine(v.ProductName + ": " + v.Category);
}
}

Categories