How to custom unity visual scripting's Trigger Transition node? - unity3d

using UnityEngine;
namespace Unity.VisualScripting
{
/// <summary>
/// Triggers the transition in the parent state graph.
/// </summary>
[UnitSurtitle("State")]
[UnitCategory("Nesting")]
[UnitShortTitle("Trigger Transition")]
[TypeIcon(typeof(IStateTransition))]
public sealed class TriggerStateTransition : Unit
{
/// <summary>
/// The moment at which the parent state transition should be triggered.
/// </summary>
[DoNotSerialize]
[PortLabelHidden]
public ControlInput trigger { get; private set; }
protected override void Definition()
{
trigger = ControlInput(nameof(trigger), Trigger);
}
private ControlOutput Trigger(Flow flow)
{
var stateTransition = flow.stack.GetParent<INesterStateTransition>();
flow.stack.ExitParentElement();
Debug.Log($"change state !!!");
stateTransition.Branch(flow);
return null;
}
}
}
After I copy this code from ProjectName\Library\PackageCache\com.unity.visualscripting#1.7.7\Runtime\VisualScripting.State\TriggerStateTransition.cs and add some debug log the state graph turn gray but it still work to translate to next state.
How can I custom the Trigger Transition node but and do not change graph look?
before:
state graph before
transition before
after:
state graph after
transition after

Related

Unity - I have my movement script with a hold down run but i want to turn it into a toggle run

AH Yes, I have this script for simple movement it has the main movement at the last. in the script i check for the input from my input script and do the movement and for script i check if the character is running (which is input from different script) and if it is running movement speed is equal to running speed. else it is walking speed.
i want my character to have a toggle sprint instead of hold down (which this script has)
Can anyone help me to input toggle run.Thank You
movement script
using System.Linq;
using UnityEngine;
namespace LowPolyShooterPack
{
[RequireComponent(typeof(Rigidbody), typeof(CapsuleCollider))]
public class Movement : MovementBehaviour
{
#region FIELDS SERIALIZED
[Header("Audio Clips")]
[Tooltip("The audio clip that is played while walking.")]
[SerializeField]
private AudioClip audioClipWalking;
[Tooltip("The audio clip that is played while running.")]
[SerializeField]
private AudioClip audioClipRunning;
[Header("Speeds")]
[SerializeField]
private float speedWalking = 5.0f;
[Tooltip("How fast the player moves while running."), SerializeField]
private float speedRunning = 9.0f;
#endregion
#region PROPERTIES
//Velocity.
private Vector3 Velocity
{
//Getter.
get => rigidBody.velocity;
//Setter.
set => rigidBody.velocity = value;
}
#endregion
#region FIELDS
/// <summary>
/// Attached Rigidbody.
/// </summary>
private Rigidbody rigidBody;
/// <summary>
/// Attached CapsuleCollider.
/// </summary>
private CapsuleCollider capsule;
/// <summary>
/// Attached AudioSource.
/// </summary>
private AudioSource audioSource;
/// <summary>
/// True if the character is currently grounded.
/// </summary>
private bool grounded;
/// <summary>
/// Player Character.
/// </summary>
private CharacterBehaviour playerCharacter;
/// <summary>
/// The player character's equipped weapon.
/// </summary>
private WeaponBehaviour equippedWeapon;
/// <summary>
/// Array of RaycastHits used for ground checking.
/// </summary>
private readonly RaycastHit[] groundHits = new RaycastHit[8];
#endregion
#region UNITY FUNCTIONS
/// <summary>
/// Awake.
/// </summary>
protected override void Awake()
{
//Get Player Character.
playerCharacter = ServiceLocator.Current.Get<IGameModeService>().GetPlayerCharacter();
}
/// Initializes the FpsController on start.
protected override void Start()
{
//Rigidbody Setup.
rigidBody = GetComponent<Rigidbody>();
rigidBody.constraints = RigidbodyConstraints.FreezeRotation;
//Cache the CapsuleCollider.
capsule = GetComponent<CapsuleCollider>();
//Audio Source Setup.
audioSource = GetComponent<AudioSource>();
audioSource.clip = audioClipWalking;
audioSource.loop = true;
}
/// Checks if the character is on the ground.
private void OnCollisionStay()
{
//Bounds.
Bounds bounds = capsule.bounds;
//Extents.
Vector3 extents = bounds.extents;
//Radius.
float radius = extents.x - 0.01f;
//Cast. This checks whether there is indeed ground, or not.
Physics.SphereCastNonAlloc(bounds.center, radius, Vector3.down,
groundHits, extents.y - radius * 0.5f, ~0, QueryTriggerInteraction.Ignore);
//We can ignore the rest if we don't have any proper hits.
if (!groundHits.Any(hit => hit.collider != null && hit.collider != capsule))
return;
//Store RaycastHits.
for (var i = 0; i < groundHits.Length; i++)
groundHits[i] = new RaycastHit();
//Set grounded. Now we know for sure that we're grounded.
grounded = true;
}
protected override void FixedUpdate()
{
//Move.
MoveCharacter();
//Unground.
grounded = false;
}
/// Moves the camera to the character, processes jumping and plays sounds every
frame.
protected override void Update()
{
//Get the equipped weapon!
equippedWeapon = playerCharacter.GetInventory().GetEquipped();
//Play Sounds!
PlayFootstepSounds();
}
#endregion
#region METHODS
private void MoveCharacter()
{
#region Calculate Movement Velocity
//Get Movement Input!
Vector2 frameInput = playerCharacter.GetInputMovement();
//Calculate local-space direction by using the player's input.
var movement = new Vector3(frameInput.x, 0.0f, frameInput.y);
//Running speed calculation.
if(playerCharacter.IsRunning())
movement *= speedRunning;
else
{
//Multiply by the normal walking speed.
movement *= speedWalking;
}
//World space velocity calculation. This allows us to add it to the rigidbody's
velocity properly.
movement = transform.TransformDirection(movement);
#endregion
//Update Velocity.
Velocity = new Vector3(movement.x, 0.0f, movement.z);
}
/// <summary>
/// Plays Footstep Sounds. This code is slightly old, so may not be great, but it
functions alright-y!
/// </summary>
private void PlayFootstepSounds()
{
//Check if we're moving on the ground. We don't need footsteps in the air.
if (grounded && rigidBody.velocity.sqrMagnitude > 0.1f)
{
//Select the correct audio clip to play.
audioSource.clip = playerCharacter.IsRunning() ? audioClipRunning :
audioClipWalking;
//Play it!
if (!audioSource.isPlaying)
audioSource.Play();
}
//Pause it if we're doing something like flying, or not moving!
else if (audioSource.isPlaying)
audioSource.Pause();
}
#endregion
}
}
Character Behavior
using UnityEngine;
namespace LowPolyShooterPack
{
/// <summary>
/// Character Abstract Behaviour.
/// </summary>
public abstract class CharacterBehaviour : MonoBehaviour
{
#region UNITY
/// <summary>
/// Awake.
/// </summary>
protected virtual void Awake(){}
/// <summary>
/// Start.
/// </summary>
protected virtual void Start(){}
/// <summary>
/// Update.
/// </summary>
protected virtual void Update(){}
/// <summary>
/// Late Update.
/// </summary>
protected virtual void LateUpdate(){}
#endregion
#region GETTERS
/// <summary>
/// Returns the player character's main camera.
/// </summary>
public abstract Camera GetCameraWorld();
/// <summary>
/// Returns a reference to the Inventory component.
/// </summary>
public abstract InventoryBehaviour GetInventory();
/// <summary>
/// Returns true if the Crosshair should be visible.
/// </summary>
public abstract bool IsCrosshairVisible();
/// <summary>
/// Returns true if the character is running.
/// </summary>
public abstract bool IsRunning();
/// <summary>
/// Returns true if the character is aiming.
/// </summary>
public abstract bool IsAiming();
/// <summary>
/// Returns true if the game cursor is locked.
/// </summary>
public abstract bool IsCursorLocked();
/// <summary>
/// Returns true if the tutorial text should be visible on the screen.
/// </summary>
public abstract bool IsTutorialTextVisible();
/// <summary>
/// Returns the Movement Input.
/// </summary>
public abstract Vector2 GetInputMovement();
/// <summary>
/// Returns the Look Input.
/// </summary>
public abstract Vector2 GetInputLook();
#endregion
#region ANIMATION
/// <summary>
/// Ejects a casing from the equipped weapon.
/// </summary>
public abstract void EjectCasing();
/// <summary>
/// Fills the character's equipped weapon's ammunition by a certain amount, or fully if set to -1.
/// </summary>
public abstract void FillAmmunition(int amount);
/// <summary>
/// Sets the equipped weapon's magazine to be active or inactive!
/// </summary>
public abstract void SetActiveMagazine(int active);
/// <summary>
/// Reload Animation Ended.
/// </summary>
public abstract void AnimationEndedReload();
/// <summary>
/// Inspect Animation Ended.
/// </summary>
public abstract void AnimationEndedInspect();
/// <summary>
/// Holster Animation Ended.
/// </summary>
public abstract void AnimationEndedHolster();
#endregion
}
}
any help would be really grateful.
BTW i tried myself by implementing this code'
if(Input.getKeyDown("shift") {
playerCharacter.isRunning = !playerCharacter.isRunning;
}
if(playerCharacter.isRunning)
{
movement *= speedRunning;
}
I would guess all you gotta do is to add
Input.GetKeyDown(KeyCode.LeftShift) {
playerCharacter.isRunning = !playerCharacter.isRunning;
}
Into your Update Function just before the MoveCharacter.
Maybe add a Debug.Log("Running: " + playerCharacter.isRunning); in the if or in the Update Method.
I guess why the thing you tried does not work is because you would need to use
Input.GetKeyDown("left shift") or Input.GetKeyDown("right shift") Using the KeyCode is more save in general though.

Is it still possible to do ContentOverride in Winforms ViewModels?

Previously it was possible to use ContentOverride in Winforms ViewModels.
Is this still possible in MDriven 7.0.0.11262?
I am not aware of any changes so it should work as before. The ContentOverride signal the render-logic (Eco.ViewModel.WinForms.ViewModelUserControl) to call the OnColumnUIOverride event with the following argument:
/// <summary>
/// Arguments for the OnColumnUIOverrideArgs event; fired to allow for override of column render.
/// If you have other UI components that you want to use in ViewModel driven UI's this event provides
/// a hook point to inject them.
/// </summary>
public class OnColumnUIOverrideArgs : EventArgs
{
public ViewModel ViewModel;
/// <summary>
/// The Column in question
/// </summary>
public ViewModelColumn ViewModelColumn;
/// <summary>
/// What kind of UI we had in mind
/// </summary>
public ViewModelUIComponentType ViewModelUIComponentType;
/// <summary>
/// Set this to false to stop us from calling default logic
/// </summary>
public bool ContinueWithDefault;
}

Load scene with param variable Unity

In my game there is a map view that contains a 50x50 grid of tiles. When you click on the tile you get sent to that tiles view and attack things, etc. The only difference between these "tiles" as far as the code will be concerned is the tiles ID, aka. which number on the grid. That number will be passed to the server on init to handle the rest.
Obviously, with that being the only difference in tiles it would be a mistake to create a scene "1", scene "2"...scene "2500" and call SceneManager.LoadScene to switch to the specific tile view.
I could use DontDestroyOnLoad(); when the tile is clicked to preserve the tile ID on scene switch but 1) it only accepts gameobjects not just an int variable 2) I don't need/want to preserve that variable for anything more than the init in tile view. So while it could work it seems like overkill.
Is there a better practice for essentially just passing a parameter to a scene load?
You can make a static class that holds the information for you. This class will not be attached to any GameObject and will not be destroyed when changing scene. It is static which means there can only be ONE of it; you can't write StaticClassName scn = new StaticClassName() to create new static classes. You access them straight through StaticClassName.SomeStaticMethod() for example and can be accessed from anywhere. See this example on how to store a value in a variable, change scene and then use it in that scene (please note the added using UnityEngine.SceneManagement;):
A normal Unity script attached to a GameObject in Scene "Test":
using UnityEngine;
using UnityEngine.SceneManagement;
public class TestingScript : MonoBehaviour
{
void Start()
{
StaticClass.CrossSceneInformation = "Hello Scene2!";
SceneManager.LoadScene("Test2");
}
}
A new static class (not inheriting from monobehaviour) that holds information:
public static class StaticClass
{
public static string CrossSceneInformation { get; set; }
}
A script attached to a game object in scene "Test2":
using UnityEngine;
public class TestingScript2: MonoBehaviour
{
void Start ()
{
Debug.Log(StaticClass.CrossSceneInformation);
}
}
You don't need to have the entire class static (if you for some reason need to create more instances of it). If you were to remove the static from the class (not the variable) you can still access the static variable through StaticClass.CrossSceneInformation but you can also do StaticClass sc = new StaticClass();. With this sc you can use the class's non-static members but not the static CrossSceneInformation since there can only be ONE of that (because it's static).
Just wanted to share a premade solution for this as the question is already answered, so others or maybe even the OP use it.
This script uses a key-value approach for storing and modifying variables inside a static class that means it is available across scenes so you can use it as a cross-scene persistent storage, so all you need to do is import the script to your Unity project and use the API (check below for an example):
using System.Collections.Generic;
/// <summary>
/// A simple static class to get and set globally accessible variables through a key-value approach.
/// </summary>
/// <remarks>
/// <para>Uses a key-value approach (dictionary) for storing and modifying variables.</para>
/// <para>It also uses a lock to ensure consistency between the threads.</para>
/// </remarks>
public static class GlobalVariables
{
private static readonly object lockObject = new object();
private static Dictionary<string, object> variablesDictionary = new Dictionary<string, object>();
/// <summary>
/// The underlying key-value storage (dictionary).
/// </summary>
/// <value>Gets the underlying variables dictionary</value>
public static Dictionary<string, object> VariablesDictionary => variablesDictionary;
/// <summary>
/// Retrieves all global variables.
/// </summary>
/// <returns>The global variables dictionary object.</returns>
public static Dictionary<string, object> GetAll()
{
return variablesDictionary;
}
/// <summary>
/// Gets a variable and casts it to the provided type argument.
/// </summary>
/// <typeparam name="T">The type of the variable</typeparam>
/// <param name="key">The variable key</param>
/// <returns>The casted variable value</returns>
public static T Get<T>(string key)
{
if (variablesDictionary == null || !variablesDictionary.ContainsKey(key))
{
return default(T);
}
return (T)variablesDictionary[key];
}
/// <summary>
/// Sets the variable, the existing value gets overridden.
/// </summary>
/// <remarks>It uses a lock under the hood to ensure consistensy between threads</remarks>
/// <param name="key">The variable name/key</param>
/// <param name="value">The variable value</param>
public static void Set(string key, object value)
{
lock (lockObject)
{
if (variablesDictionary == null)
{
variablesDictionary = new Dictionary<string, object>();
}
variablesDictionary[key] = value;
}
}
}
And you can use it on a script that is inside your Main Menu scene let's say:
public class MainMenuScript : MonoBehaviour
{
void Start()
{
// Load a sample level
LoadLevel(12);
}
public void LoadLevel(int level)
{
GlobalVariables.Set("currentLevelIndex", level);
UnityEngine.SceneManagement.SceneManager.LoadScene("Game");
}
}
And the other one is inside Game scene:
public class GameScript : MonoBehaviour
{
void Start()
{
int levelIndex = GlobalVariables.Get<int>("currentLevelIndex");
Debug.Log(levelIndex); // Outputs 12
}
}
So, the data is assigned in the Main Menu scene is accessible from the Game or any other scene.
Learn more about the script with usage examples >
Maakep! Perfect and easy code!
But your method to load scene not working.
You can use another method:
UnityEngine.SceneManagement.SceneManager.LoadScene("Vuforia-4-Spheric");

Workflow Foundation Native activity child activities on designer

I've created Native Activity that looks like this:
public sealed class ConsoleColorScope : NativeActivity
{
#region Constructors and Destructors
/// <summary>
/// Initializes a new instance of the <see cref="ConsoleColorScope"/> class.
/// </summary>
public ConsoleColorScope()
{
this.Color = ConsoleColor.Gray;
}
#endregion
#region Public Properties
/// <summary>
/// Gets or sets Body.
/// </summary>
[DefaultValue(null)]
public Activity Body { get; set; }
/// <summary>
/// Gets or sets Color.
/// </summary>
public ConsoleColor Color { get; set; }
#endregion
#region Methods
/// <summary>
/// The execute.
/// </summary>
/// <param name="context">
/// The context.
/// </param>
protected override void Execute(NativeActivityContext context)
{
context.Properties.Add(ConsoleColorProperty.Name, new ConsoleColorProperty(this.Color));
if (this.Body != null)
{
context.ScheduleActivity(this.Body);
}
}
#endregion
/// <summary>
/// The console color property.
/// </summary>
private class ConsoleColorProperty : IExecutionProperty
{
#region Constants and Fields
/// <summary>
/// The name.
/// </summary>
public const string Name = "ConsoleColorProperty";
/// <summary>
/// The color.
/// </summary>
private readonly ConsoleColor color;
/// <summary>
/// The original.
/// </summary>
private ConsoleColor original;
#endregion
#region Constructors and Destructors
/// <summary>
/// Initializes a new instance of the <see cref="ConsoleColorProperty"/> class.
/// </summary>
/// <param name="color">
/// The color.
/// </param>
public ConsoleColorProperty(ConsoleColor color)
{
this.color = color;
}
#endregion
#region Explicit Interface Methods
/// <summary>
/// Cleanup the workflow thread.
/// </summary>
void IExecutionProperty.CleanupWorkflowThread()
{
Console.ForegroundColor = this.original;
}
/// <summary>
/// Setup the workflow thread.
/// </summary>
void IExecutionProperty.SetupWorkflowThread()
{
this.original = Console.ForegroundColor;
Console.ForegroundColor = this.color;
}
#endregion
}
}
This is class taken from the working sample:
http://code.msdn.microsoft.com/windowsdesktop/Windows-Workflow-c5649c23#content
However, when I open XAML file, I'm unable to see child activities within scope, as it is shown on the picture on the link above. All I can see is the names of the scopes.
I've created my own version of NativeActivity and I have same issue. Is there some procedure that I have to follow that would allow me to see body of NativeActivity in which I can drag and drop other activities (similar to Sequence activity) as it is shown on the demo description?
You'll need to create an Activity Designer project to go along with your custom activity which has a drop-zone (a WorkflowItemPresenter control) for placing an activity to fill your custom activity's body property with. Then you can setup the plumbing to link your designer to an activity. The following illustrates the steps in detail.
Create A New Activity Designer Project
In your solution, add a new Activity Designer project named like <Your Custom Activity Library>.Design. The assembly must be named <Your Custom Activity Library>.Design.dll, so that Visual Studio will use your activity designers for your custom activities. In the XAML for your designer, you'll utilize the WorkflowItemPresenter to display a drop-zone that accepts an activity that users of your custom activity can use.
<sap:ActivityDesigner x:Class="Your_Custom_Activity_Library.Design.ConsoleColorScopeDesigner"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sap="clr-namespace:System.Activities.Presentation;assembly=System.Activities.Presentation"
xmlns:sapv="clr-namespace:System.Activities.Presentation.View;assembly=System.Activities.Presentation">
<Grid>
<sap:WorkflowItemPresenter HintText="Drop an activity here!"
Item="{Binding Path=ModelItem.Body, Mode=TwoWay}"
MinWidth="300"
MinHeight="150" />
</Grid>
</sap:ActivityDesigner>
The ModelItem in the XAML represents your activity, and you can have your designer set any property in your activity with it. In the above sample, I'm setting up the WorkflowItemPresenter to bind to your activity's Body property.
Registering Designers With Your Activity
Next, add a class called RegisterMetadata (can be any name really) that implements the IRegisterMetadata interface. Its implementation is very simple:
public class RegisterMetadata : IRegisterMetadata
{
/// <summary>
/// Registers all activity designer metadata.
/// </summary>
public static void RegisterAll()
{
// Attribute table builder for the metadata store.
AttributeTableBuilder builder = new AttributeTableBuilder();
// Register the designer for the custom activities.
builder.AddCustomAttributes(typeof(ConsoleColorScope), new DesignerAttribute(typeof(ConsoleColorScopeDesigner)));
// Add the attributes to the metadata store.
MetadataStore.AddAttributeTable(builder.CreateTable());
}
/// <summary>
/// Registers this instance.
/// </summary>
public void Register()
{
RegisterMetadata.RegisterAll();
}
}
The way Visual Studio loads your custom activity designers is by looking for assemblies named <Your Custom Activity Library>.Design.dll, and then it looks for a public class that implements the IRegisterMetadata interface.
You should now be able to drag and drop your custom activity to a workflow and it will have a drop-zone allowing you to specify the Body activity.
You can get fancy with your designer, and expose friendly controls to allow a user to setup your custom activity. You could also create your own designers for the out-of-the-box activities provided in the .NET Framework.
Hope that helps.

Convert Library from ObjectContext to DbContext

I have a library (based on code found in an old blog post) that allows me to very easily wrap a façade around my data access using Entity Framework. It uses ObjectContext and has performed well enough for my purposes.
But now, we are excitedly investigating code first using DbContext and of course would like to reuse / adapt as much of our existing effort as possible.
Everything went ok naively converting our Facade enabling library with IObjectContextAdapter until we tried to utilise our facade, when the following error was received:
The type 'Employee' cannot be used as type parameter 'TEntity' in the generic type or method 'DbContextManagement.FacadeBase'. There is no implicit reference conversion from 'Employee' to 'System.Data.Objects.DataClasses.EntityObject'
MSDN says:
EntityObject derived types are not supported by the DbContext API, to use these entity types you must use the ObjectContext API.
That's fine, but how would I then go ahead completing my refactor to bypass this inability?
Here's some code (line breaks introduced):
FacadeBase.cs
namespace DbContextManagement
{
using System;
using System.Collections;
using System.Configuration;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Data.Metadata.Edm;
using System.Data.Objects.DataClasses;
using System.Linq;
using System.Reflection;
public abstract class FacadeBase<TDbContext, TEntity>
where TDbContext : DbContext, new()
where TEntity : EntityObject
{
protected TDbContext DbContext
{
get
{
if (DbContextManager == null)
{
this.InstantiateDbContextManager();
}
return DbContextManager.GetDbContext<TDbContext>();
}
}
private DbContextManager DbContextManager { get; set; }
public virtual void Add(TEntity newObject)
{
var context = ((IObjectContextAdapter)this.DbContext).ObjectContext;
string entitySetName;
if (newObject.EntityKey != null)
{
entitySetName = newObject.EntityKey.EntitySetName;
}
else
{
string entityTypeName = newObject.GetType().Name;
var container = context.MetadataWorkspace.GetEntityContainer(
context.DefaultContainerName,
DataSpace.CSpace);
entitySetName = (from meta in container.BaseEntitySets
where meta.ElementType.Name ==
entityTypeName
select meta.Name).First();
}
context.AddObject(entitySetName, newObject);
}
public virtual void Delete(TEntity obsoleteObject)
{
var context = ((IObjectContextAdapter)this.DbContext).ObjectContext;
context.DeleteObject(obsoleteObject);
}
private void InstantiateDbContextManager()
{
var objectContextManagerConfiguration =
ConfigurationManager.GetSection("DbContext") as Hashtable;
if (objectContextManagerConfiguration != null &&
objectContextManagerConfiguration.ContainsKey("managerType"))
{
var managerTypeName =
objectContextManagerConfiguration["managerType"] as string;
if (string.IsNullOrEmpty(managerTypeName))
{
throw new ConfigurationErrorsException(
"The managerType attribute is empty.");
}
managerTypeName = managerTypeName.Trim().ToLower();
try
{
var frameworkAssembly =
Assembly.GetAssembly(typeof(DbContextManager));
var managerType =
frameworkAssembly.GetType(managerTypeName, true, true);
this.DbContextManager =
Activator.CreateInstance(managerType) as DbContextManager;
}
catch (Exception e)
{
throw new ConfigurationErrorsException(
"The managerType specified in the
configuration is not valid.", e);
}
}
else
{
throw new ConfigurationErrorsException(
"A Facade.DbContext tag or its managerType attribute
is missing in the configuration.");
}
}
}
}
EmployeeFacade.cs
namespace Facade
{
using System.Collections.Generic;
using System.Linq;
using DataModel;
using DataModel.Entities;
using DbContextManagement;
public sealed class EmployeeFacade : FacadeBase<FleetContext, Employee>
{
public Employee GetById(int? employeeId)
{
return employeeId == null
? null
: this.DbContext.Employees.FirstOrDefault(m => m.Id == employeeId);
}
}
}
Employee.cs
namespace DataModel.Entities
{
public class Employee
{
public int Id { get; set; }
public string Surname { get; set; }
public string Forename { get; set; }
public string EmployeeNumber { get; set; }
}
}
If your entities are derived from EntityObject and the code you want to reuse is dependent on EntityObject based entities then it is a show stopper. You cannot use DbContext API until your entities are POCOs (no EntityObject parent).
Btw. you can use code only mapping with ObjectContext (and POCOs) without ever using DbContext. You just need to:
Create EntityTypeConfiguration or ComplexTypeConfiguration based class for each your POCO entity / complex type describing the mapping
Use DbModelBuilder to collect your configurations and call Build to get DbModel instance
Call Compile on your DbModel instance to get DbCompiledModel
Cache compiled model for lifetime of your application
When you need a new ObjectContext instance call CreateObjectContext on compiled model
Just be aware that code mapping has much more limited feature set so not everything you currently have in EDMX will be possible to achieve through code mapping.
With a very large nod to the author of the original code in the link given above, here is what I ended up with. It passes basic scenarios but I haven't yet tried out deeper navigational and related queries. Even if there is an issue, I reckon it will be bug fix rather than show stop.
Here goes. The following is reusable, works with multiple contexts and means you can go to the database and get something back in 2 lines of actual code in the client.
DataModel (Class Library Project)
Your EF Code First project with DbContext POCOs, etc.
DbContextManagement (Class Library Project)
DbContextManager.cs
namespace DbContextManagement
{
using System.Data.Entity;
/// <summary>
/// Abstract base class for all other DbContextManager classes.
/// </summary>
public abstract class DbContextManager
{
/// <summary>
/// Returns a reference to an DbContext instance.
/// </summary>
/// <typeparam name="TDbContext">The type of the db context.</typeparam>
/// <returns>The current DbContext</returns>
public abstract TDbContext GetDbContext<TDbContext>()
where TDbContext : DbContext, new();
}
}
DbContextScope.cs
namespace DbContextManagement
{
using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Threading;
/// <summary>
/// Defines a scope wherein only one DbContext instance is created, and shared by all of those who use it.
/// </summary>
/// <remarks>Instances of this class are supposed to be used in a using() statement.</remarks>
public class DbContextScope : IDisposable
{
/// <summary>
/// List of current DbContexts (supports multiple contexts).
/// </summary>
private readonly List<DbContext> contextList;
/// <summary>
/// DbContext scope definitiion.
/// </summary>
[ThreadStatic]
private static DbContextScope currentScope;
/// <summary>
/// Holds a value indicating whether the context is disposed or not.
/// </summary>
private bool isDisposed;
/// <summary>
/// Initializes a new instance of the <see cref="DbContextScope"/> class.
/// </summary>
/// <param name="saveAllChangesAtEndOfScope">if set to <c>true</c> [save all changes at end of scope].</param>
protected DbContextScope(bool saveAllChangesAtEndOfScope)
{
if (currentScope != null && !currentScope.isDisposed)
{
throw new InvalidOperationException("DbContextScope instances cannot be nested.");
}
this.SaveAllChangesAtEndOfScope = saveAllChangesAtEndOfScope;
this.contextList = new List<DbContext>();
this.isDisposed = false;
Thread.BeginThreadAffinity();
currentScope = this;
}
/// <summary>
/// Gets or sets a value indicating whether to automatically save all object changes at end of the scope.
/// </summary>
/// <value><c>true</c> if [save all changes at end of scope]; otherwise, <c>false</c>.</value>
private bool SaveAllChangesAtEndOfScope { get; set; }
/// <summary>
/// Save all object changes to the underlying datastore.
/// </summary>
public void SaveAllChanges()
{
var transactions = new List<DbTransaction>();
foreach (var context in this.contextList
.Select(dbcontext => ((IObjectContextAdapter)dbcontext)
.ObjectContext))
{
context.Connection.Open();
var databaseTransaction = context.Connection.BeginTransaction();
transactions.Add(databaseTransaction);
try
{
context.SaveChanges();
}
catch
{
/* Rollback & dispose all transactions: */
foreach (var transaction in transactions)
{
try
{
transaction.Rollback();
}
catch
{
// "Empty general catch clause suppresses any errors."
// Haven't quite figured out what to do here yet.
}
finally
{
databaseTransaction.Dispose();
}
}
transactions.Clear();
throw;
}
}
try
{
/* Commit all complete transactions: */
foreach (var completeTransaction in transactions)
{
completeTransaction.Commit();
}
}
finally
{
/* Dispose all transactions: */
foreach (var transaction in transactions)
{
transaction.Dispose();
}
transactions.Clear();
/* Close all open connections: */
foreach (var context in this.contextList
.Select(dbcontext => ((IObjectContextAdapter)dbcontext).ObjectContext)
.Where(context => context.Connection.State != System.Data.ConnectionState.Closed))
{
context.Connection.Close();
}
}
}
/// <summary>
/// Disposes the DbContext.
/// </summary>
public void Dispose()
{
// Monitor for possible future bugfix.
// CA1063 : Microsoft.Design : Provide an overridable implementation of Dispose(bool)
// on 'DbContextScope' or mark the type as sealed. A call to Dispose(false) should
// only clean up native resources. A call to Dispose(true) should clean up both managed
// and native resources.
if (this.isDisposed)
{
return;
}
// Monitor for possible future bugfix.
// CA1063 : Microsoft.Design : Modify 'DbContextScope.Dispose()' so that it calls
// Dispose(true), then calls GC.SuppressFinalize on the current object instance
// ('this' or 'Me' in Visual Basic), and then returns.
currentScope = null;
Thread.EndThreadAffinity();
try
{
if (this.SaveAllChangesAtEndOfScope && this.contextList.Count > 0)
{
this.SaveAllChanges();
}
}
finally
{
foreach (var context in this.contextList)
{
try
{
context.Dispose();
}
catch (ObjectDisposedException)
{
// Monitor for possible future bugfix.
// CA2202 : Microsoft.Usage : Object 'databaseTransaction' can be disposed
// more than once in method 'DbContextScope.SaveAllChanges()'.
// To avoid generating a System.ObjectDisposedException you should not call
// Dispose more than one time on an object.
}
}
this.isDisposed = true;
}
}
/// <summary>
/// Returns a reference to a DbContext of a specific type that is - or will be -
/// created for the current scope. If no scope currently exists, null is returned.
/// </summary>
/// <typeparam name="TDbContext">The type of the db context.</typeparam>
/// <returns>The current DbContext</returns>
protected internal static TDbContext GetCurrentDbContext<TDbContext>()
where TDbContext : DbContext, new()
{
if (currentScope == null)
{
return null;
}
var contextOfType = currentScope.contextList
.OfType<TDbContext>()
.FirstOrDefault();
if (contextOfType == null)
{
contextOfType = new TDbContext();
currentScope.contextList.Add(contextOfType);
}
return contextOfType;
}
}
}
FacadeBase.cs
namespace DbContextManagement
{
using System;
using System.Collections;
using System.Collections.Generic;
using System.Configuration;
using System.Data.Entity;
using System.Reflection;
/// <summary>
/// Generic base class for all other Facade classes.
/// </summary>
/// <typeparam name="TDbContext">The type of the db context.</typeparam>
/// <typeparam name="TEntity">The type of the entity.</typeparam>
/// <typeparam name="TEntityKey">The type of the entity key.</typeparam>
/// <remarks>Not sure the handling of TEntityKey is something I've worked with properly.</remarks>
public abstract class FacadeBase<TDbContext, TEntity, TEntityKey>
where TDbContext : DbContext, new()
where TEntity : class
{
/// <summary>
/// Gets the db context.
/// </summary>
public TDbContext DbContext
{
get
{
if (DbContextManager == null)
{
this.InstantiateDbContextManager();
}
return DbContextManager != null
? DbContextManager.GetDbContext<TDbContext>()
: null;
}
}
/// <summary>
/// Gets or sets the DbContextManager.
/// </summary>
/// <value>The DbContextManager.</value>
private DbContextManager DbContextManager { get; set; }
/// <summary>
/// Adds a new entity object to the context.
/// </summary>
/// <param name="newObject">A new object.</param>
public virtual void Add(TEntity newObject)
{
this.DbContext.Set<TEntity>().Add(newObject);
}
/// <summary>
/// Deletes an entity object.
/// </summary>
/// <param name="obsoleteObject">An obsolete object.</param>
public virtual void Delete(TEntity obsoleteObject)
{
this.DbContext.Set<TEntity>().Remove(obsoleteObject);
}
/// <summary>
/// Gets all entities for the given type.
/// </summary>
/// <returns>DbContext Set of TEntity.</returns>
public virtual IEnumerable<TEntity> GetAll()
{
return this.DbContext.Set<TEntity>();
}
/// <summary>
/// Gets the entity by the specified entity key.
/// </summary>
/// <param name="entityKey">The entity key.</param>
/// <returns>Entity matching specified entity key or null if not found.</returns>
public virtual TEntity GetByKey(TEntityKey entityKey)
{
return this.DbContext.Set<TEntity>().Find(entityKey);
}
/// <summary>
/// Deletes the entity for the specified entity key.
/// </summary>
/// <param name="entityKey">The entity key.</param>
public virtual void DeleteByKey(TEntityKey entityKey)
{
var entity = this.DbContext.Set<TEntity>().Find(entityKey);
if (entity != null)
{
this.DbContext.Set<TEntity>().Remove(entity);
}
}
/// <summary>
/// Instantiates a new DbContextManager based on application configuration settings.
/// </summary>
private void InstantiateDbContextManager()
{
/* Retrieve DbContextManager configuration settings: */
var contextManagerConfiguration = ConfigurationManager.GetSection("DbContext") as Hashtable;
if (contextManagerConfiguration == null)
{
throw new ConfigurationErrorsException("A Facade.DbContext tag or its managerType attribute is missing in the configuration.");
}
if (!contextManagerConfiguration.ContainsKey("managerType"))
{
throw new ConfigurationErrorsException("dbManagerConfiguration does not contain key 'managerType'.");
}
var managerTypeName = contextManagerConfiguration["managerType"] as string;
if (string.IsNullOrEmpty(managerTypeName))
{
throw new ConfigurationErrorsException("The managerType attribute is empty.");
}
managerTypeName = managerTypeName.Trim().ToUpperInvariant();
try
{
/* Try to create a type based on it's name: */
var frameworkAssembly = Assembly.GetAssembly(typeof(DbContextManager));
var managerType = frameworkAssembly.GetType(managerTypeName, true, true);
/* Try to create a new instance of the specified DbContextManager type: */
this.DbContextManager = Activator.CreateInstance(managerType) as DbContextManager;
}
catch (Exception e)
{
throw new ConfigurationErrorsException("The managerType specified in the configuration is not valid.", e);
}
}
}
}
ScopedDbContextManager.cs
namespace DbContextManagement
{
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
/// <summary>
/// Manages multiple db contexts.
/// </summary>
public sealed class ScopedDbContextManager : DbContextManager
{
/// <summary>
/// List of Object Contexts.
/// </summary>
private List<DbContext> contextList;
/// <summary>
/// Returns the DbContext instance that belongs to the current DbContextScope.
/// If currently no DbContextScope exists, a local instance of an DbContext
/// class is returned.
/// </summary>
/// <typeparam name="TDbContext">The type of the db context.</typeparam>
/// <returns>Current scoped DbContext.</returns>
public override TDbContext GetDbContext<TDbContext>()
{
var currentDbContext = DbContextScope.GetCurrentDbContext<TDbContext>();
if (currentDbContext != null)
{
return currentDbContext;
}
if (this.contextList == null)
{
this.contextList = new List<DbContext>();
}
currentDbContext = this.contextList.OfType<TDbContext>().FirstOrDefault();
if (currentDbContext == null)
{
currentDbContext = new TDbContext();
this.contextList.Add(currentDbContext);
}
return currentDbContext;
}
}
}
UnitOfWorkScope.cs
namespace DbContextManagement
{
/// <summary>
/// Defines a scope for a business transaction. At the end of the scope all object changes can be persisted to the underlying datastore.
/// </summary>
/// <remarks>Instances of this class are supposed to be used in a using() statement.</remarks>
public sealed class UnitOfWorkScope : DbContextScope
{
/// <summary>
/// Initializes a new instance of the <see cref="UnitOfWorkScope"/> class.
/// </summary>
/// <param name="saveAllChangesAtEndOfScope">if set to <c>true</c> [save all changes at end of scope].</param>
public UnitOfWorkScope(bool saveAllChangesAtEndOfScope)
: base(saveAllChangesAtEndOfScope)
{
}
}
}
Facade (Class Library Project)
YourEntityFacade.cs
namespace Facade
{
using System.Collections.Generic;
using System.Linq;
using DataModel;
using DataModel.Entities;
using DbContextManagement;
public class YourEntityFacade : FacadeBase<YourDbContext, YourEntity, int>
{
public override IEnumerable<YourEntity> GetAll()
{
return base.GetAll()
.Distinct()
.ToList();
}
}
}
TestConsole (Console Application Project)
App.config
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="DbContext" type="System.Configuration.SingleTagSectionHandler" />
</configSections>
<DbContext managerType="DbContextManagement.ScopedDbContextManager" />
<connectionStrings>
<add
name="YourDbContext"
providerName="System.Data.SqlClient"
connectionString="Your connection string" />
</connectionStrings>
</configuration>
Program.cs
namespace TestConsole
{
using System;
using System.Collections.Generic;
using DataModel.Entities;
using DbContextManagement;
using Facade;
public static class Program
{
public static void Main()
{
TestGetAll();
Console.ReadLine();
}
private static void TestGetAll()
{
Console.WriteLine();
Console.WriteLine("Test GetAll()");
Console.WriteLine();
IEnumerable<YourEntity> yourEntities;
using (new UnitOfWorkScope(false))
{
yourEntities= new YourEntityFacade().GetAll();
}
if (yourEntities != null)
{
foreach (var yourEntity in yourEntities)
{
Console.WriteLine(
string.Format("{0}, {1}",
yourEntity.Id,
yourEntity.Name));
}
}
else
{
Console.WriteLine("GetAll() NULL");
}
}
}
}
So, usage is very simple and you can work with multiple contexts and facades within a scope. With this approach, the only code you are writing is custom queries. No more endless updating UnitOfWorks with repository references and authoring copy-cat repositories. I think it's great but be aware this is beta code and I'm sure it has a large hole somewhere that will need plugging :)
Thank you to all and in particular Ladislav for patience and help on this and many other related questions I ask. I hope this code is of interest. I contacted the author at the blog above but no reply yet and these days I think he's into NHibernate.
Richard