We currently use sp's and udf's to generate an XML document that is sent via web service to the client for consumption. I have used an ORM (Entity and Open Access) before but I alway interacted directly with a single class (table). If I had data that needed to goto multiple tables I would break it apart in code and just update the ORM class (table) the data belonged to.
I am wondering if I can get a bit more complicated. Can I somehow create a complex object in the ORM that is an aggregate of all these fields. In other words I would pass to the constructor of my complex orm object the record key desired. The object would then gather from all the tables the needed info and return to me the object.....which I could then serialize into xml and send on it's way.
I understand that somewhere in the ORM or the DB I have to have the logic to gather all the pieces together so really just looking for input.
If I want an object comprised of data from multiple tables I just add all the tables to the .edmx file. From there I can create a class that gets whatever I want. There's no need for a 1:1 pairing. Within my method to create the object based on say the primary key from TableA you can just do;
Obj GetObj(string primaryKey)
{
dataBase context = new dataBase();
var obj = (from a
in context.TableA
where a.Id == primaryKey
select a);
var otherObj = (from b
in context.TableB
where b.Id == a.ForeignKey
select b);
Obj foo = new Obj();
foo.Id = a.Id;
foo.SomethingElse = a.Somthing;
foo.FromB = b.Id;
foo.AnInt = (int)b.count;
return foo;
}
To save some changes;
void AddNewObject()
{
dataBase context = new Context;
TableA a = new TableA();
TableB b = new TableB();
a.Id = this.Id;
a.OtherField = this.OtherField;
b.Key = this.BKey;
b.SomeInt = this.SomeInt;
context.AddObject("TableA", a);
context.AddObject("TableB", b);
context.SaveChanges();
}
Of course these will need some error handling I haven't included but the concept isn't that different from when you have the standard EntityObject<->Table mapping. I think it's simpler to keep some of the normal mapping then build your class on top of it.
Related
Background
I am changing my LINQ-to-SQL code in my project to Entity Framework. Most of the change over was relatively simple, however, I have run into a fairly major issue. With LINQ-to-SQL, I was able to load an entire object graph (for Model B) using stored procedures like so:
ViewModel.Model = MyDbContext.usp_ModelA_GetByID(AId).Single();
List<ModelB> Details =
(from b in MyDbContext.usp_ModelB_GetByID(BId)
join c in MyDbContext.usp_ModelC_GetAll()
on b.CId equals c.CId
select new ModelB()
{
BId = b.BId,
CId = b.CId,
C = c
}).ToList();
ViewModel.Model.ModelBs.AddRange(Details);
However, after converting this code to EF, on the line where ViewModel.Model.ModelBs is accessed, I get the error "EntityCommandExecutionException" with the inner exception explaining that "The SELECT permission was denied on the object 'ModelBTable'." Obviously, EF is attempting to fetch the ModelBs for the ModelA even though I have already loaded them from the database. While I don't fully understand why it's trying to load the entities even though I have added them, I can only assume that because it didn't load them itself, it doesn't believe they are fully loaded and probably views all of the objects I loaded into it as "New".
In an effort to bypass EF attempting to fetch the objects itself, I decided to change my code to:
ViewModel.Model = MyDbContext.usp_ModelA_GetByID(AId).Single();
List<ModelB> Details =
(from b in MyDbContext.usp_ModelB_GetByID(BId)
join c in MyDbContext.usp_ModelC_GetAll()
on b.CId equals c.CId
select new ModelB()
{
BId = b.BId,
CId = c.CId,
C = c
}).ToList();
ViewModel.Model.ModelBs = new EntityCollection<ModelB>();
foreach (ModelB detail in Details)
{
ViewModel.Model.ModelBs.Attach(detail);
}
After making this change, I now run into the error "InvalidOperationException" with a message of "The EntityCollection could not be initialized because the relationship manager for the object to which the EntityCollection belongs is already attached to an ObjectContext. The InitializeRelatedCollection method should only be called to initialize a new EntityCollection during deserialization of an object graph.".
This is confusing enough because I am using the same context to load all of the entities so I'm unsure as to why it won't allow me to combine them together. I am able to do this in other ORMs without issue.
After researching this error, I decided to attempt an approach that I hoped would trick EF into thinking that the entire object graph was loaded by the same context so I rewrote my code to be:
ViewModel.Model =
(from a in MyDbContext.usp_ModelA_GetByID(AId)
select new A()
{
AId = a.AId,
ModelBs = (from b in MyDbContext.usp_ModelB_GetByID(BId)
join c in MyDbContext.usp_ModelC_GetAll()
on b.CId equals c.CId
select new ModelB()
{
BId = b.BId,
CId = b.CId,
C = c
}).ToEntityCollection()
}).Single();
with ToEntityCollection being an extension method I created like so:
public static EntityCollection<TEntity> ToEntityCollection<TEntity>(
this IEnumerable<TEntity> source) where TEntity : class, IEntityWithRelationships
{
EntityCollection<TEntity> set = new EntityCollection<TEntity>();
foreach (TEntity entity in source)
{
set.Attach(entity);
}
return set;
}
Now, I get the error "InvalidOperationException" with a message of "Requested operation is not allowed when the owner of this RelatedEnd is null. RelatedEnd objects that were created with the default constructor should only be used as a container during serialization.".
After extensively researching each of these errors, I was still unable to find a solution pertaining to my problem.
Question
So, after all of that, my question is: How do I load an entire object graph when each object has its own stored procedure using Entity Framework 4?
Update
So, based on the answers so far, I feel I need to include the following caveats here:
I am not looking for an answer that uses a single stored procedure to load an entire object graph. I am looking for a way to load an object graph using a get stored procedure per entity. I realize that loading the object graph using a single stored procedure could, theoretically perform much better, but at this time, I am more interested in smaller changes to the code base especially with regards to the way the database is structured.
If your solution requires editing the edmx directly, it will not be an acceptable answer. Since this is an auto-generated file, editing the edmx directly essentially means that those same changes would need to be re-done upon any modification through the designer.
Update 2
So, after some deliberation, I came up with a work around. What I did was change my ViewModel to have a List ModelBs property that pulls the data using the stored procedure joins and in my view, I am just setting this property as the datasource. This is definitely not what I would consider to be an optimal solution because now my ViewModel is acting more like the Model than a ViewModel and I can no longer traverse my ModelA type to get the list of ModelBs, but it works! I still don't understand why I can do:
(from b in MyDbContext.usp_ModelB_GetByID(BId)
join c in MyDbContext.usp_ModelC_GetAll()
on b.CId equals c.CId
select new ModelB()
{
BId = b.BId,
CId = b.CId,
C = c //<------Setting a navigation property and EF figures out that it belongs
}).ToList();
but I can't do:
(from a in MyDbContext.usp_ModelA_GetByID(AId)
select new ModelA()
{
AId = a.AId,
ModelBs = MyDbContext.usp_ModelB_GetByID(BId).ToEntityCollection() //<----Won't let me set the navigation property when the navigation property is a collection.
}).Single();
It can be done in a fairly simple way but takes some manual effort. Here is an MSDN post on handling stored procedures with multiple result sets which shows both a code first and database first approach.
Example:
Load EntityB proc:
create proc dbo.Get_EntityB_by_EntityAId( #aId int )
as
select distinct
b.EntityBId
, b.Description
from
EntityA a
left outer join EntityB b
on a.PrimaryEntityB_EntityBId = b.EntityBId
left outer join EntityB b2
on a.AlternativeEntityB_EntityBId = b2.EntityBId
where
a.EntityAId = #aId
go
Load EntityA proc (which calls load B proc)
create proc dbo.Get_EntityA_by_Id( #id int )
as
-- use a select statement
select
a.EntityAId
, a.Description
, a.PrimaryEntityB_EntityBId
, a.AlternativeEntityB_EntityBId
from
EntityA a
where
a.EntityAId = #id
-- and/or other sprocs
exec dbo.Get_EntityB_by_EntityAId #id
go
Entity classes
[Table("EntityA")]
public partial class EntityA
{
public int EntityAId { get; set; }
public string Description { get; set; }
public virtual EntityB PrimaryEntityB { get; set; }
public virtual EntityB AlternativeEntityB { get; set; }
}
[Table("EntityB")]
public partial class EntityB
{
public int EntityBId { get; set; }
public string Description { get; set; }
[InverseProperty("PrimaryEntityB")]
public virtual ICollection<EntityA> EntityAsViaPrimary { get; set; }
[InverseProperty( "AlternativeEntityB" )]
public virtual ICollection<EntityA> EntityAsViaAlternative { get; set; }
}
Method that calls sproc and handles results (for this method, you could return the one EntityA if you'd like)
public static void EagerLoadEntityA( int aId )
{
using( var db = new TestEntities() )
{
// if using code first
db.Database.Initialize( false );
var cmd = db.Database.Connection.CreateCommand();
cmd.CommandText = "dbo.Get_EntityA_by_Id";
db.Database.Connection.Open();
try
{
var reader = cmd.ExecuteReader();
var objContext = ( ( IObjectContextAdapter )db ).ObjectContext;
var aEntities = objContext
.Translate<EntityA>( reader, "EntityAs", MergeOption.AppendOnly );
reader.NextResult();
var bEntities = objContext
.Translate<EntityB>( reader, "EntityBs", MergeOption.AppendOnly );
}
finally
{
db.Database.Connection.Close();
}
}
}
Usage:
EagerLoadEntityA( 1234 );
var entityA = db.EntityAs.Find( 1234 ); // cached
var primB = entityA.PrimaryEntityB; // this is already loaded
Okay, so after even further deliberation, I figured out a solution that works for what I am wanting. Since I am in a web environment and have no need to lazily load objects, I turned EnableLazyLoading to false for the entire DbContext. Then, using an EF feature called the magical relationship fix-up, I am able to do the following:
ViewModel.Model = MyDbContext.usp_ModelA_GetByID(AId).Single();
var Details =
(from b in MyDbContext.usp_ModelB_GetByID(BId)
join c in MyDbContext.usp_ModelC_GetAll()
on b.CId equals c.CId
select new ModelB()
{
BId = b.BId,
CId = b.CId,
C = c
}).ToList();
//ToList() executes the proc and projects the plate details into the object
//graph which never tries to select from the database because LazyLoadingEnabled is
//false. Then, the magical relationship fix-up allows me to traverse my object graph
//using ViewModel.Model.ModelBs which returns all of the ModelBs loaded into the graph
//that are related to my ModelA.
Stored procedures cannot be directly associated with an Entity for the purposes of selecting / reading data. Typically stored procedures that are used for retrieval will return complex types and not entities. In order to interact with the DB indirectly, EF provides the ability to associate an Entity with a View for reads, and stored procedures for Insert, Update, and Delete.
Read this article for a full summary of working with EF and stored procedures.
http://msdn.microsoft.com/en-us/data/gg699321.aspx
Model #1 - This model sits in a database on our Dev Server.
Model #1 http://content.screencast.com/users/Keith.Barrows/folders/Jing/media/bdb2b000-6e60-4af0-a7a1-2bb6b05d8bc1/Model1.png
Model #2 - This model sits in a database on our Prod Server and is updated each day by automatic feeds. alt text http://content.screencast.com/users/Keith.Barrows/folders/Jing/media/4260259f-bce6-43d5-9d2a-017bd9a980d4/Model2.png
I have written what should be some simple code to sync my feed (Model #2) into my working DB (Model #1). Please note this is prototype code and the models may not be as pretty as they should. Also, the entry into Model #1 for the feed link data (mainly ClientID) is a manual process at this point which is why I am writing this simple sync method.
private void SyncFeeds()
{
var sourceList = from a in _dbFeed.Auto where a.Active == true select a;
foreach (RivWorks.Model.NegotiationAutos.Auto source in sourceList)
{
var targetList = from a in _dbRiv.Product where a.alternateProductID == source.AutoID select a;
if (targetList.Count() > 0)
{
// UPDATE...
try
{
var product = targetList.First();
product.alternateProductID = source.AutoID;
product.isFromFeed = true;
product.isDeleted = false;
product.SKU = source.StockNumber;
_dbRiv.SaveChanges();
}
catch (Exception ex)
{
string m = ex.Message;
}
}
else
{
// INSERT...
try
{
long clientID = source.Client.ClientID;
var companyDetail = (from a in _dbRiv.AutoNegotiationDetails where a.ClientID == clientID select a).First();
var company = companyDetail.Company;
switch (companyDetail.FeedSourceTable.ToUpper())
{
case "AUTO":
var product = new RivWorks.Model.Negotiation.Product();
product.alternateProductID = source.AutoID;
product.isFromFeed = true;
product.isDeleted = false;
product.SKU = source.StockNumber;
company.Product.Add(product);
break;
}
_dbRiv.SaveChanges();
}
catch (Exception ex)
{
string m = ex.Message;
}
}
}
}
Now for the questions:
In Model #2, the class structure for Auto is missing ClientID (see red circled area). Now, everything I have learned, EF creates a child class of Client and I should be able to find the ClientID in the child class. Yet, when I run my code, source.Client is a NULL object. Am I expecting something that EF does not do? Is there a way to populate the child class correctly?
Why does EF hide the child entity ID (ClientID in this case) in the parent table? Is there any way to expose it?
What else sticks out like the proverbial sore thumb?
TIA
1) The reason you are seeing a null for source.Client is because related objects are not loaded until you request them, or they are otherwise loaded into the object context. The following will load them explicitly:
if (!source.ClientReference.IsLoaded)
{
source.ClientReference.Load();
}
However, this is sub-optimal when you have a list of more than one record, as it sends one database query per Load() call. A better alternative is to the Include() method in your initial query, to instruct the ORM to load the related entities you are interested in, so:
var sourceList = from a in _dbFeed.Auto .Include("Client") where a.Active == true select a;
An alternative third method is to use something call relationship fix-up, where if, in your example for instance, the related clients had been queried previously, they would still be in your object context. For example:
var clients = (from a in _dbFeed.Client select a).ToList();
The EF will then 'fix-up' the relationships so source.Client would not be null. Obviously this is only something you would do if you required a list of all clients for synching, so is not relevant for your specific example.
Always remember that objects are never loaded into the EF unless you request them!
2) The first version of the EF deliberately does not map foreign key fields to observable fields or properties. This is a good rundown on the matter. In EF4.0, I understand foreign keys will be exposed due to popular demand.
3) One issue you may run into is the number of database queries requesting Products or AutoNegotiationContacts may generate. As an alternative, consider loading them in bulk or with a join on your initial query.
It's also seen as good practice to use an object context for one 'operation', then dispose of it, rather than persisting them across requests. There is very little overhead in initialising one, so one object context per SychFeeds() is more appropriate. ObjectContext implements IDisposable, so you can instantiate it in a using block and wrap the method's contents in that, to ensure everything is cleaned up correctly once your changes are submitted.
Here is the code I'm working with, I'm still a bit new to LINQ, so this is a work in progress. Specifically, I'd like to get my results from this query (about 7 columns of strings, ints, and datetime), and return them to the method that called the method containing this LINQ to SQL query. A simple code example would be super helpful.
using (ormDataContext context = new ormDataContext(connStr))
{
var electionInfo = from t1 in context.elections
join t2 in context.election_status
on t1.statusID equals t2.statusID
select new { t1, t2 };
}
(In this case, my query is returning all the contents of 2 tables, election and election_status.)
Specifically, I'd like to get my
results from this query (about 7
columns of strings, ints, and
datetime), and return them
Hi, the problem you've got with your query is that you're creating an anonymous type. You cannot return an anonymous type from a method, so this is where you're going to have trouble.
What you will need to do is to create a "wrapper" type that can take an election and an election_status and then return those.
Here's a little sample of what I'm talking about; as you can see I declare a Tuple class. The method that you will wrap your query in returns an IEnumerable.
I hope this helps :-)
class Tuple
{
Election election;
Election_status election_status;
public Tuple(Election election, Election_status election_status)
{
this.election = election;
this.election_status = election_status;
}
}
public IEnumerable<Tuple> getElections()
{
IEnumerable<Tuple> result = null;
using (ormDataContext context = new ormDataContext(connStr))
{
result = from t1 in context.elections
join t2 in context.election_status
on t1.statusID equals t2.statusID
select new Tuple(t1, t2);
}
}
UPDATE
Following from NagaMensch's comments, a better way to achieve the desired result would be to use the built in LINQ to SQL associations.
If you go to your entity diagram and click on toolbox, you will see 3 options. Class, Association and Inheritance. We want to use Association.
Click on Association and click on the ElectionStatus entity, hold the mouse button down and it will allow you to draw a line to the Election entity.
Once you've drawn the line it will ask you which properties are involved in the association. You want to select the StatusId column from the Election entity, and the StatusId column from the ElectionStatus entity.
Now that you've completed your mapping you will be able to simplify your query greatly because the join will not be necessary. You can just access the election status via a brand new property that LINQ to SQL will have added to the Election entity.
Your code can now look like this:
//context has to be moved outside the function
static ExampleDataContext context = new ExampleDataContext();
//Here we can return an IEnumerable of Election now, instead of using the Tuple class
public static IEnumerable<Election> getElections()
{
return from election in context.Elections
select election;
}
static void Main(string[] args)
{
//get the elections
var elections = getElections();
//lets go through the elections
foreach (var election in elections)
{
//here we can access election status via the ElectionStatus property
Console.WriteLine("Election name: {0}; Election status: {1}", election.ElectionName, election.ElectionStatus.StatusDescription);
}
}
You can also find a "how to" on LINQ to SQL associations here.
Note: It's worth mentioning that if you have an FK relationship set up between your tables in the database; LINQ to SQL will automatically pick the relationship up and map the association for you (therefore creating the properties).
You'll need to create classes that have the same structure as the anonymous types. Then, instead of "new { t1, t2 }", you use "new MyClass(t1, t2)".
Once you have a named class, you can pass it all over the place as you were hoping.
The problem is, that you are creating a anonymous type, hence there is no way to declare a method with this return type. You have to create a new type that will hold your query result and return this type.
But I suggest not to return the result in a new type but return just a colection of election objects and access the election_status objects through the relation properties assuming you included them in your model. The data load options cause the query to include the related election status objects in the query result.
public IList<election> GetElections()
{
using (ormDataContext context = new ormDataContext(connStr))
{
DataLoadOptions dlo = new DataLoadOptions();
dlo.LoadWith<election>(e => e.election_status);
context.DeferredLoadingEnabled = false;
context.LoadOptions = dlo;
return context.elections.ToList();
}
}
Now you can do the following.
IList<election> elections = GetElections();
// Access the election status.
Console.WriteLin(elections[0].election_status);
I general LINQ to SQL could just retrieve the related entities on demand - that is called deferred loading.
ormDataContext context = new ormDataContext(connStr));
IList<election> elections = context.elections.ToList();
// This will trigger a query that loads the election
// status of the first election object.
Console.WriteLine(elections[0].election_status);
But this requires you not to close the data context until you finished using the retrieved objects, hence cannot be used with a using statement encapsulated in a method.
IEnumerable<object> getRecordsList()
{
using (var dbObj = new OrderEntryDbEntities())
{
return (from orderRec in dbObj.structSTIOrderUpdateTbls
select orderRec).ToList<object>();
}
}
maybe it can help :)
using (ormDataContext context = new ormDataContext(connStr))
{
var electionInfo = from t1 in context.elections
join t2 in context.election_status
on t1.statusID equals t2.statusID
select new {
t1.column1,
t1.column2,
t1.column3,
t1.column4,
t2.column5,
t2.column6,
t2.column7
};
}
foreach (var ei in electionInfo){
//write what do u want
}
return electionInfo.ToList();
You cannot return an anonymous type from a method. It is only available within the scope in which it is created. You'll have to create a class instead.
I've a table like this :
ID Name Key Value
1 Mydata1 Mail myval1
1 Mydata1 Name myval2
1 Mydata1 Nick myval3
1 Yourdata key2 myval4
and a class like this :
[Serializable]
public class Mydata
{
public string Mail{get;set;}
public string Name{get;set;}
public string Nick{get;set;}
}
I extract from my Table data
where Name = 'MyData1' in a list.
How To serialize this data in my class.
Thanks!
You should look into LINQ to SQL, LINQ to Entities, NHibernate, etc. These are OR Mappers, or Object/Relational Mappers. They are designed to query your database for you, and return serialized objects from your domain. They can greatly reduce the amount of effort it takes to build an application with objects that are persisted to and retrieved from a database server.
Note that Linq to SQL required SQL Server, while the other two can be used with any database.
If you have your class already, you are looking for "POCO" support in an ORM.
E.g. Entities Framework v4 does this. See here for details. I understand nHibernate has this support today.
Alternatively in your data access layer, you can do the conversion in your data access layer (DTO == Data Transfer Object) using a data reader:
var rs = dbCmd.ExecuteReader();
var results = new List<MyDto>();
while (rs.Read()) {
results.Add(new MyDto {
Id = rs.ReadInt("Id"),
Name = rs.ReadString("Name"),
...
};
}
Using LINQ to SQL:
using (var dc = MyDataContext()) {
var results = from d in dc.MyDataType
select new MyDto {
Id = d.Id,
Name = d.Name,
...
};
}
However I tend to add a method to customise the generated entity with a helper method to perform the mapping (which helps avoid repeating the property copy code for each query).
Normally when you update an object in linq2sql you get the object from a datacontext and use the same datacontext to save the object, right?
What's the best way to update a object that hasn't been retreived by that datacontext that you use to perform the save operation, i.e. I'm using flourinefx to pass data between flex and asp.net and when object return from the client to be saved I don't know how to save the object?
public static void Save(Client client)
{
CompanyDataContext db = new CompanyDataContext();
Validate(client);
if(client.Id.Equals(Guid.Empty))
{
//Create (right?):
client.Id = Guid.NewGuid();
db.Clients.InsertOnSubmit(client);
db.SubmitChanges();
}
else
{
//Update:
OffertaDataContext db = new OffertaDataContext();
db.Clients.????
}
}
Update: different approaches to use Attach doens't work in this case. So I guess a reflection based approach is required.
To update an existing but disconnected object, you need to "attach" it do the data context. This will re-use the existing primary key etc. You can control how to handle changes- i.e. treat as dirty, or treat as clean and track future changes, etc.
The Attach method is on the table - i.e.
ctx.Customers.Attach(customer); // optional bool to treat as modified
I think you have 2 options here:
1) Attach the object to the DataContext on which you will do your save
2) Using the primary key on your object, grab an instance that is attached to your context (e.g. do a FirstOrDefault()), and then copy the data over from the modified object to the object that has a context (reflection might be useful here).
Rick Strahl has a very good blog article on attaching entities to a context at http://www.west-wind.com/weblog/posts/134095.aspx, particularly in regards to some of the problems you might encounter.
I am hoping you can help. I am developing a tiered website using Linq to Sql. I created a new class(or object) in DBML designer called memberState. This object is not an actual table in the database. I have this method in my middle layer:
public override IEnumerable(memberState) GetMembersByState(string #state)<br/>
{<br/>
using (BulletinWizardDataContext context = DataContext)<br/>
{<br/>
IEnumerable(memberState) mems = (from m in context.Members<br/>
join ma in context.MemberAddresses<br/>
on m.UserId equals ma.UserId<br/>
join s in context.States<br/>
on ma.StateId equals s.StateId<br/>
where s.StateName == #state<br/>
select new memberState<br/>
{<br/>
userId = m.UserID,<br/>
firstName = m.FirstName,<br/>
middleInitial = m.MiddleInitial,<br/>
lastName = m.LastName,<br/>
createDate = m.CreateDate,<br/>
modifyDate = m.ModifyDate<br/>
}).ToArray(memberState)();<br/>
return mems;
}
}
The tables in my joins (Members, States, and MemberAddresses are actual tables in my Database). I created the object memberStates so I could use it in the query above (notice the Select New memberState. When the data is updated on the web page how do I persist the changes back to the Member Table? My Member Table consists of the following columns: UserId, FirstName, MiddleInitial, LastName, CreateDate, ModifyDate. I am not sure how save the changes back to the database.
Thanks,