TPL Dataflow block to produce multiple outputs from a single input - c#

I have started looking into TPL Dataflow as a solution to a processing workflow.
The gist of the processing workflow is to read in input messages from multiple tables and create four reflecting objects from them and persist them to four other tables, so each input message should result in four new messages getting created.
I cannot identify one of the predefined blocks that can help with the creation of the four objects, at first TransformManyBlock seems to be what I am looking for but it returns multiple objects of the same type where I will have four types.
Example of the Problem
We have two tables containing Employee details from two legacy systems, their entities look like this
public partial class EmployeeTblA
{
public int Id { get; set; }
public int System { get; set; }
public string Forename { get; set; }
public string Surname { get; set; }
public int Age { get; set; }
public int Number { get; set; }
public string Street { get; set; }
public string PostCode { get; set; }
public virtual EmployeeSystem SystemNavigation { get; set; }
}
public partial class EmployeeTblB
{
public int Id { get; set; }
public int System { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public string Address { get; set; }
public string Postcode { get; set; }
public virtual EmployeeSystem SystemNavigation { get; set; }
}
We want to take the data from the two systems and put the data into our shiny new system, to do this we need to convert the entities from the old system to the entities used in the new system. First we convert the entities from the old system to a base class which look like this
public class BaseEmployee
{
public int Id { get; set; }
public int System { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public string Address { get; set; }
public string Postcode { get; set; }
}
We then want to create three new objects from the base class that represent the entities of the new system which looks like this
public partial class EmployeeName
{
public int Id { get; set; }
public int System { get; set; }
public int LegacyId { get; set; }
public string Name { get; set; }
public virtual EmployeeSystem SystemNavigation { get; set; }
}
public partial class EmployeeAge
{
public int Id { get; set; }
public int System { get; set; }
public int LegacyId { get; set; }
public int Age { get; set; }
public virtual EmployeeSystem SystemNavigation { get; set; }
}
public partial class EmployeeAddress
{
public int Id { get; set; }
public int System { get; set; }
public int LegacyId { get; set; }
public string Address { get; set; }
public string Postcode { get; set; }
public virtual EmployeeSystem SystemNavigation { get; set; }
}
The rough flow of my TPL for the above example
Read data from tables in DB into a TranformBlock<Employee,BaseEmployee> converting to a common object, this is down twice for each of the legacy systems
Each TranformBlock<Employee, BaseEmployee> is linked to a BatchBlock to group all the input flows.
BatchBlock is linked to a Block<BaseEmployee, ...> which will take the input and create two new objects from the input data, EmployeeName and EmployeeAge.
The Block<BaseEmployee, ...> will then be linked to Action block and Action which take save them to their respective table's in the DB
I know I can create a custom block but I cannot figure out how I could use it to provide the output to four separate linked ActionBlock using dataflow, can someone please point me in the right direction?

The Broadcast block was the component I ended up going with, I used it to broadcast the BaseEmployee object to other output streams splitting out the reflecting objects I needed to create.
Full pipeline same below
_transEmployeeA = new TransformBlock<EmployeeTblA, BaseMsg>((input) =>
{
return new BaseMsg()
{
Id = input.Id,
System = input.System,
Name = string.Concat(input.Forename, " ", input.Surname),
Age = input.Age,
Address = string.Concat(input.Number, " ", input.Street),
Postcode = input.PostCode
};
});
_transEmployeeB = new TransformBlock<EmployeeTblB, BaseMsg>((input) =>
{
return new BaseMsg()
{
Id = input.Id,
System = input.System,
Name = input.Name,
Age = input.Age,
Address = input.Address,
Postcode = input.Postcode
};
});
_broadcastBaseMsg = new BroadcastBlock<BaseMsg>(null);
_transEmployeeName = new TransformBlock<BaseMsg, EmployeeName>((baseMsg) =>
{
return new EmployeeName()
{
System = baseMsg.System,
LegacyId = baseMsg.Id,
Name = baseMsg.Name
};
});
_transEmployeeAge = new TransformBlock<BaseMsg, EmployeeAge>((baseMsg) =>
{
return new EmployeeAge()
{
System = baseMsg.System,
LegacyId = baseMsg.Id,
Age = baseMsg.Age
};
});
_transEmployeeAddress = new TransformBlock<BaseMsg, EmployeeAddress>((baseMsg) =>
{
return new EmployeeAddress()
{
System = baseMsg.System,
LegacyId = baseMsg.Id,
Address = baseMsg.Address,
Postcode = baseMsg.Postcode
};
});
_bufferEmployeeName = new BufferBlock<EmployeeName>();
_bufferEmployeeAge = new BufferBlock<EmployeeAge>();
_bufferEmployeeAddress = new BufferBlock<EmployeeAddress>();
_actionEmployeeName = new ActionBlock<EmployeeName>((output) =>
{
using (var cxt = new SandboxContext())
{
cxt.EmployeeNames.Add(output);
cxt.SaveChanges();
}
});
_actionEmployeeAge = new ActionBlock<EmployeeAge>((output) =>
{
using (var cxt = new SandboxContext())
{
cxt.EmployeeAges.Add(output);
cxt.SaveChanges();
}
});
_actionEmployeeAddress = new ActionBlock<EmployeeAddress>((output) =>
{
using (var cxt = new SandboxContext())
{
cxt.EmployeeAddresses.Add(output);
cxt.SaveChanges();
}
});
var linkOpts = new DataflowLinkOptions()
{
PropagateCompletion = true
};
// Transform Employees and pass to Batch
_transEmployeeA.LinkTo(_broadcastBaseMsg, linkOpts);
_transEmployeeB.LinkTo(_broadcastBaseMsg, linkOpts);
// Transform Broadcast to respective outputs
_broadcastBaseMsg.LinkTo(_transEmployeeName, linkOpts);
_broadcastBaseMsg.LinkTo(_transEmployeeAge, linkOpts);
_broadcastBaseMsg.LinkTo(_transEmployeeAddress, linkOpts);
// Add outputs to Buffer
_transEmployeeName.LinkTo(_bufferEmployeeName, linkOpts);
_transEmployeeAge.LinkTo(_bufferEmployeeAge, linkOpts);
_transEmployeeAddress.LinkTo(_bufferEmployeeAddress, linkOpts);
// Persist outputs to DB
_bufferEmployeeName.LinkTo(_actionEmployeeName, linkOpts);
_bufferEmployeeAge.LinkTo(_actionEmployeeAge, linkOpts);
_bufferEmployeeAddress.LinkTo(_actionEmployeeAddress, linkOpts);
Additionally comments from #TheodorZoulias helped to simply my usage of TPL dataflow for this particular dataflow.

Related

Loop through 4 object lists & add their data in a ListView C# Xamarin.Forms

I am working on a Xamarin.Forms Project and I am at a dead-end of sorts. My issue is that I want to display user transactions which I pull from a server, in a listview, however I need four different pull requests to get all the data which means I have four different objects lists which I grouped by the transaction number as you can see in this screenshot:
The key transaction number can be seen and if you expand you'll see the other data within each transaction
Here is the code where I group the deserialised json lists with the common key:
var t = JsonConvert.DeserializeObject<List<trans_mod>>(transactions);
var l = JsonConvert.DeserializeObject<List<loc_mod>>(loc);
var d = JsonConvert.DeserializeObject<List<disc_mod>>(disc);
var it = JsonConvert.DeserializeObject<List<item_mod>>(itm);
var q = it.AsQueryable().GroupBy(g => g.trans).ToList();
var q2= d.AsQueryable().GroupBy(g => g.trans).ToList();
var q3 = l.AsQueryable().GroupBy(g => g.trans).ToList();
var q4 = t.AsQueryable().GroupBy(g => g.position).ToList();
Object Models for each list
public class loc_mod
{
[DataMember]
public string location { get; set; }
[JsonProperty(PropertyName = "#modify_stamp")]
public string stamp { get; set; }
[JsonProperty(PropertyName = "$trans")]
public string trans { get; set; }
}
public class disc_mod
{
[DataMember]
public string discount { get; set; }
[JsonProperty(PropertyName = "#modify_stamp")]
public string stamp { get; set; }
[JsonProperty(PropertyName = "$trans")]
public string trans { get; set; }
}
public class item_mod
{
[JsonProperty(PropertyName = "item.price")]
public string price { get; set; }
[JsonProperty(PropertyName = "item.name")]
public string name { get; set; }
[JsonProperty(PropertyName = "#modify_stamp")]
public string stamp { get; set; }
[JsonProperty(PropertyName = "$trans")]
public string trans { get; set; }
}
public class trans_mod
{
[DataMember]
public string refer { get; set; }
[DataMember]
public string date { get; set; }
[DataMember]
public string time { get; set; }
[DataMember]
public int points { get; set; }
[DataMember]
public string _total { get; set; }
[JsonProperty(PropertyName = "$$position")]
public string position { get; set; }
[JsonProperty(PropertyName = "#modify_stamp")]
public string stamp { get; set; }
[JsonProperty(PropertyName = "$trans")]
public string trans { get; set; }
}
public class itms
{
public string price { get; set; }
public string name { get; set; }
public DateTime stamp { get; set; }
[JsonProperty(PropertyName = "$trans")]
public string trans { get; set; }
}
What I want to do is to loop through all four lists and add the data from each list in the listview but I can't think of a way I can do that.
Listview Add() code Example:
Transactions.Add(new Transaction
{
Details = "Date: " + ti[i].date + " | Time: " + ti[i].time + " |
Reference: " + ti[i].refer,
Isvisible = false, Items= ti[i].item, Total = ti[i].total, Discount
= ti[i].discount
});
Sorry if this is a bit confusing, it's confusing for me as well as I am a relative beginner. Any help is welcome!
Define an Interface that your item classes all implement.
That interface has a method that returns whatever you need for listview.
public Interface IHasTransaction
{
Transaction GetTransaction();
}
public class loc_mod : IHasTransaction
{
...
public Transaction GetTransaction()
{
// Use fields of this class to create a Transaction.
return new Transaction(...);
}
}
public class disc_mod : IHasTransaction
{
...
}
If you want, you can make a list that has a mixture of these:
public List<IHasTransaction> models = new List<IHasTransaction>();
models.Add(new loc_mod(...));
models.Add(new disc_mod(...));
Given any of these items
IHasTransaction model
You can easily get the corresponding Transaction:
model.GetTransaction()
OR
var lm = new loc_mod(...);
lm.GetTransaction()

creating an object in ternary operator from query result

I need to create objects to return to front end from data I got from database because the response doesnt contain ALL fields of entity and I also add this [NotMapped] propety AmountOfTenants = t.Apartment.Tenants.Count(), to the response.
If I remove ternary operator here t.ApartmentId != null ? and just create new Apartment every time, then, when a Tenant doesnt have any related Apartmetn to him, then my json response contains Tenant with an Apartment object inside of him, where all values are 0/null, thats why i need ternary, to set Apartment to null if Tenant's ApartmentId is null.
Without ternary it works, but i get that apartment with 0/null values and when i add ternary operator i get this error:
System.InvalidCastException: Unable to cast object of type 'System.Linq.Expressions.NewExpression' to type 'System.Linq.Expressions.MethodCallExpression'.
Please help
List<Tenant> tenants = await _context.Tenants.Include(tenant => tenant.Apartment.House)
.Select(t => new Tenant
{
Id = t.Id,
FirstName = t.FirstName,
LastName = t.LastName,
PersonalCode = t.PersonalCode,
Birthday = t.Birthday,
PhoneNumber = t.PhoneNumber,
Email = t.Email,
ApartmentId = t.ApartmentId,
Apartment = t.ApartmentId != null ? new Apartment
{
Id = t.Apartment.Id,
Number = t.Apartment.Number,
Floor = t.Apartment.Floor,
AmountOfTenants = t.Apartment.Tenants.Count(),
AmountOfRooms = t.Apartment.AmountOfRooms,
TotalArea = t.Apartment.TotalArea,
AvailableArea = t.Apartment.AvailableArea,
HouseId = t.Apartment.HouseId,
House = t.Apartment.House
} : null
}).ToListAsync();
----------------------------------------------EDIT
someone asked for Tenant entity:
namespace RestApi.Models
{
public class Tenant
{
public long Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string PersonalCode { get; set; }
public DateTime Birthday { get; set; }
public string PhoneNumber { get; set; }
public string Email { get; set; }
public long? ApartmentId { get; set; }
public Apartment? Apartment { get; set; }
}
}
--------------------------EDIT
someone asked for Apartment class:
namespace RestApi.Models
{
public class Apartment
{
public long Id { get; set; }
public int Number { get; set; }
public int Floor { get; set; }
public int AmountOfRooms { get; set; }
[NotMapped]
public int AmountOfTenants { get; set; }
public int TotalArea{ get; set; }
public int AvailableArea { get; set; }
public long? HouseId { get; set; }
public House House { get; set; }
public ICollection<Tenant> Tenants { get; set; }
}
}
------------------------------ EDIT
I got a friend to check something similar on his machine:
var tenants = await context.Tenants
.Include(t => t.Apartment).ThenInclude(a => a.House)
.Include(t => t.Apartment).ThenInclude(a => a.Tenants)
.Select(t => new Tenant
{
Id = t.Id,
//etc...
Apartment = t.ApartmentId != null ? new Apartment
{
Id = t.Apartment.Id,
NumberOfTenants = t.Apartment.Tenants.Count(),
//etc...
} : null
}).ToListAsync();
on his machine this new statement inside another new statement works and gives no error
You could try using this, which should eliminate the need to use the NotMapped annotation and you wouldn't need to use the select at that point either.
public class Apartment
{
public long Id { get; set; }
public int Number { get; set; }
public int Floor { get; set; }
public int AmountOfRooms { get; set; }
public int AmountOfTenants { get { return this.Tenants != null ? this.Tenants.Count : 0; } }
public int TotalArea { get; set; }
public int AvailableArea { get; set; }
public long? HouseId { get; set; }
public House House { get; set; }
public ICollection<Tenant> Tenants { get; set; }
}
New Query
List<Tenant> tenants = await _context.Tenants.Include(tenant => tenant.Apartment).ThenInclude(a => a.House).ToListAsync();
Edit
As for an explanation of the error you are getting. It is basically saying that it can't make a new expression inside of a new expression (i.e. creating a new Apartment inside of a new Tenant)
I'm making an assumption about your Apartments class so correct me if I go astray here.
Assumption : A tenant can only have one apartment and an apartment can have multiple tenants.
With that being said you tenant class just needs to reference an Apartment object. The Apartment ID is contained in Apartment object so you're really storing that data twice. If you're looking to add an new tenant to an apartment you don't need the select statement. You might want to do a bullet point summary to clarify your intentions if there is more to this. If you're looking to include all objects within the Tenant class(data from other tables) you can consider lazy loading if your application permits.
public class Tenant
{
[Key]
public long Id { get; set; } //Primary Key
public string FirstName { get; set; }
public string LastName { get; set; }
public string PersonalCode { get; set; }
public DateTime Birthday { get; set; }
public string PhoneNumber { get; set; }
public string Email { get; set; }
//public long? ApartmentId { get; set; }
public virtual Apartment Apartment { get; set; }
}
public class Apartment
{
[Key]
public Guid ApartmentID { get; set; } //Primary Key
public virtual List<Tenant> Tenants { get; set; }
}
public class appStuff
{
void AddStuff()
{
var myContext = new _context();
Apartment apartment = myContext.Find(Predicate4YourApartment());
var ten = new Tenant
{
Birthday = DateAndTime.Now,
Apartment = apartment,
FirstName = "John" //add the rest.
};
myContext.TenantsTable.Add(ten);
//This will add the Tenant to the Tenants table and the apartment to the Apartments table if you create a new one
//instead of finding an existing one like shown above
myContext.SaveChanges();
}
void GetStuff()
{
var myContext = new _context();
var myTenant = myContext.TenantsTable.Where(x => x.Apartment.ApartmentID == "DesiredID");
}
}
public class _context : DbContext
{
public _context() : base()
{
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseLazyLoadingProxies();
base.OnConfiguring(optionsBuilder);
}
}

Combining data from 2 databases in MVC

I am fairly new to asp.net mvc and I currently have an application that shows a number of errors. I have 2 pages that contain Application Errors and Log Errors. The data comes from 2 different databases but I am wanting to display the data from both databases on one page.
The tables have headings with different names that mean the same thing e.g. ApplicationName in the Application Database is the same thing as LogName in the Log Database.
Below is a small example of what I currently have and an example of what I am wanting.
Current
Application Errors
ID ApplicationName ApplicationMessage ApplicationDate
1 Something Hello World 01/01/2015
2 Something Else Another Message 03/01/2015
Log Errors
ID LogName LogMessage LogDate
1 Some Log A log message 02/01/2015
2 Another Log Another Log Message 04/01/2015
What I Want
Internal Errors
ID Name Message Date
1 Something Hello World 01/01/2015
2 Some Log A log message 02/01/2015
3 Something Else Another Message 03/01/2015
4 Another Log Another Log Message 04/01/2015
At the minute, I have 2 separate models for each database but I think I need to merge both models into one model that combines them both but I am unsure on how to do this. How would I be able to merge both data sources together to display the data within the same page?
Current Models
Application
[Table("ELMAH_Error")]
public class ElmahError
{
[Key]
public System.Guid ErrorId { get; set; }
public System.String Application { get; set; }
public System.String Host { get; set; }
public System.String Type { get; set; }
public System.String Source { get; set; }
public System.String Message { get; set; }
public System.String User { get; set; }
public System.Int32 StatusCode { get; set; }
public System.DateTime TimeUtc { get; set; }
public System.Int32 Sequence { get; set; }
public System.String AllXml { get; set; }
}
Log
[Table("LogEntry")]
public class LogEntry
{
[Key]
public Int64 ID { get; set; }
public DateTime LogDate { get; set; }
public Int16 Priority { get; set; }
public string SourceClass { get; set; }
public string Category { get; set; }
public string Message { get; set; }
public string UserID { get; set; }
public string ProcessID { get; set; }
}
From the models, there are a number of fields that I would like to merge as well as fields that are not similar that I would also like to include. The model below shows exactly what I want but I just don't know how to implement it.
Internal Errors
public class InternalErrors
{
public string Id { get; set; } //L:ID && E:ErrorId
public int Priority { get; set; } //L:Priority
public string Application { get; set; } //L:SourceClass && E:Application
public string Message { get; set; } //L:Message && E:Message
public string Type { get; set; } //L:Category && E:Type
public string User { get; set; } //L:UserID && E:User
public string ProcessID { get; set; } //L:ProcessID
public DateTime Date { get; set; } //L:LogDate && E:TimeUtc
public int StatusCode { get; set; } //E:StatusCode
public string AllXml { get; set; } //E:AllXml
public int Sequence { get; set; } //E:Sequence
public int ErrorCount { get; set; } //E:ErrorCount
}
I hope this is enough information for you to provide an answer, if you need anything else, let me know.
Thanks in advance
if what you want is this
Internal Errors
ID Name Message Date
1 Something Hello World 01/01/2015
2 Some Log A log message 02/01/2015
3 Something Else Another Message 03/01/2015
4 Another Log Another Log Message 04/01/2015
then create a class with name InternalErrors as follows.
public class InternalErrors
{
public int ID;
public string Name;
public string Message;
public DateTime Date;
}
Now you can write a Linq Query as follows to get data from Application Errors and Log Errors and Perform union on it.
var AppErrors=from AE in _db.ApplicationErrors select AE;
var LogErrors=from LE in _dc.LogErrors select LE;
var internerrors=AppErrors.Union(LogErrors);
var InternalErrors=(from ie in internerrors select new InternalErrors()
{
ID=ie.ID,
Message=ie.ApplicationMessage,
Name=ie.ApplicationName,
Date=ie.ApplicationDate
}).ToList();
The viewmodel approach from MRebati is the best solution.
I often find it usefull to have a base class and different implementations:
public abstract class ErrorViewModel
{
public abstract int Id { get; }
public abstract string Name { get; }
}
public class ElmahErrorViewModel
{
public ElmahErrorViewModel(ElmahError instance)
{
this.Instance = instance;
}
public ElmahError Instance { get; private set; }
public int Id { get { return Instance.ErrorId; } }
public string Name { get { return instance.Appication; } }
}
that way you can create a List<ErrorViewModel> and add entries with
var items = from e in context.ElmahErrors
select new ElmahErrorViewModel(e);
list.AddRange(items);
var items2 = from l in context.LogEntrys
select new LogEntryViewModel(l);
list.AddRange(items2);
This is very usefull since you hide the details but you still can seprate the list and access the underlying object with
var elmahErrors = items.OfType<ElmahErrorViewModel>().Select(x => x.Instance);
There are many ways to provide data from the models to the View.
One is the ViewModel. It must contain the data you want to send to view. Look at this:
using System;
public class ErrorViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public string Message { get; set; }
public DateTime Date { get; set; }
}
And in the Controller you need to Create a list of this ViewModel and populate it with your data.
you can use linq
using System;
using System.Linq;
using System.Collections.Generic;
public class Program
{
public static void Main()
{
var elmahErrorList = new List<ElmahError>{
new ElmahError{ ErrorId = Guid.NewGuid(), Application = "Something",Message = "Hello World" , TimeUtc = DateTime.Now },
new ElmahError{ ErrorId = Guid.NewGuid(), Application = "Something Else",Message = "Another Message" , TimeUtc = DateTime.Now }
};
var logEntryList = new List<LogEntry>{
new LogEntry{ ID = 1, SourceClass = "Something",Message = "Hello World" , LogDate = DateTime.Now },
new LogEntry{ ID = 1, SourceClass = "Something Else",Message = "Another Message" , LogDate = DateTime.Now }
};
var internalErrorsList = new List<InternalErrors>();
var elmahErrorListinternalErrorses = elmahErrorList.Select(e => new InternalErrors
{
Id = e.ErrorId.ToString(),
Application = e.Application,
Message = e.Message,
Type = e.Type,
User = e.User,
Date = e.TimeUtc,
StatusCode = e.StatusCode,
AllXml = e.AllXml,
Sequence = e.Sequence
});
internalErrorsList.AddRange(elmahErrorListinternalErrorses);
var elmahErrorListlogEntryLists = logEntryList.Select(l => new InternalErrors
{
Id = l.ID.ToString(),
Priority = l.Priority,
Application = l.SourceClass,
Message = l.Message,
Type = l.Category,
User = l.UserID,
Date = l.LogDate
});
internalErrorsList.AddRange(elmahErrorListlogEntryLists);
internalErrorsList.ForEach(f =>
{
Console.Write(f.Id); Console.Write("\t");
Console.Write(f.Application);Console.Write("\t");
Console.Write(f.Message);Console.Write("\t");
Console.Write(f.Date);Console.Write("\t");
Console.WriteLine();
});
}
public class InternalErrors
{
public string Id { get; set; } //L:ID && E:ErrorId
public int Priority { get; set; } //L:Priority
public string Application { get; set; } //L:SourceClass && E:Application
public string Message { get; set; } //L:Message && E:Message
public string Type { get; set; } //L:Category && E:Type
public string User { get; set; } //L:UserID && E:User
public string ProcessID { get; set; } //L:ProcessID
public DateTime Date { get; set; } //L:LogDate && E:TimeUtc
public int StatusCode { get; set; } //E:StatusCode
public string AllXml { get; set; } //E:AllXml
public int Sequence { get; set; } //E:Sequence
public int ErrorCount { get; set; } //E:ErrorCount
}
public class ElmahError
{
public System.Guid ErrorId { get; set; }
public System.String Application { get; set; }
public System.String Host { get; set; }
public System.String Type { get; set; }
public System.String Source { get; set; }
public System.String Message { get; set; }
public System.String User { get; set; }
public System.Int32 StatusCode { get; set; }
public System.DateTime TimeUtc { get; set; }
public System.Int32 Sequence { get; set; }
public System.String AllXml { get; set; }
}
public class LogEntry
{
public Int64 ID { get; set; }
public DateTime LogDate { get; set; }
public Int16 Priority { get; set; }
public string SourceClass { get; set; }
public string Category { get; set; }
public string Message { get; set; }
public string UserID { get; set; }
public string ProcessID { get; set; }
}
}
Demo : https://dotnetfiddle.net/mrWGDn

ASP.NET MVC4: How to create a lookup table so entries aren't repeated

virI'm trying to create a lookup table in my ASP.NET MVC4 application with Entity Framework Code First. It is for our locations and there should be two entries. I need to have some sort of ID associated with them so there is a LocationID stored in my Software table. However, when I create them there is an entry to created for each row in the Software Table.
Here is my Software class:
public class Software
{
public int Id { get; set; }
public virtual List<SoftwareType> SoftwareTypes { get; set; }
public virtual List<Location> Locations { get; set; }
public virtual List<SoftwarePublisher> Publishers { get; set; }
[Required]
[StringLength(128)]
public string Title { get; set; }
[Required]
[StringLength(10)]
public string Version { get; set; }
[Required]
[StringLength(128)]
public string SerialNumber { get; set; }
[Required]
[StringLength(3)]
public string Platform { get; set; }
[StringLength(1000)]
public string Notes { get; set; }
[Required]
[StringLength(15)]
public string PurchaseDate { get; set; }
public bool Suite { get; set; }
public string SubscriptionEndDate { get; set; }
//[Required]
//[StringLength(3)]
public int SeatCount { get; set; }
}
Here is my Locations class:
public class Location
{
public int Id { get; set; }
[Required]
[StringLength(20)]
public string LocationName { get; set; }
public virtual Software Software { get; set; }
}
Here is my Global.asax call to a seed method:
Database.SetInitializer(new SampleData());
using (var context = new Context())
{
context.Database.Initialize(true);
}
Here is my context:
public class Context : DbContext
{
public DbSet<Software> Software { get; set; }
public DbSet<Location> Locations { get; set; }
public DbSet<SoftwarePublisher> SoftwarePublishers { get; set; }
public DbSet<SoftwareType> SoftwareTypes { get; set; }
public Context()
{
Configuration.ProxyCreationEnabled = false;
}
}
And here is my seeding:
public class SampleData : CreateDatabaseIfNotExists<Context>
{
protected override void Seed(Context context)
{
new List<Software> {
new Software {
Title = "Adobe Creative Suite",
Version = "CS6",
SerialNumber = "1234634543",
Platform = "Mac",
Notes = "Macs rock!",
PurchaseDate = "2012-12-04",
Suite = true,
SubscriptionEndDate = null,
SeatCount = 0,
SoftwareTypes = new List<SoftwareType>
{ new SoftwareType { Type="Suite" }},
Locations = new List<Location>
{ new Location { LocationName = "Paradise" }},
Publishers = new List<SoftwarePublisher>
{ new SoftwarePublisher { Publisher = "Adobe" }}},
...other similar rows...
}.ForEach(s => context.Software.Add(s));
base.Seed(context);
context.SaveChanges();
}
Because I am creating a new list for things like Locations (I need to fix the other things like SoftwareType and Publisher, but let's focus on Locations), it is creating a new row in my Locations table. How do I restructure my classes, so that I have two entries in my Locations table and then IDs in my Software table pointing to one of those two entries? Please bear in mind I am an Entity Framework newbie, so please try to be explicit. Thanks.
I think you want a many to many relationship between Software and Locations. To do that, you'll need to create a join table (also called a linking table). I believe you want to do this in your OnModelCreating override
this.HasMany(i => i.Softwares)
.WithMany(c => c.Locations)
.Map(mc =>
{
mc.MapLeftKey("SoftwareId");
mc.MapRightKey("LocationId");
mc.ToTable("SoftwareLocations");
});
I got that snippet from this blog post

EF4.1 - Attribute Evaluating to null at runtime

I'm using EF4.1 code first to create a simple database app with SQL CE 4 backend. I have a Product class and a CallItem class defined as so:
class CallItem
{
public int id { get; set; }
public float discount { get; set; }
public virtual Product Product { get; set; }
}
class Product
{
public int id { get; set; }
public decimal BaseCost { get; set; }
public int UnitSize { get; set; }
public bool isWasteOil { get; set; }
public string Code { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string Ingredients { get; set; }
}
edit - When I am creating a collection of CallItems using a LINQ query, I cannot access the attributes of the Product attached to each CallItem, eg
var callItems = from ci in context.CallItems select ci;
foreach(CallItem callItem in callItems)
{
RunSheet nrs = new RunSheet();
nrs.prodCode = callitem.Product.Code;
}
Interrogating the database shows that Productid in CallItems is being populated. However, the following line generates a NullReferenceException during run time:
nrs.prodCode = callitem.Product.Code;
Because callitem.Product is evaluating to null. Is this something to do with lazy loading and if so how can I resolve the issue?
RunSheet is another class, nrs is an instance whose attribute 'prodCode' I want to populate with the CallItem's Product's code.
Thanks!
From that code what you've showed it should work. Have you tried explicit loading?
var callItems = from ci in context.CallItems.Include(c => c.Product) select ci;
foreach(CallItem callItem in callItems)
{
RunSheet nrs = new RunSheet();
nrs.prodCode = callitem.Product.Code;
}
public class CallItem
{
public int Id { get; set; }
public float Discount { get; set; }
public virtual Product Product { get; set; }
}
public class Product
{
public int Id { get; set; }
public decimal BaseCost { get; set; }
public int UnitSize { get; set; }
public bool IsWasteOil { get; set; }
public string Code { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string Ingredients { get; set; }
}
using (var context = new StackOverFlowContext())
{
var p = new Product
{
Id = 1,
BaseCost = 200,
Code = "Hola",
Description = "Soe description",
Ingredients = "Some ingredients",
IsWasteOil = true,
Name = "My Product",
UnitSize = 10
};
var item = new CallItem
{
Id = 101,
Discount = 10,
Product = p
};
context.CallItems.Add(item);
context.SaveChanges();
var result = from temp in context.CallItems
select temp;
Console.WriteLine("CallItem Id"+result.First().Id);
Console.WriteLine("ProductId"+result.First().Product.Id);
}
I wrote the above code with the following output
CallItemId 1
ProductId 1
The sql Profiler showed this
SELECT TOP (1)
[c].[Id] AS [Id],
[c].[Discount] AS [Discount],
[c].[Product_Id] AS [Product_Id]
FROM [dbo].[CallItems] AS [c]
It was too long for a comment ,so i put it here .

Categories

Resources